From fe5b28e6bfb2d5414c62af911025686400d997ac Mon Sep 17 00:00:00 2001 From: Ekaterina Lebedeva Date: Thu, 6 Mar 2025 05:23:53 +0300 Subject: [PATCH 01/24] [#338] pool: Support avg request time for ListContainerStream Signed-off-by: Ekaterina Lebedeva --- pool/client.go | 30 +++++++++++++++++++++++++----- pool/statistic.go | 5 +++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pool/client.go b/pool/client.go index f1abbf2..7865072 100644 --- a/pool/client.go +++ b/pool/client.go @@ -90,6 +90,8 @@ func (m MethodIndex) String() string { return "containerGet" case methodContainerList: return "containerList" + case methodContainerListStream: + return "containerListStream" case methodContainerDelete: return "containerDelete" case methodEndpointInfo: @@ -457,13 +459,16 @@ type PrmListStream struct { // // Must be initialized using Pool.ListContainersStream, any other usage is unsafe. type ResListStream struct { - r *sdkClient.ContainerListReader - handleError func(context.Context, apistatus.Status, error) error + r *sdkClient.ContainerListReader + elapsedTimeCallback func(time.Duration) + handleError func(context.Context, apistatus.Status, error) error } // Read reads another list of the container identifiers. func (x *ResListStream) Read(buf []cid.ID) (int, error) { + start := time.Now() n, ok := x.r.Read(buf) + x.elapsedTimeCallback(time.Since(start)) if !ok { res, err := x.r.Close() if err == nil { @@ -487,7 +492,14 @@ func (x *ResListStream) Read(buf []cid.ID) (int, error) { // // Returns an error if container can't be read. func (x *ResListStream) Iterate(f func(cid.ID) bool) error { - return x.r.Iterate(f) + start := time.Now() + err := x.r.Iterate(func(id cid.ID) bool { + x.elapsedTimeCallback(time.Since(start)) + stop := f(id) + start = time.Now() + return stop + }) + return err } // Close ends reading list of the matched containers and returns the result of the operation @@ -508,11 +520,19 @@ func (c *clientWrapper) containerListStream(ctx context.Context, prm PrmListStre Session: prm.Session, } - res, err := cl.ContainerListInit(ctx, cliPrm) + start := time.Now() + cnrRdr, err := cl.ContainerListInit(ctx, cliPrm) + c.incRequests(time.Since(start), methodContainerListStream) if err = c.handleError(ctx, nil, err); err != nil { return ResListStream{}, fmt.Errorf("init container listing on client: %w", err) } - return ResListStream{r: res, handleError: c.handleError}, nil + return ResListStream{ + r: cnrRdr, + elapsedTimeCallback: func(elapsed time.Duration) { + c.incRequests(elapsed, methodContainerListStream) + }, + handleError: c.handleError, + }, nil } // containerDelete invokes sdkClient.ContainerDelete parse response status to error. diff --git a/pool/statistic.go b/pool/statistic.go index 40da88f..b9c2430 100644 --- a/pool/statistic.go +++ b/pool/statistic.go @@ -97,6 +97,11 @@ func (n NodeStatistic) AverageListContainer() time.Duration { return n.averageTime(methodContainerList) } +// AverageListContainerStream returns average time to perform ContainerListStream request. +func (n NodeStatistic) AverageListContainerStream() time.Duration { + return n.averageTime(methodContainerListStream) +} + // AverageDeleteContainer returns average time to perform ContainerDelete request. func (n NodeStatistic) AverageDeleteContainer() time.Duration { return n.averageTime(methodContainerDelete) From a262a0038f7dbd485e03c13c5f3e8f23dd3cad80 Mon Sep 17 00:00:00 2001 From: Ekaterina Lebedeva Date: Mon, 10 Mar 2025 19:24:26 +0300 Subject: [PATCH 02/24] [#343] pool: Fix Yoda condition go-staticcheck recommends not to use Yoda conditions. Signed-off-by: Ekaterina Lebedeva --- pool/pool_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pool/pool_test.go b/pool/pool_test.go index b063294..e6af664 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -372,7 +372,7 @@ func TestUpdateNodesHealth(t *testing.T) { changed := tc.wasHealthy != tc.willHealthy require.Equalf(t, tc.willHealthy, cli.isHealthy(), "healthy status should be: %v", tc.willHealthy) - require.Equalf(t, changed, 1 == log.Len(), "healthy status should be changed: %v", changed) + require.Equalf(t, changed, log.Len() == 1, "healthy status should be changed: %v", changed) }) } } From 87bb55f992dc3b7eb43c1bfc5f0fe4567c641d61 Mon Sep 17 00:00:00 2001 From: Pavel Pogodaev Date: Fri, 24 Jan 2025 12:41:07 +0300 Subject: [PATCH 03/24] [#51] add address to logs Signed-off-by: Pavel Pogodaev --- pool/object_put_pool_transformer.go | 7 ++++--- pool/pool.go | 2 +- pool/tree/client.go | 10 +++++++--- pool/tree/pool.go | 6 +++--- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pool/object_put_pool_transformer.go b/pool/object_put_pool_transformer.go index e596aeb..6955919 100644 --- a/pool/object_put_pool_transformer.go +++ b/pool/object_put_pool_transformer.go @@ -2,6 +2,7 @@ package pool import ( "context" + "fmt" sdkClient "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" @@ -134,7 +135,7 @@ func (it *internalTarget) putAsStream(ctx context.Context, o *object.Object) err it.res.OID = res.StoredObjectID() it.res.Epoch = res.StoredEpoch() } - return err + return fmt.Errorf("put as stream '%s': %w", it.address, err) } func (it *internalTarget) tryPutSingle(ctx context.Context, o *object.Object) (bool, error) { @@ -151,7 +152,7 @@ func (it *internalTarget) tryPutSingle(ctx context.Context, o *object.Object) (b res, err := it.client.ObjectPutSingle(ctx, cliPrm) if err != nil && status.Code(err) == codes.Unimplemented { - return false, err + return false, fmt.Errorf("address '%s': %w", it.address, err) } if err == nil { @@ -166,5 +167,5 @@ func (it *internalTarget) tryPutSingle(ctx context.Context, o *object.Object) (b } return true, nil } - return true, err + return true, fmt.Errorf("try put single '%s': %w", it.address, err) } diff --git a/pool/pool.go b/pool/pool.go index 2f30ae4..f6588c5 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -1646,7 +1646,7 @@ func (p *Pool) GetSplitInfo(ctx context.Context, cnrID cid.ID, objID oid.ID, tok case errors.As(err, &errSplit): return errSplit.SplitInfo(), nil case err == nil || errors.As(err, &errECInfo): - return nil, relations.ErrNoSplitInfo + return nil, fmt.Errorf("failed to get raw object header %w", relations.ErrNoSplitInfo) default: return nil, fmt.Errorf("failed to get raw object header: %w", err) } diff --git a/pool/tree/client.go b/pool/tree/client.go index b93b5e6..b7682d9 100644 --- a/pool/tree/client.go +++ b/pool/tree/client.go @@ -49,11 +49,11 @@ func (c *treeClient) dial(ctx context.Context) error { var err error c.client, err = c.createClient() if err != nil { - return err + return fmt.Errorf("couldn't dial '%s': %w", c.address, err) } if _, err = rpcapi.Healthcheck(c.client, &tree.HealthcheckRequest{}, rpcclient.WithContext(ctx)); err != nil { - return fmt.Errorf("healthcheck tree service: %w", err) + return fmt.Errorf("healthcheck tree service '%s': %w", c.address, err) } c.healthy = true @@ -127,5 +127,9 @@ func (c *treeClient) close() error { if c.client == nil || c.client.Conn() == nil { return nil } - return c.client.Conn().Close() + err := c.client.Conn().Close() + if err != nil { + return fmt.Errorf("address '%s': %w", c.address, err) + } + return nil } diff --git a/pool/tree/pool.go b/pool/tree/pool.go index f5cee26..dda74c1 100644 --- a/pool/tree/pool.go +++ b/pool/tree/pool.go @@ -295,7 +295,7 @@ func (p *Pool) Dial(ctx context.Context) error { for j, node := range nodes { clients[j] = newTreeClient(node.Address(), p.dialOptions, p.nodeDialTimeout, p.streamTimeout) if err := clients[j].dial(ctx); err != nil { - p.log(zap.WarnLevel, "failed to dial tree client", zap.String("address", node.Address()), zap.Error(err)) + p.log(zap.WarnLevel, "failed to dial tree client", zap.Error(err)) continue } @@ -1095,7 +1095,7 @@ func (p *Pool) getNewTreeClient(ctx context.Context, node netmap.NodeInfo) (*tre newTreeCl := newTreeClient(addr.URIAddr(), p.dialOptions, p.nodeDialTimeout, p.streamTimeout) if err = newTreeCl.dial(ctx); err != nil { - p.log(zap.WarnLevel, "failed to dial tree client", zap.String("address", addr.URIAddr()), zap.Error(err)) + p.log(zap.WarnLevel, "failed to dial tree client", zap.Error(err)) // We have to close connection here after failed `dial()`. // This is NOT necessary in object pool and regular tree pool without netmap support, because: @@ -1103,7 +1103,7 @@ func (p *Pool) getNewTreeClient(ctx context.Context, node netmap.NodeInfo) (*tre // - regular tree pool is going to reuse connection by calling `redialIfNecessary()`. // Tree pool with netmap support does not operate with background goroutine, so we have to close connection immediately. if err = newTreeCl.close(); err != nil { - p.log(zap.WarnLevel, "failed to close recently dialed tree client", zap.String("address", addr.URIAddr()), zap.Error(err)) + p.log(zap.WarnLevel, "failed to close recently dialed tree client", zap.Error(err)) } return false From 826df9303cb7510f7cc7890348bf579bf417bae8 Mon Sep 17 00:00:00 2001 From: Airat Arifullin Date: Wed, 26 Mar 2025 13:10:16 +0300 Subject: [PATCH 04/24] [#349] object: Regenerate protobuf for `Patch` method * `PatchRequestBody` got `NewSplitHeader` field * Introduce `SetNewSplitHeader`, `GetNewSplitHeader` * Fix converter and marshaler Signed-off-by: Airat Arifullin --- api/object/convert.go | 15 ++ api/object/grpc/service.pb.go | 248 ++++++++++++--------- api/object/grpc/service_protoopaque.pb.go | 250 ++++++++++++---------- api/object/marshal.go | 13 +- api/object/types.go | 10 + 5 files changed, 317 insertions(+), 219 deletions(-) diff --git a/api/object/convert.go b/api/object/convert.go index 016a367..90e40dc 100644 --- a/api/object/convert.go +++ b/api/object/convert.go @@ -2389,6 +2389,7 @@ func (r *PatchRequestBody) ToGRPCMessage() grpc.Message { m.SetNewAttributes(AttributesToGRPC(r.newAttributes)) m.SetReplaceAttributes(r.replaceAttributes) m.SetPatch(r.patch.ToGRPCMessage().(*object.PatchRequest_Body_Patch)) + m.SetNewSplitHeader(r.newSplitHeader.ToGRPCMessage().(*object.Header_Split)) } return m @@ -2437,6 +2438,20 @@ func (r *PatchRequestBody) FromGRPCMessage(m grpc.Message) error { } } + newSplitHeader := v.GetNewSplitHeader() + if newSplitHeader == nil { + r.newSplitHeader = nil + } else { + if r.newSplitHeader == nil { + r.newSplitHeader = new(SplitHeader) + } + + err = r.newSplitHeader.FromGRPCMessage(newSplitHeader) + if err != nil { + return err + } + } + return nil } diff --git a/api/object/grpc/service.pb.go b/api/object/grpc/service.pb.go index 9603467..8926f5c 100644 --- a/api/object/grpc/service.pb.go +++ b/api/object/grpc/service.pb.go @@ -5181,6 +5181,9 @@ type PatchRequest_Body struct { // merged. If the incoming `new_attributes` list contains already existing // key, then it just replaces it while merging the lists. ReplaceAttributes *bool `protobuf:"varint,3,opt,name=replace_attributes,json=replaceAttributes" json:"replace_attributes,omitempty"` + // New split header for the object. This defines how the object will relate + // to other objects in a split operation. + NewSplitHeader *Header_Split `protobuf:"bytes,5,opt,name=new_split_header,json=newSplitHeader" json:"new_split_header,omitempty"` // The patch that is applied for the object. Patch *PatchRequest_Body_Patch `protobuf:"bytes,4,opt,name=patch" json:"patch,omitempty"` unknownFields protoimpl.UnknownFields @@ -5233,6 +5236,13 @@ func (x *PatchRequest_Body) GetReplaceAttributes() bool { return false } +func (x *PatchRequest_Body) GetNewSplitHeader() *Header_Split { + if x != nil { + return x.NewSplitHeader + } + return nil +} + func (x *PatchRequest_Body) GetPatch() *PatchRequest_Body_Patch { if x != nil { return x.Patch @@ -5252,6 +5262,10 @@ func (x *PatchRequest_Body) SetReplaceAttributes(v bool) { x.ReplaceAttributes = &v } +func (x *PatchRequest_Body) SetNewSplitHeader(v *Header_Split) { + x.NewSplitHeader = v +} + func (x *PatchRequest_Body) SetPatch(v *PatchRequest_Body_Patch) { x.Patch = v } @@ -5270,6 +5284,13 @@ func (x *PatchRequest_Body) HasReplaceAttributes() bool { return x.ReplaceAttributes != nil } +func (x *PatchRequest_Body) HasNewSplitHeader() bool { + if x == nil { + return false + } + return x.NewSplitHeader != nil +} + func (x *PatchRequest_Body) HasPatch() bool { if x == nil { return false @@ -5285,6 +5306,10 @@ func (x *PatchRequest_Body) ClearReplaceAttributes() { x.ReplaceAttributes = nil } +func (x *PatchRequest_Body) ClearNewSplitHeader() { + x.NewSplitHeader = nil +} + func (x *PatchRequest_Body) ClearPatch() { x.Patch = nil } @@ -5305,6 +5330,9 @@ type PatchRequest_Body_builder struct { // merged. If the incoming `new_attributes` list contains already existing // key, then it just replaces it while merging the lists. ReplaceAttributes *bool + // New split header for the object. This defines how the object will relate + // to other objects in a split operation. + NewSplitHeader *Header_Split // The patch that is applied for the object. Patch *PatchRequest_Body_Patch } @@ -5316,6 +5344,7 @@ func (b0 PatchRequest_Body_builder) Build() *PatchRequest_Body { x.Address = b.Address x.NewAttributes = b.NewAttributes x.ReplaceAttributes = b.ReplaceAttributes + x.NewSplitHeader = b.NewSplitHeader x.Patch = b.Patch return m0 } @@ -5902,7 +5931,7 @@ var file_api_object_grpc_service_proto_rawDesc = []byte{ 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x06, 0x0a, - 0x04, 0x42, 0x6f, 0x64, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x04, 0x42, 0x6f, 0x64, 0x79, 0x22, 0xfd, 0x04, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, @@ -5916,7 +5945,7 @@ var file_api_object_grpc_service_proto_rawDesc = []byte{ 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0xcf, 0x02, 0x0a, 0x04, 0x42, 0x6f, + 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x99, 0x03, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, @@ -5928,87 +5957,92 @@ var file_api_object_grpc_service_proto_rawDesc = []byte{ 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, - 0x3f, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, - 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, - 0x6f, 0x64, 0x79, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x1a, 0x59, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x3a, 0x0a, 0x0c, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0xa4, 0x02, 0x0a, 0x0d, - 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, - 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, - 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, - 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x46, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x61, 0x5f, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, - 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, - 0x52, 0x0a, 0x0d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, + 0x48, 0x0a, 0x10, 0x6e, 0x65, 0x77, 0x5f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x5f, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x53, 0x70, + 0x6c, 0x69, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x05, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x50, 0x61, + 0x74, 0x63, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x59, 0x0a, 0x05, 0x50, 0x61, + 0x74, 0x63, 0x68, 0x12, 0x3a, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x61, + 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0xa4, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, + 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, + 0x79, 0x12, 0x46, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x1a, 0x3d, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x35, 0x0a, 0x09, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x2e, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x44, 0x52, 0x08, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x49, 0x64, 0x32, 0xd4, 0x05, 0x0a, 0x0d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, - 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, - 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x03, 0x50, 0x75, - 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0a, 0x6d, + 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x0d, 0x76, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, + 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x3d, 0x0a, + 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x35, 0x0a, 0x09, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x49, 0x44, 0x52, 0x08, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x32, 0xd4, 0x05, 0x0a, + 0x0d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, + 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, + 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, + 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, + 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x4b, 0x0a, 0x06, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, + 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, + 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, - 0x12, 0x4b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, - 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x65, - 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, - 0x04, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, - 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, - 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, + 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x21, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x25, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, - 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x50, 0x75, 0x74, 0x53, 0x69, - 0x6e, 0x67, 0x6c, 0x65, 0x12, 0x22, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, - 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, - 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, - 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, - 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, - 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, - 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0x62, 0x5a, 0x43, 0x67, 0x69, 0x74, - 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, - 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, - 0x66, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2d, 0x67, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0xaa, 0x02, 0x1a, 0x4e, 0x65, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x08, 0x65, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07, + 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, + 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, + 0x08, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x21, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6e, + 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x25, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x54, 0x0a, 0x09, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x12, 0x22, + 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, + 0x12, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x28, 0x01, 0x42, 0x62, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, + 0x66, 0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, + 0x64, 0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x73, 0x64, 0x6b, + 0x2d, 0x67, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x67, + 0x72, 0x70, 0x63, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0xaa, 0x02, 0x1a, 0x4e, 0x65, 0x6f, + 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, + 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x70, 0xe8, 0x07, } var file_api_object_grpc_service_proto_msgTypes = make([]protoimpl.MessageInfo, 42) @@ -6071,6 +6105,7 @@ var file_api_object_grpc_service_proto_goTypes = []any{ (grpc1.ChecksumType)(0), // 55: neo.fs.v2.refs.ChecksumType (*Object)(nil), // 56: neo.fs.v2.object.Object (*Header_Attribute)(nil), // 57: neo.fs.v2.object.Header.Attribute + (*Header_Split)(nil), // 58: neo.fs.v2.object.Header.Split } var file_api_object_grpc_service_proto_depIdxs = []int32{ 20, // 0: neo.fs.v2.object.GetRequest.body:type_name -> neo.fs.v2.object.GetRequest.Body @@ -6163,32 +6198,33 @@ var file_api_object_grpc_service_proto_depIdxs = []int32{ 56, // 87: neo.fs.v2.object.PutSingleRequest.Body.object:type_name -> neo.fs.v2.object.Object 48, // 88: neo.fs.v2.object.PatchRequest.Body.address:type_name -> neo.fs.v2.refs.Address 57, // 89: neo.fs.v2.object.PatchRequest.Body.new_attributes:type_name -> neo.fs.v2.object.Header.Attribute - 40, // 90: neo.fs.v2.object.PatchRequest.Body.patch:type_name -> neo.fs.v2.object.PatchRequest.Body.Patch - 11, // 91: neo.fs.v2.object.PatchRequest.Body.Patch.source_range:type_name -> neo.fs.v2.object.Range - 51, // 92: neo.fs.v2.object.PatchResponse.Body.object_id:type_name -> neo.fs.v2.refs.ObjectID - 0, // 93: neo.fs.v2.object.ObjectService.Get:input_type -> neo.fs.v2.object.GetRequest - 2, // 94: neo.fs.v2.object.ObjectService.Put:input_type -> neo.fs.v2.object.PutRequest - 4, // 95: neo.fs.v2.object.ObjectService.Delete:input_type -> neo.fs.v2.object.DeleteRequest - 6, // 96: neo.fs.v2.object.ObjectService.Head:input_type -> neo.fs.v2.object.HeadRequest - 9, // 97: neo.fs.v2.object.ObjectService.Search:input_type -> neo.fs.v2.object.SearchRequest - 12, // 98: neo.fs.v2.object.ObjectService.GetRange:input_type -> neo.fs.v2.object.GetRangeRequest - 14, // 99: neo.fs.v2.object.ObjectService.GetRangeHash:input_type -> neo.fs.v2.object.GetRangeHashRequest - 16, // 100: neo.fs.v2.object.ObjectService.PutSingle:input_type -> neo.fs.v2.object.PutSingleRequest - 18, // 101: neo.fs.v2.object.ObjectService.Patch:input_type -> neo.fs.v2.object.PatchRequest - 1, // 102: neo.fs.v2.object.ObjectService.Get:output_type -> neo.fs.v2.object.GetResponse - 3, // 103: neo.fs.v2.object.ObjectService.Put:output_type -> neo.fs.v2.object.PutResponse - 5, // 104: neo.fs.v2.object.ObjectService.Delete:output_type -> neo.fs.v2.object.DeleteResponse - 8, // 105: neo.fs.v2.object.ObjectService.Head:output_type -> neo.fs.v2.object.HeadResponse - 10, // 106: neo.fs.v2.object.ObjectService.Search:output_type -> neo.fs.v2.object.SearchResponse - 13, // 107: neo.fs.v2.object.ObjectService.GetRange:output_type -> neo.fs.v2.object.GetRangeResponse - 15, // 108: neo.fs.v2.object.ObjectService.GetRangeHash:output_type -> neo.fs.v2.object.GetRangeHashResponse - 17, // 109: neo.fs.v2.object.ObjectService.PutSingle:output_type -> neo.fs.v2.object.PutSingleResponse - 19, // 110: neo.fs.v2.object.ObjectService.Patch:output_type -> neo.fs.v2.object.PatchResponse - 102, // [102:111] is the sub-list for method output_type - 93, // [93:102] is the sub-list for method input_type - 93, // [93:93] is the sub-list for extension type_name - 93, // [93:93] is the sub-list for extension extendee - 0, // [0:93] is the sub-list for field type_name + 58, // 90: neo.fs.v2.object.PatchRequest.Body.new_split_header:type_name -> neo.fs.v2.object.Header.Split + 40, // 91: neo.fs.v2.object.PatchRequest.Body.patch:type_name -> neo.fs.v2.object.PatchRequest.Body.Patch + 11, // 92: neo.fs.v2.object.PatchRequest.Body.Patch.source_range:type_name -> neo.fs.v2.object.Range + 51, // 93: neo.fs.v2.object.PatchResponse.Body.object_id:type_name -> neo.fs.v2.refs.ObjectID + 0, // 94: neo.fs.v2.object.ObjectService.Get:input_type -> neo.fs.v2.object.GetRequest + 2, // 95: neo.fs.v2.object.ObjectService.Put:input_type -> neo.fs.v2.object.PutRequest + 4, // 96: neo.fs.v2.object.ObjectService.Delete:input_type -> neo.fs.v2.object.DeleteRequest + 6, // 97: neo.fs.v2.object.ObjectService.Head:input_type -> neo.fs.v2.object.HeadRequest + 9, // 98: neo.fs.v2.object.ObjectService.Search:input_type -> neo.fs.v2.object.SearchRequest + 12, // 99: neo.fs.v2.object.ObjectService.GetRange:input_type -> neo.fs.v2.object.GetRangeRequest + 14, // 100: neo.fs.v2.object.ObjectService.GetRangeHash:input_type -> neo.fs.v2.object.GetRangeHashRequest + 16, // 101: neo.fs.v2.object.ObjectService.PutSingle:input_type -> neo.fs.v2.object.PutSingleRequest + 18, // 102: neo.fs.v2.object.ObjectService.Patch:input_type -> neo.fs.v2.object.PatchRequest + 1, // 103: neo.fs.v2.object.ObjectService.Get:output_type -> neo.fs.v2.object.GetResponse + 3, // 104: neo.fs.v2.object.ObjectService.Put:output_type -> neo.fs.v2.object.PutResponse + 5, // 105: neo.fs.v2.object.ObjectService.Delete:output_type -> neo.fs.v2.object.DeleteResponse + 8, // 106: neo.fs.v2.object.ObjectService.Head:output_type -> neo.fs.v2.object.HeadResponse + 10, // 107: neo.fs.v2.object.ObjectService.Search:output_type -> neo.fs.v2.object.SearchResponse + 13, // 108: neo.fs.v2.object.ObjectService.GetRange:output_type -> neo.fs.v2.object.GetRangeResponse + 15, // 109: neo.fs.v2.object.ObjectService.GetRangeHash:output_type -> neo.fs.v2.object.GetRangeHashResponse + 17, // 110: neo.fs.v2.object.ObjectService.PutSingle:output_type -> neo.fs.v2.object.PutSingleResponse + 19, // 111: neo.fs.v2.object.ObjectService.Patch:output_type -> neo.fs.v2.object.PatchResponse + 103, // [103:112] is the sub-list for method output_type + 94, // [94:103] is the sub-list for method input_type + 94, // [94:94] is the sub-list for extension type_name + 94, // [94:94] is the sub-list for extension extendee + 0, // [0:94] is the sub-list for field type_name } func init() { file_api_object_grpc_service_proto_init() } diff --git a/api/object/grpc/service_protoopaque.pb.go b/api/object/grpc/service_protoopaque.pb.go index 3a389a1..8c74ef0 100644 --- a/api/object/grpc/service_protoopaque.pb.go +++ b/api/object/grpc/service_protoopaque.pb.go @@ -5050,6 +5050,7 @@ type PatchRequest_Body struct { xxx_hidden_Address *grpc1.Address `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` xxx_hidden_NewAttributes *[]*Header_Attribute `protobuf:"bytes,2,rep,name=new_attributes,json=newAttributes" json:"new_attributes,omitempty"` xxx_hidden_ReplaceAttributes bool `protobuf:"varint,3,opt,name=replace_attributes,json=replaceAttributes" json:"replace_attributes,omitempty"` + xxx_hidden_NewSplitHeader *Header_Split `protobuf:"bytes,5,opt,name=new_split_header,json=newSplitHeader" json:"new_split_header,omitempty"` xxx_hidden_Patch *PatchRequest_Body_Patch `protobuf:"bytes,4,opt,name=patch" json:"patch,omitempty"` XXX_raceDetectHookData protoimpl.RaceDetectHookData XXX_presence [1]uint32 @@ -5105,6 +5106,13 @@ func (x *PatchRequest_Body) GetReplaceAttributes() bool { return false } +func (x *PatchRequest_Body) GetNewSplitHeader() *Header_Split { + if x != nil { + return x.xxx_hidden_NewSplitHeader + } + return nil +} + func (x *PatchRequest_Body) GetPatch() *PatchRequest_Body_Patch { if x != nil { return x.xxx_hidden_Patch @@ -5122,7 +5130,11 @@ func (x *PatchRequest_Body) SetNewAttributes(v []*Header_Attribute) { func (x *PatchRequest_Body) SetReplaceAttributes(v bool) { x.xxx_hidden_ReplaceAttributes = v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 4) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 5) +} + +func (x *PatchRequest_Body) SetNewSplitHeader(v *Header_Split) { + x.xxx_hidden_NewSplitHeader = v } func (x *PatchRequest_Body) SetPatch(v *PatchRequest_Body_Patch) { @@ -5143,6 +5155,13 @@ func (x *PatchRequest_Body) HasReplaceAttributes() bool { return protoimpl.X.Present(&(x.XXX_presence[0]), 2) } +func (x *PatchRequest_Body) HasNewSplitHeader() bool { + if x == nil { + return false + } + return x.xxx_hidden_NewSplitHeader != nil +} + func (x *PatchRequest_Body) HasPatch() bool { if x == nil { return false @@ -5159,6 +5178,10 @@ func (x *PatchRequest_Body) ClearReplaceAttributes() { x.xxx_hidden_ReplaceAttributes = false } +func (x *PatchRequest_Body) ClearNewSplitHeader() { + x.xxx_hidden_NewSplitHeader = nil +} + func (x *PatchRequest_Body) ClearPatch() { x.xxx_hidden_Patch = nil } @@ -5179,6 +5202,9 @@ type PatchRequest_Body_builder struct { // merged. If the incoming `new_attributes` list contains already existing // key, then it just replaces it while merging the lists. ReplaceAttributes *bool + // New split header for the object. This defines how the object will relate + // to other objects in a split operation. + NewSplitHeader *Header_Split // The patch that is applied for the object. Patch *PatchRequest_Body_Patch } @@ -5190,9 +5216,10 @@ func (b0 PatchRequest_Body_builder) Build() *PatchRequest_Body { x.xxx_hidden_Address = b.Address x.xxx_hidden_NewAttributes = &b.NewAttributes if b.ReplaceAttributes != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 4) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 5) x.xxx_hidden_ReplaceAttributes = *b.ReplaceAttributes } + x.xxx_hidden_NewSplitHeader = b.NewSplitHeader x.xxx_hidden_Patch = b.Patch return m0 } @@ -5779,7 +5806,7 @@ var file_api_object_grpc_service_proto_rawDesc = []byte{ 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x06, 0x0a, - 0x04, 0x42, 0x6f, 0x64, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x04, 0x42, 0x6f, 0x64, 0x79, 0x22, 0xfd, 0x04, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, @@ -5793,7 +5820,7 @@ var file_api_object_grpc_service_proto_rawDesc = []byte{ 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0xcf, 0x02, 0x0a, 0x04, 0x42, 0x6f, + 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x99, 0x03, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, @@ -5805,87 +5832,92 @@ var file_api_object_grpc_service_proto_rawDesc = []byte{ 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, - 0x3f, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, - 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, - 0x6f, 0x64, 0x79, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x1a, 0x59, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x3a, 0x0a, 0x0c, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0xa4, 0x02, 0x0a, 0x0d, - 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, - 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, - 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, - 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x46, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x61, 0x5f, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, - 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, - 0x52, 0x0a, 0x0d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, + 0x48, 0x0a, 0x10, 0x6e, 0x65, 0x77, 0x5f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x5f, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x52, 0x0e, 0x6e, 0x65, 0x77, 0x53, 0x70, + 0x6c, 0x69, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x05, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x2e, 0x50, 0x61, + 0x74, 0x63, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x59, 0x0a, 0x05, 0x50, 0x61, + 0x74, 0x63, 0x68, 0x12, 0x3a, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x61, + 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0xa4, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, + 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x04, 0x62, 0x6f, 0x64, + 0x79, 0x12, 0x46, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x1a, 0x3d, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x35, 0x0a, 0x09, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x2e, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x44, 0x52, 0x08, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x49, 0x64, 0x32, 0xd4, 0x05, 0x0a, 0x0d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, - 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, - 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x03, 0x50, 0x75, - 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0a, 0x6d, + 0x65, 0x74, 0x61, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x52, 0x0a, 0x0d, 0x76, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, + 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x3d, 0x0a, + 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x35, 0x0a, 0x09, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x49, 0x44, 0x52, 0x08, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x32, 0xd4, 0x05, 0x0a, + 0x0d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, + 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, + 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x6e, 0x65, + 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, + 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x4b, 0x0a, 0x06, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, + 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, + 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, - 0x12, 0x4b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, - 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x65, - 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, - 0x04, 0x48, 0x65, 0x61, 0x64, 0x12, 0x1d, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, - 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, - 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, + 0x63, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x21, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x25, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, - 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x09, 0x50, 0x75, 0x74, 0x53, 0x69, - 0x6e, 0x67, 0x6c, 0x65, 0x12, 0x22, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, - 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, - 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, - 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, - 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, - 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, - 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0x62, 0x5a, 0x43, 0x67, 0x69, 0x74, - 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, - 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, - 0x66, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2d, 0x67, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0xaa, 0x02, 0x1a, 0x4e, 0x65, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x08, 0x65, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07, + 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, + 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, + 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, + 0x08, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x21, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6e, + 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x12, 0x5d, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x25, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, + 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, + 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x54, 0x0a, 0x09, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x12, 0x22, + 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x50, 0x75, 0x74, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, + 0x12, 0x1e, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x6e, 0x65, 0x6f, 0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x2e, 0x50, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x28, 0x01, 0x42, 0x62, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x2e, 0x66, 0x72, 0x6f, 0x73, 0x74, + 0x66, 0x73, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x54, 0x72, 0x75, 0x65, 0x43, 0x6c, 0x6f, 0x75, + 0x64, 0x4c, 0x61, 0x62, 0x2f, 0x66, 0x72, 0x6f, 0x73, 0x74, 0x66, 0x73, 0x2d, 0x73, 0x64, 0x6b, + 0x2d, 0x67, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x67, + 0x72, 0x70, 0x63, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0xaa, 0x02, 0x1a, 0x4e, 0x65, 0x6f, + 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, + 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x70, 0xe8, 0x07, } var file_api_object_grpc_service_proto_msgTypes = make([]protoimpl.MessageInfo, 42) @@ -5948,6 +5980,7 @@ var file_api_object_grpc_service_proto_goTypes = []any{ (grpc1.ChecksumType)(0), // 55: neo.fs.v2.refs.ChecksumType (*Object)(nil), // 56: neo.fs.v2.object.Object (*Header_Attribute)(nil), // 57: neo.fs.v2.object.Header.Attribute + (*Header_Split)(nil), // 58: neo.fs.v2.object.Header.Split } var file_api_object_grpc_service_proto_depIdxs = []int32{ 20, // 0: neo.fs.v2.object.GetRequest.body:type_name -> neo.fs.v2.object.GetRequest.Body @@ -6040,32 +6073,33 @@ var file_api_object_grpc_service_proto_depIdxs = []int32{ 56, // 87: neo.fs.v2.object.PutSingleRequest.Body.object:type_name -> neo.fs.v2.object.Object 48, // 88: neo.fs.v2.object.PatchRequest.Body.address:type_name -> neo.fs.v2.refs.Address 57, // 89: neo.fs.v2.object.PatchRequest.Body.new_attributes:type_name -> neo.fs.v2.object.Header.Attribute - 40, // 90: neo.fs.v2.object.PatchRequest.Body.patch:type_name -> neo.fs.v2.object.PatchRequest.Body.Patch - 11, // 91: neo.fs.v2.object.PatchRequest.Body.Patch.source_range:type_name -> neo.fs.v2.object.Range - 51, // 92: neo.fs.v2.object.PatchResponse.Body.object_id:type_name -> neo.fs.v2.refs.ObjectID - 0, // 93: neo.fs.v2.object.ObjectService.Get:input_type -> neo.fs.v2.object.GetRequest - 2, // 94: neo.fs.v2.object.ObjectService.Put:input_type -> neo.fs.v2.object.PutRequest - 4, // 95: neo.fs.v2.object.ObjectService.Delete:input_type -> neo.fs.v2.object.DeleteRequest - 6, // 96: neo.fs.v2.object.ObjectService.Head:input_type -> neo.fs.v2.object.HeadRequest - 9, // 97: neo.fs.v2.object.ObjectService.Search:input_type -> neo.fs.v2.object.SearchRequest - 12, // 98: neo.fs.v2.object.ObjectService.GetRange:input_type -> neo.fs.v2.object.GetRangeRequest - 14, // 99: neo.fs.v2.object.ObjectService.GetRangeHash:input_type -> neo.fs.v2.object.GetRangeHashRequest - 16, // 100: neo.fs.v2.object.ObjectService.PutSingle:input_type -> neo.fs.v2.object.PutSingleRequest - 18, // 101: neo.fs.v2.object.ObjectService.Patch:input_type -> neo.fs.v2.object.PatchRequest - 1, // 102: neo.fs.v2.object.ObjectService.Get:output_type -> neo.fs.v2.object.GetResponse - 3, // 103: neo.fs.v2.object.ObjectService.Put:output_type -> neo.fs.v2.object.PutResponse - 5, // 104: neo.fs.v2.object.ObjectService.Delete:output_type -> neo.fs.v2.object.DeleteResponse - 8, // 105: neo.fs.v2.object.ObjectService.Head:output_type -> neo.fs.v2.object.HeadResponse - 10, // 106: neo.fs.v2.object.ObjectService.Search:output_type -> neo.fs.v2.object.SearchResponse - 13, // 107: neo.fs.v2.object.ObjectService.GetRange:output_type -> neo.fs.v2.object.GetRangeResponse - 15, // 108: neo.fs.v2.object.ObjectService.GetRangeHash:output_type -> neo.fs.v2.object.GetRangeHashResponse - 17, // 109: neo.fs.v2.object.ObjectService.PutSingle:output_type -> neo.fs.v2.object.PutSingleResponse - 19, // 110: neo.fs.v2.object.ObjectService.Patch:output_type -> neo.fs.v2.object.PatchResponse - 102, // [102:111] is the sub-list for method output_type - 93, // [93:102] is the sub-list for method input_type - 93, // [93:93] is the sub-list for extension type_name - 93, // [93:93] is the sub-list for extension extendee - 0, // [0:93] is the sub-list for field type_name + 58, // 90: neo.fs.v2.object.PatchRequest.Body.new_split_header:type_name -> neo.fs.v2.object.Header.Split + 40, // 91: neo.fs.v2.object.PatchRequest.Body.patch:type_name -> neo.fs.v2.object.PatchRequest.Body.Patch + 11, // 92: neo.fs.v2.object.PatchRequest.Body.Patch.source_range:type_name -> neo.fs.v2.object.Range + 51, // 93: neo.fs.v2.object.PatchResponse.Body.object_id:type_name -> neo.fs.v2.refs.ObjectID + 0, // 94: neo.fs.v2.object.ObjectService.Get:input_type -> neo.fs.v2.object.GetRequest + 2, // 95: neo.fs.v2.object.ObjectService.Put:input_type -> neo.fs.v2.object.PutRequest + 4, // 96: neo.fs.v2.object.ObjectService.Delete:input_type -> neo.fs.v2.object.DeleteRequest + 6, // 97: neo.fs.v2.object.ObjectService.Head:input_type -> neo.fs.v2.object.HeadRequest + 9, // 98: neo.fs.v2.object.ObjectService.Search:input_type -> neo.fs.v2.object.SearchRequest + 12, // 99: neo.fs.v2.object.ObjectService.GetRange:input_type -> neo.fs.v2.object.GetRangeRequest + 14, // 100: neo.fs.v2.object.ObjectService.GetRangeHash:input_type -> neo.fs.v2.object.GetRangeHashRequest + 16, // 101: neo.fs.v2.object.ObjectService.PutSingle:input_type -> neo.fs.v2.object.PutSingleRequest + 18, // 102: neo.fs.v2.object.ObjectService.Patch:input_type -> neo.fs.v2.object.PatchRequest + 1, // 103: neo.fs.v2.object.ObjectService.Get:output_type -> neo.fs.v2.object.GetResponse + 3, // 104: neo.fs.v2.object.ObjectService.Put:output_type -> neo.fs.v2.object.PutResponse + 5, // 105: neo.fs.v2.object.ObjectService.Delete:output_type -> neo.fs.v2.object.DeleteResponse + 8, // 106: neo.fs.v2.object.ObjectService.Head:output_type -> neo.fs.v2.object.HeadResponse + 10, // 107: neo.fs.v2.object.ObjectService.Search:output_type -> neo.fs.v2.object.SearchResponse + 13, // 108: neo.fs.v2.object.ObjectService.GetRange:output_type -> neo.fs.v2.object.GetRangeResponse + 15, // 109: neo.fs.v2.object.ObjectService.GetRangeHash:output_type -> neo.fs.v2.object.GetRangeHashResponse + 17, // 110: neo.fs.v2.object.ObjectService.PutSingle:output_type -> neo.fs.v2.object.PutSingleResponse + 19, // 111: neo.fs.v2.object.ObjectService.Patch:output_type -> neo.fs.v2.object.PatchResponse + 103, // [103:112] is the sub-list for method output_type + 94, // [94:103] is the sub-list for method input_type + 94, // [94:94] is the sub-list for extension type_name + 94, // [94:94] is the sub-list for extension extendee + 0, // [0:94] is the sub-list for field type_name } func init() { file_api_object_grpc_service_proto_init() } diff --git a/api/object/marshal.go b/api/object/marshal.go index 82e265b..2ed888c 100644 --- a/api/object/marshal.go +++ b/api/object/marshal.go @@ -136,10 +136,11 @@ const ( patchRequestBodyPatchRangeField = 1 patchRequestBodyPatchChunkField = 2 - patchRequestBodyAddrField = 1 - patchRequestBodyNewAttrsField = 2 - patchRequestBodyReplaceAttrField = 3 - patchRequestBodyPatchField = 4 + patchRequestBodyAddrField = 1 + patchRequestBodyNewAttrsField = 2 + patchRequestBodyReplaceAttrField = 3 + patchRequestBodyPatchField = 4 + patchRequestBodyNewSplitHeaderField = 5 patchResponseBodyObjectIDField = 1 ) @@ -1372,7 +1373,8 @@ func (r *PatchRequestBody) StableMarshal(buf []byte) []byte { offset += proto.NestedStructureMarshal(patchRequestBodyNewAttrsField, buf[offset:], &r.newAttributes[i]) } offset += proto.BoolMarshal(patchRequestBodyReplaceAttrField, buf[offset:], r.replaceAttributes) - proto.NestedStructureMarshal(patchRequestBodyPatchField, buf[offset:], r.patch) + offset += proto.NestedStructureMarshal(patchRequestBodyPatchField, buf[offset:], r.patch) + proto.NestedStructureMarshal(patchRequestBodyNewSplitHeaderField, buf[offset:], r.newSplitHeader) return buf } @@ -1389,6 +1391,7 @@ func (r *PatchRequestBody) StableSize() int { } size += proto.BoolSize(patchRequestBodyReplaceAttrField, r.replaceAttributes) size += proto.NestedStructureSize(patchRequestBodyPatchField, r.patch) + size += proto.NestedStructureSize(patchRequestBodyNewSplitHeaderField, r.newSplitHeader) return size } diff --git a/api/object/types.go b/api/object/types.go index 537fb02..57a93ce 100644 --- a/api/object/types.go +++ b/api/object/types.go @@ -360,6 +360,8 @@ type PatchRequestBody struct { newAttributes []Attribute + newSplitHeader *SplitHeader + replaceAttributes bool patch *PatchRequestBodyPatch @@ -1591,6 +1593,14 @@ func (r *PatchRequestBody) SetReplaceAttributes(replace bool) { r.replaceAttributes = replace } +func (r *PatchRequestBody) SetNewSplitHeader(newSplitHeader *SplitHeader) { + r.newSplitHeader = newSplitHeader +} + +func (r *PatchRequestBody) GetNewSplitHeader() *SplitHeader { + return r.newSplitHeader +} + func (r *PatchRequestBody) GetPatch() *PatchRequestBodyPatch { if r != nil { return r.patch From 76265fe9be7ff9e04fea173511205adf2426de4d Mon Sep 17 00:00:00 2001 From: Airat Arifullin Date: Wed, 26 Mar 2025 13:10:54 +0300 Subject: [PATCH 05/24] [#349] object: Introduce `SplitHeader` type * Also introduce `SplitHeader` getter and `SetSplitHeader` setter for `Object` type. Signed-off-by: Airat Arifullin --- object/object.go | 26 +++++++++ object/split_header.go | 124 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 object/split_header.go diff --git a/object/object.go b/object/object.go index b98685d..ed551b5 100644 --- a/object/object.go +++ b/object/object.go @@ -345,6 +345,32 @@ func (o *Object) SetAttributes(v ...Attribute) { }) } +// SplitHeader returns split header of the object. If it's set, then split header +// defines how the object relates to other objects in a split operation. +func (o *Object) SplitHeader() (splitHeader *SplitHeader) { + if v2 := (*object.Object)(o). + GetHeader(). + GetSplit(); v2 != nil { + splitHeader = NewSplitHeaderFromV2(v2) + } + + return +} + +// SetSplitHeader sets split header. +func (o *Object) SetSplitHeader(v *SplitHeader) { + o.setSplitFields(func(sh *object.SplitHeader) { + v2 := v.ToV2() + + sh.SetParent(v2.GetParent()) + sh.SetPrevious(v2.GetPrevious()) + sh.SetParentHeader(v2.GetParentHeader()) + sh.SetParentSignature(v2.GetParentSignature()) + sh.SetChildren(v2.GetChildren()) + sh.SetSplitID(v2.GetSplitID()) + }) +} + // PreviousID returns identifier of the previous sibling object. func (o *Object) PreviousID() (v oid.ID, isSet bool) { v2 := (*object.Object)(o) diff --git a/object/split_header.go b/object/split_header.go new file mode 100644 index 0000000..5cbc214 --- /dev/null +++ b/object/split_header.go @@ -0,0 +1,124 @@ +package object + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs" + frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" +) + +// SplitHeader is an object's header component that defines the relationship +// between this object and other objects if the object is part of a split operation. +type SplitHeader object.SplitHeader + +// NewSplitHeaderFromV2 wraps v2 SplitHeader message to SplitHeader. +func NewSplitHeaderFromV2(v2 *object.SplitHeader) *SplitHeader { + return (*SplitHeader)(v2) +} + +// NewSplitHeader creates blank SplitHeader instance. +func NewSplitHeader() *SplitHeader { + return NewSplitHeaderFromV2(new(object.SplitHeader)) +} + +func (sh *SplitHeader) ToV2() *object.SplitHeader { + return (*object.SplitHeader)(sh) +} + +func (sh *SplitHeader) ParentID() (v oid.ID, isSet bool) { + v2 := (*object.SplitHeader)(sh) + if id := v2.GetParent(); id != nil { + _ = v.ReadFromV2(*id) + isSet = true + } + + return +} + +func (sh *SplitHeader) SetParentID(v oid.ID) { + v2 := new(refs.ObjectID) + v.WriteToV2(v2) + (*object.SplitHeader)(sh).SetParent(v2) +} + +func (sh *SplitHeader) PreviousID() (v oid.ID, isSet bool) { + v2 := (*object.SplitHeader)(sh) + if id := v2.GetPrevious(); id != nil { + _ = v.ReadFromV2(*id) + isSet = true + } + + return +} + +func (sh *SplitHeader) SetPreviousID(v oid.ID) { + v2 := new(refs.ObjectID) + v.WriteToV2(v2) + (*object.SplitHeader)(sh).SetPrevious(v2) +} + +func (sh *SplitHeader) ParentSignature() *frostfscrypto.Signature { + v2 := (*object.SplitHeader)(sh) + if parSigV2 := v2.GetParentSignature(); parSigV2 != nil { + parSig := new(frostfscrypto.Signature) + _ = parSig.ReadFromV2(*parSigV2) + } + + return nil +} + +func (sh *SplitHeader) SetParentSignature(v *frostfscrypto.Signature) { + var parSigV2 *refs.Signature + + if v != nil { + parSigV2 = new(refs.Signature) + v.WriteToV2(parSigV2) + } + + (*object.SplitHeader)(sh).SetParentSignature(parSigV2) +} + +func (sh *SplitHeader) ParentHeader() (parentHeader *Object) { + v2 := (*object.SplitHeader)(sh) + + if parHdr := v2.GetParentHeader(); parHdr != nil { + parentHeader = New() + parentHeader.setHeaderField(func(h *object.Header) { + *h = *v2.GetParentHeader() + }) + } + + return +} + +func (sh *SplitHeader) SetParentHeader(parentHeader *Object) { + (*object.SplitHeader)(sh).SetParentHeader(parentHeader.ToV2().GetHeader()) +} + +func (sh *SplitHeader) Children() (res []oid.ID) { + v2 := (*object.SplitHeader)(sh) + if children := v2.GetChildren(); len(children) > 0 { + res = make([]oid.ID, len(children)) + for i := range children { + _ = res[i].ReadFromV2(children[i]) + } + } + + return +} + +func (sh *SplitHeader) SetChildren(children []oid.ID) { + v2Children := make([]refs.ObjectID, len(children)) + for i := range children { + children[i].WriteToV2(&v2Children[i]) + } + (*object.SplitHeader)(sh).SetChildren(v2Children) +} + +func (sh *SplitHeader) SplitID() *SplitID { + return NewSplitIDFromV2((*object.SplitHeader)(sh).GetSplitID()) +} + +func (sh *SplitHeader) SetSplitID(v *SplitID) { + (*object.SplitHeader)(sh).SetSplitID(v.ToV2()) +} From 4d36a49d3945937f55d1bbc94915f1e20cd27ed3 Mon Sep 17 00:00:00 2001 From: Airat Arifullin Date: Wed, 26 Mar 2025 13:11:41 +0300 Subject: [PATCH 06/24] [#349] object: Make patcher apply patching for split header * Change `PatchApplier` interface: `ApplyAttributesPatch` -> `ApplyHeaderPatch`. Make `ApplyHeaderPatch` receive `ApplyHeaderPatchPrm` as parameter; * Fix `patcher`: apply patch for split header; * Fix `patcher` unit-tests. Add test-case for split header; * Extend `Patch` struct with `NewSplitHeader`; * Change `ObjectPatcher` interface for client: `PatchAttributes` -> `PatchHeader`. Fix `objectPatcher`. * Fix object transformer: since object header sets `SplitHeader` if it's passed. Signed-off-by: Airat Arifullin --- client/object_patch.go | 27 ++++- client/object_patch_test.go | 2 +- object/patch.go | 8 ++ object/patcher/patcher.go | 68 +++++++++++-- object/patcher/patcher_test.go | 163 ++++++++++++++++++++++++++++-- object/transformer/transformer.go | 11 +- 6 files changed, 259 insertions(+), 20 deletions(-) diff --git a/client/object_patch.go b/client/object_patch.go index 6930644..87033c5 100644 --- a/client/object_patch.go +++ b/client/object_patch.go @@ -26,11 +26,19 @@ import ( // usage is unsafe. type ObjectPatcher interface { // PatchAttributes patches attributes. Attributes can be patched no more than once, - // otherwise, the server returns an error. + // otherwise, the server returns an error. `PatchAttributes` and `PatchHeader` are mutually + // exclusive - only one method can be used. // // Result means success. Failure reason can be received via Close. PatchAttributes(ctx context.Context, newAttrs []object.Attribute, replace bool) bool + // PatchHeader patches object's header. Header can be patched no more than once, + // otherwise, the server returns an error. `PatchAttributes` and `PatchHeader` are mutually + // exclusive - only one method can be used. + // + // Result means success. Failure reason can be received via Close. + PatchHeader(ctx context.Context, prm PatchHeaderPrm) bool + // PatchPayload patches the object's payload. // // PatchPayload receives `payloadReader` and thus the payload of the patch is read and sent by chunks of @@ -60,6 +68,14 @@ type ObjectPatcher interface { Close(_ context.Context) (*ResObjectPatch, error) } +type PatchHeaderPrm struct { + NewSplitHeader *object.SplitHeader + + NewAttributes []object.Attribute + + ReplaceAttributes bool +} + // ResObjectPatch groups resulting values of ObjectPatch operation. type ResObjectPatch struct { statusRes @@ -163,6 +179,15 @@ func (x *objectPatcher) PatchAttributes(_ context.Context, newAttrs []object.Att }) } +func (x *objectPatcher) PatchHeader(_ context.Context, prm PatchHeaderPrm) bool { + return x.patch(&object.Patch{ + Address: x.addr, + NewAttributes: prm.NewAttributes, + ReplaceAttributes: prm.ReplaceAttributes, + NewSplitHeader: prm.NewSplitHeader, + }) +} + func (x *objectPatcher) PatchPayload(_ context.Context, rng *object.Range, payloadReader io.Reader) bool { offset := rng.GetOffset() diff --git a/client/object_patch_test.go b/client/object_patch_test.go index 63996b6..3e801f3 100644 --- a/client/object_patch_test.go +++ b/client/object_patch_test.go @@ -177,7 +177,7 @@ func TestObjectPatcher(t *testing.T) { maxChunkLen: test.maxChunkLen, } - success := patcher.PatchAttributes(context.Background(), nil, false) + success := patcher.PatchHeader(context.Background(), PatchHeaderPrm{}) require.True(t, success) success = patcher.PatchPayload(context.Background(), test.rng, bytes.NewReader([]byte(test.patchPayload))) diff --git a/object/patch.go b/object/patch.go index 2a06674..9c6ddc4 100644 --- a/object/patch.go +++ b/object/patch.go @@ -18,6 +18,10 @@ type Patch struct { // filled with NewAttributes. Otherwise, the attributes are just merged. ReplaceAttributes bool + // A new split header which is set to object's header. If `nil`, then split header patching + // is ignored. + NewSplitHeader *SplitHeader + // Payload patch. If this field is not set, then it assumed such Patch patches only // header (see NewAttributes, ReplaceAttributes). PayloadPatch *PayloadPatch @@ -41,6 +45,8 @@ func (p *Patch) ToV2() *v2object.PatchRequestBody { v2.SetNewAttributes(attrs) v2.SetReplaceAttributes(p.ReplaceAttributes) + v2.SetNewSplitHeader(p.NewSplitHeader.ToV2()) + v2.SetPatch(p.PayloadPatch.ToV2()) return v2 @@ -63,6 +69,8 @@ func (p *Patch) FromV2(patch *v2object.PatchRequestBody) { p.ReplaceAttributes = patch.GetReplaceAttributes() + p.NewSplitHeader = NewSplitHeaderFromV2(patch.GetNewSplitHeader()) + if v2patch := patch.GetPatch(); v2patch != nil { p.PayloadPatch = new(PayloadPatch) p.PayloadPatch.FromV2(v2patch) diff --git a/object/patcher/patcher.go b/object/patcher/patcher.go index aad1f2a..66df9d3 100644 --- a/object/patcher/patcher.go +++ b/object/patcher/patcher.go @@ -11,10 +11,12 @@ import ( ) var ( - ErrOffsetExceedsSize = errors.New("patch offset exceeds object size") - ErrInvalidPatchOffsetOrder = errors.New("invalid patch offset order") - ErrPayloadPatchIsNil = errors.New("nil payload patch") - ErrAttrPatchAlreadyApplied = errors.New("attribute patch already applied") + ErrOffsetExceedsSize = errors.New("patch offset exceeds object size") + ErrInvalidPatchOffsetOrder = errors.New("invalid patch offset order") + ErrPayloadPatchIsNil = errors.New("nil payload patch") + ErrAttrPatchAlreadyApplied = errors.New("attribute patch already applied") + ErrHeaderPatchAlreadyApplied = errors.New("header patch already applied") + ErrSplitHeaderPatchAppliedWithPayloadPatch = errors.New("split header patch applied with payload patch") ) // PatchRes is the result of patch application. @@ -27,13 +29,24 @@ type PatchApplier interface { // ApplyAttributesPatch applies the patch only for the object's attributes. // // ApplyAttributesPatch can't be invoked few times, otherwise it returns `ErrAttrPatchAlreadyApplied` error. + // `ApplyHeaderPatch` and `ApplyAttributesPatch` are mutually exclusive - only one method can be used. // // The call is idempotent for the original header if it's invoked with empty `newAttrs` and // `replaceAttrs = false`. ApplyAttributesPatch(ctx context.Context, newAttrs []objectSDK.Attribute, replaceAttrs bool) error + // ApplyHeaderPatch applies the patch only for the object's attributes. + // + // ApplyHeaderPatch can't be invoked few times, otherwise it returns `ErrHeaderPatchAlreadyApplied` error. + // `ApplyHeaderPatch` and `ApplyAttributesPatch` are mutually exclusive - only one method can be used. + // + // The call is idempotent for the original header if it's invoked with `ApplyHeaderPatchPrm` with not set fields. + ApplyHeaderPatch(ctx context.Context, prm ApplyHeaderPatchPrm) error + // ApplyPayloadPatch applies the patch for the object's payload. // + // ApplyPayloadPatch returns `ErrSplitHeaderPatchAppliedWithPayloadPatch` when attempting to apply it with a split header patch. + // // ApplyPayloadPatch returns `ErrPayloadPatchIsNil` error if patch is nil. ApplyPayloadPatch(ctx context.Context, payloadPatch *objectSDK.PayloadPatch) error @@ -41,6 +54,14 @@ type PatchApplier interface { Close(context.Context) (PatchRes, error) } +type ApplyHeaderPatchPrm struct { + NewSplitHeader *objectSDK.SplitHeader + + NewAttributes []objectSDK.Attribute + + ReplaceAttributes bool +} + // RangeProvider is the interface that provides a method to get original object payload // by a given range. type RangeProvider interface { @@ -61,7 +82,9 @@ type patcher struct { hdr *objectSDK.Object - attrPatchAlreadyApplied bool + hdrPatchAlreadyApplied bool + + splitHeaderPatchAlreadyApplied bool readerBuffSize int } @@ -107,10 +130,10 @@ func New(prm Params) PatchApplier { func (p *patcher) ApplyAttributesPatch(ctx context.Context, newAttrs []objectSDK.Attribute, replaceAttrs bool) error { defer func() { - p.attrPatchAlreadyApplied = true + p.hdrPatchAlreadyApplied = true }() - if p.attrPatchAlreadyApplied { + if p.hdrPatchAlreadyApplied { return ErrAttrPatchAlreadyApplied } @@ -127,7 +150,38 @@ func (p *patcher) ApplyAttributesPatch(ctx context.Context, newAttrs []objectSDK return nil } +func (p *patcher) ApplyHeaderPatch(ctx context.Context, prm ApplyHeaderPatchPrm) error { + defer func() { + p.hdrPatchAlreadyApplied = true + }() + + if p.hdrPatchAlreadyApplied { + return ErrHeaderPatchAlreadyApplied + } + + if prm.NewSplitHeader != nil { + p.hdr.SetSplitHeader(prm.NewSplitHeader) + + p.splitHeaderPatchAlreadyApplied = true + } + + if prm.ReplaceAttributes { + p.hdr.SetAttributes(prm.NewAttributes...) + } else if len(prm.NewAttributes) > 0 { + mergedAttrs := mergeAttributes(prm.NewAttributes, p.hdr.Attributes()) + p.hdr.SetAttributes(mergedAttrs...) + } + + if err := p.objectWriter.WriteHeader(ctx, p.hdr); err != nil { + return fmt.Errorf("writer header: %w", err) + } + return nil +} + func (p *patcher) ApplyPayloadPatch(ctx context.Context, payloadPatch *objectSDK.PayloadPatch) error { + if p.splitHeaderPatchAlreadyApplied { + return ErrSplitHeaderPatchAppliedWithPayloadPatch + } if payloadPatch == nil { return ErrPayloadPatchIsNil } diff --git a/object/patcher/patcher_test.go b/object/patcher/patcher_test.go index 3abb939..4819b59 100644 --- a/object/patcher/patcher_test.go +++ b/object/patcher/patcher_test.go @@ -106,7 +106,11 @@ func TestPatchRevert(t *testing.T) { patcher := New(prm) - err := patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes) + err := patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: modifPatch.NewSplitHeader, + NewAttributes: modifPatch.NewAttributes, + ReplaceAttributes: modifPatch.ReplaceAttributes, + }) require.NoError(t, err) err = patcher.ApplyPayloadPatch(context.Background(), modifPatch.PayloadPatch) @@ -145,7 +149,11 @@ func TestPatchRevert(t *testing.T) { patcher = New(prm) - err = patcher.ApplyAttributesPatch(context.Background(), revertPatch.NewAttributes, revertPatch.ReplaceAttributes) + err = patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: revertPatch.NewSplitHeader, + NewAttributes: revertPatch.NewAttributes, + ReplaceAttributes: revertPatch.ReplaceAttributes, + }) require.NoError(t, err) err = patcher.ApplyPayloadPatch(context.Background(), revertPatch.PayloadPatch) @@ -157,7 +165,7 @@ func TestPatchRevert(t *testing.T) { require.Equal(t, originalObjectPayload, patchedPatchedObj.Payload()) } -func TestPatchRepeatAttributePatch(t *testing.T) { +func TestPatchRepeatHeaderPatch(t *testing.T) { obj, _ := newTestObject() modifPatch := &objectSDK.Patch{} @@ -187,11 +195,142 @@ func TestPatchRepeatAttributePatch(t *testing.T) { patcher := New(prm) - err := patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes) + err := patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: modifPatch.NewSplitHeader, + NewAttributes: modifPatch.NewAttributes, + ReplaceAttributes: modifPatch.ReplaceAttributes, + }) require.NoError(t, err) - err = patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes) - require.ErrorIs(t, err, ErrAttrPatchAlreadyApplied) + err = patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: modifPatch.NewSplitHeader, + NewAttributes: modifPatch.NewAttributes, + ReplaceAttributes: modifPatch.ReplaceAttributes, + }) + require.ErrorIs(t, err, ErrHeaderPatchAlreadyApplied) +} + +func TestPatchSplitHeader(t *testing.T) { + obj, _ := newTestObject() + + const ( + splitIDStr = "a59c9f87-14bc-4a61-95d1-7eb10f036163" + parentStr = "9cRjAaPqUt5zaDAjBkSCqFfPdkE8dHJ7mtRupRjPWp6E" + previosStr = "6WaTd9HobT4Z52NnKWHAtjqtQu2Ww5xZwNdT4ptshkKE" + ) + + splitID := objectSDK.NewSplitID() + require.NoError(t, splitID.Parse(splitIDStr)) + + var par, prev oid.ID + require.NoError(t, par.DecodeString(parentStr)) + require.NoError(t, prev.DecodeString(previosStr)) + + splitHdr := objectSDK.NewSplitHeader() + splitHdr.SetSplitID(splitID) + splitHdr.SetParentID(par) + splitHdr.SetPreviousID(prev) + + originalObjectPayload := []byte("*******************") + + obj.SetPayload(originalObjectPayload) + obj.SetPayloadSize(uint64(len(originalObjectPayload))) + + rangeProvider := &mockRangeProvider{ + originalObjectPayload: originalObjectPayload, + } + + t.Run("no payload patch", func(t *testing.T) { + patchedObj, _ := newTestObject() + + wr := &mockPatchedObjectWriter{ + obj: patchedObj, + } + + modifPatch := &objectSDK.Patch{ + NewSplitHeader: splitHdr, + } + + prm := Params{ + Header: obj.CutPayload(), + + RangeProvider: rangeProvider, + + ObjectWriter: wr, + } + + patcher := New(prm) + + err := patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: modifPatch.NewSplitHeader, + NewAttributes: modifPatch.NewAttributes, + ReplaceAttributes: modifPatch.ReplaceAttributes, + }) + require.NoError(t, err) + + splitHdrFromPatchedObj := patchedObj.SplitHeader() + require.NotNil(t, splitHdrFromPatchedObj) + + patchObjParID, isSet := splitHdrFromPatchedObj.ParentID() + require.True(t, isSet) + require.True(t, patchObjParID.Equals(par)) + + patchObjPrevID, isSet := splitHdrFromPatchedObj.PreviousID() + require.True(t, isSet) + require.True(t, patchObjPrevID.Equals(prev)) + + require.Equal(t, splitHdrFromPatchedObj.SplitID().String(), splitID.String()) + }) + + t.Run("with payload patch", func(t *testing.T) { + patchedObj, _ := newTestObject() + + wr := &mockPatchedObjectWriter{ + obj: patchedObj, + } + + modifPatch := &objectSDK.Patch{ + NewSplitHeader: splitHdr, + PayloadPatch: &objectSDK.PayloadPatch{ + Range: rangeWithOffestWithLength(10, 0), + Chunk: []byte(""), + }, + } + + prm := Params{ + Header: obj.CutPayload(), + + RangeProvider: rangeProvider, + + ObjectWriter: wr, + } + + patcher := New(prm) + + err := patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: modifPatch.NewSplitHeader, + NewAttributes: modifPatch.NewAttributes, + ReplaceAttributes: modifPatch.ReplaceAttributes, + }) + require.NoError(t, err) + + splitHdrFromPatchedObj := patchedObj.SplitHeader() + require.NotNil(t, splitHdrFromPatchedObj) + + patchObjParID, isSet := splitHdrFromPatchedObj.ParentID() + require.True(t, isSet) + require.True(t, patchObjParID.Equals(par)) + + patchObjPrevID, isSet := splitHdrFromPatchedObj.PreviousID() + require.True(t, isSet) + require.True(t, patchObjPrevID.Equals(prev)) + + require.Equal(t, splitHdrFromPatchedObj.SplitID().String(), splitID.String()) + + err = patcher.ApplyPayloadPatch(context.Background(), modifPatch.PayloadPatch) + require.Error(t, err, ErrSplitHeaderPatchAppliedWithPayloadPatch) + }) + } func TestPatchEmptyPayloadPatch(t *testing.T) { @@ -224,7 +363,11 @@ func TestPatchEmptyPayloadPatch(t *testing.T) { patcher := New(prm) - err := patcher.ApplyAttributesPatch(context.Background(), modifPatch.NewAttributes, modifPatch.ReplaceAttributes) + err := patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: modifPatch.NewSplitHeader, + NewAttributes: modifPatch.NewAttributes, + ReplaceAttributes: modifPatch.ReplaceAttributes, + }) require.NoError(t, err) err = patcher.ApplyPayloadPatch(context.Background(), nil) @@ -599,7 +742,11 @@ func TestPatch(t *testing.T) { for i, patch := range test.patches { if i == 0 { - _ = patcher.ApplyAttributesPatch(context.Background(), patch.NewAttributes, patch.ReplaceAttributes) + _ = patcher.ApplyHeaderPatch(context.Background(), ApplyHeaderPatchPrm{ + NewSplitHeader: patch.NewSplitHeader, + NewAttributes: patch.NewAttributes, + ReplaceAttributes: patch.ReplaceAttributes, + }) } if patch.PayloadPatch == nil { diff --git a/object/transformer/transformer.go b/object/transformer/transformer.go index ce5259f..b4695ee 100644 --- a/object/transformer/transformer.go +++ b/object/transformer/transformer.go @@ -116,9 +116,14 @@ func fromObject(obj *object.Object) *object.Object { res.SetAttributes(obj.Attributes()...) res.SetType(obj.Type()) - // obj.SetSplitID creates splitHeader but we don't need to do it in case - // of small objects, so we should make nil check. - if obj.SplitID() != nil { + // There are two ways to specify split information: + // 1. Using explicit SplitHeader. Thus, we only propagate whole split information + // if it's already set in the source object (use-case: Patch method). + // 2. Using SplitID - will automatically generate a SplitHeader, but this is not requiered for + // small objects. + if obj.SplitHeader() != nil { + res.SetSplitHeader(obj.SplitHeader()) + } else if obj.SplitID() != nil { res.SetSplitID(obj.SplitID()) } From 5be341596199704033746e1568922b85efb9f41e Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 4 Apr 2025 16:51:51 +0300 Subject: [PATCH 07/24] [#345] go.mod: Bump min go version to 1.23 Signed-off-by: Evgenii Stratonikov --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1cca977..5ee242b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.frostfs.info/TrueCloudLab/frostfs-sdk-go -go 1.22 +go 1.23 require ( git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e From b27f172de9dbb3079c3106b41beac14c32071402 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 4 Apr 2025 17:14:27 +0300 Subject: [PATCH 08/24] [#345] netmap: Implement an iterator over node endpoints Signed-off-by: Evgenii Stratonikov --- api/netmap/types.go | 13 +++++++++++++ netmap/node_info.go | 17 ++++++++++++++++- netmap/node_info_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/api/netmap/types.go b/api/netmap/types.go index 38810ab..67c3e01 100644 --- a/api/netmap/types.go +++ b/api/netmap/types.go @@ -2,6 +2,7 @@ package netmap import ( "bytes" + "iter" "slices" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs" @@ -442,10 +443,22 @@ func (ni *NodeInfo) NumberOfAddresses() int { return 0 } +// Addresses returns an iterator over network addresses of the node. +func (ni NodeInfo) Addresses() iter.Seq[string] { + return func(yield func(string) bool) { + for i := range ni.addresses { + if !yield(ni.addresses[i]) { + break + } + } + } +} + // IterateAddresses iterates over network addresses of the node. // Breaks iteration on f's true return. // // Handler should not be nil. +// Deprecated: use [NodeInfo.Addresses] instead. func (ni *NodeInfo) IterateAddresses(f func(string) bool) { if ni != nil { for i := range ni.addresses { diff --git a/netmap/node_info.go b/netmap/node_info.go index a792337..a596b2b 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -3,6 +3,7 @@ package netmap import ( "errors" "fmt" + "iter" "slices" "strconv" "strings" @@ -200,12 +201,26 @@ func (x NodeInfo) NumberOfNetworkEndpoints() int { // FrostFS system requirements. // // See also SetNetworkEndpoints. +// Deprecated: use [NodeInfo.NetworkEndpoints] instead. func (x NodeInfo) IterateNetworkEndpoints(f func(string) bool) { - x.m.IterateAddresses(f) + for s := range x.NetworkEndpoints() { + if f(s) { + return + } + } +} + +// NetworkEndpoints returns an iterator over network endpoints announced by the +// node. +// +// See also SetNetworkEndpoints. +func (x NodeInfo) NetworkEndpoints() iter.Seq[string] { + return x.m.Addresses() } // IterateNetworkEndpoints is an extra-sugared function over IterateNetworkEndpoints // method which allows to unconditionally iterate over all node's network endpoints. +// Deprecated: use [NodeInfo.NetworkEndpoints] instead. func IterateNetworkEndpoints(node NodeInfo, f func(string)) { node.IterateNetworkEndpoints(func(addr string) bool { f(addr) diff --git a/netmap/node_info_test.go b/netmap/node_info_test.go index 965213e..8ef9516 100644 --- a/netmap/node_info_test.go +++ b/netmap/node_info_test.go @@ -7,6 +7,45 @@ import ( "github.com/stretchr/testify/require" ) +func TestNodeInfo_NetworkEndpoints(t *testing.T) { + t.Run("empty", func(t *testing.T) { + var n NodeInfo + for range n.NetworkEndpoints() { + t.Fatalf("handler is called, but it shouldn't") + } + }) + + var n NodeInfo + n.SetNetworkEndpoints("1", "2", "3") + + t.Run("break", func(t *testing.T) { + var res []string + for s := range n.NetworkEndpoints() { + if s == "2" { + break + } + res = append(res, s) + } + require.Equal(t, []string{"1"}, res) + }) + t.Run("continue", func(t *testing.T) { + var res []string + for s := range n.NetworkEndpoints() { + if s == "2" { + continue + } + res = append(res, s) + } + require.Equal(t, []string{"1", "3"}, res) + }) + + var res []string + for s := range n.NetworkEndpoints() { + res = append(res, s) + } + require.Equal(t, []string{"1", "2", "3"}, res) +} + func TestNodeInfo_SetAttribute(t *testing.T) { var n NodeInfo From 4b9b54a901742c8b15c36621f55fc1378509118e Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 4 Apr 2025 17:24:59 +0300 Subject: [PATCH 09/24] [#345] netmap: Implement an iterator over node attributes Signed-off-by: Evgenii Stratonikov --- netmap/node_info.go | 13 +++++++++++++ netmap/node_info_test.go | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/netmap/node_info.go b/netmap/node_info.go index a596b2b..acbfaee 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -408,8 +408,21 @@ func (x NodeInfo) NumberOfAttributes() int { return len(x.m.GetAttributes()) } +// Attributes returns an iterator over node attributes. +func (x NodeInfo) Attributes() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + a := x.m.GetAttributes() + for i := range a { + if !yield(a[i].GetKey(), a[i].GetValue()) { + break + } + } + } +} + // IterateAttributes iterates over all node attributes and passes the into f. // Handler MUST NOT be nil. +// Deprecated: use [NodeInfo.Attributes] instead. func (x NodeInfo) IterateAttributes(f func(key, value string)) { a := x.m.GetAttributes() for i := range a { diff --git a/netmap/node_info_test.go b/netmap/node_info_test.go index 8ef9516..990cc26 100644 --- a/netmap/node_info_test.go +++ b/netmap/node_info_test.go @@ -46,6 +46,47 @@ func TestNodeInfo_NetworkEndpoints(t *testing.T) { require.Equal(t, []string{"1", "2", "3"}, res) } +func TestNodeInfo_Attributes(t *testing.T) { + t.Run("empty", func(t *testing.T) { + var n NodeInfo + for range n.Attributes() { + t.Fatalf("handler is called, but it shouldn't") + } + }) + + var n NodeInfo + n.SetAttribute("key1", "value1") + n.SetAttribute("key2", "value2") + n.SetAttribute("key3", "value3") + + t.Run("break", func(t *testing.T) { + var res [][2]string + for k, v := range n.Attributes() { + if k == "key2" { + break + } + res = append(res, [2]string{k, v}) + } + require.Equal(t, [][2]string{{"key1", "value1"}}, res) + }) + t.Run("continue", func(t *testing.T) { + var res [][2]string + for k, v := range n.Attributes() { + if k == "key2" { + continue + } + res = append(res, [2]string{k, v}) + } + require.Equal(t, [][2]string{{"key1", "value1"}, {"key3", "value3"}}, res) + }) + + var res [][2]string + for k, v := range n.Attributes() { + res = append(res, [2]string{k, v}) + } + require.Equal(t, [][2]string{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}}, res) +} + func TestNodeInfo_SetAttribute(t *testing.T) { var n NodeInfo From 6458c11e833d31950f9f490d5d50e155937226a2 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Fri, 4 Apr 2025 17:26:10 +0300 Subject: [PATCH 10/24] [#345] api/netmap: Drop deprecated single-address methods Signed-off-by: Evgenii Stratonikov --- api/netmap/types.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/api/netmap/types.go b/api/netmap/types.go index 67c3e01..9d9fa09 100644 --- a/api/netmap/types.go +++ b/api/netmap/types.go @@ -410,25 +410,6 @@ func (ni *NodeInfo) SetPublicKey(v []byte) { ni.publicKey = v } -// GetAddress returns node's network address. -// -// Deprecated: use IterateAddresses. -func (ni *NodeInfo) GetAddress() (addr string) { - ni.IterateAddresses(func(s string) bool { - addr = s - return true - }) - - return -} - -// SetAddress sets node's network address. -// -// Deprecated: use SetAddresses. -func (ni *NodeInfo) SetAddress(v string) { - ni.SetAddresses(v) -} - // SetAddresses sets list of network addresses of the node. func (ni *NodeInfo) SetAddresses(v ...string) { ni.addresses = v From fb999fecac413a63cd2d7fb3058db3dc913d705c Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 11:35:42 +0300 Subject: [PATCH 11/24] [#357] netmap: Provide slice to append to in flattenNodes() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` goos: linux goarch: amd64 pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz │ old │ new │ │ sec/op │ sec/op vs base │ Netmap_ContainerNodes/REP_2-8 5.867µ ± 1% 5.821µ ± 0% -0.79% (p=0.000 n=10) Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8 5.786µ ± 2% 5.810µ ± 1% ~ (p=1.000 n=10) geomean 5.826µ 5.815µ -0.19% │ old │ new │ │ B/op │ B/op vs base │ Netmap_ContainerNodes/REP_2-8 7.609Ki ± 0% 7.328Ki ± 0% -3.70% (p=0.000 n=10) Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8 7.031Ki ± 0% 6.859Ki ± 0% -2.44% (p=0.000 n=10) geomean 7.315Ki 7.090Ki -3.07% │ old │ new │ │ allocs/op │ allocs/op vs base │ Netmap_ContainerNodes/REP_2-8 77.00 ± 0% 77.00 ± 0% ~ (p=1.000 n=10) ¹ Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8 77.00 ± 0% 77.00 ± 0% ~ (p=1.000 n=10) ¹ geomean 77.00 77.00 +0.00% ¹ all samples are equal ``` Signed-off-by: Evgenii Stratonikov --- netmap/netmap.go | 22 +++++++--------------- netmap/selector_test.go | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/netmap/netmap.go b/netmap/netmap.go index b119f07..a583455 100644 --- a/netmap/netmap.go +++ b/netmap/netmap.go @@ -139,20 +139,12 @@ func (n nodes) appendWeightsTo(wf weightFunc, w []float64) []float64 { return w } -func flattenNodes(ns []nodes) nodes { - var sz, i int - - for i = range ns { - sz += len(ns[i]) - } - - result := make(nodes, 0, sz) - +// flattenNodes flattens ns nested list and appends the result to the target slice. +func flattenNodes(target nodes, ns []nodes) nodes { for i := range ns { - result = append(result, ns[i]...) + target = append(target, ns[i]...) } - - return result + return target } // PlacementVectors sorts container nodes returned by ContainerNodes method @@ -287,7 +279,7 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, e return nil, err } - result[i] = append(result[i], flattenNodes(nodes)...) + result[i] = flattenNodes(result[i], nodes) if unique { c.addUsedNodes(result[i]...) @@ -304,14 +296,14 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, e if err != nil { return nil, err } - result[i] = append(result[i], flattenNodes(nodes)...) + result[i] = flattenNodes(result[i], nodes) c.addUsedNodes(result[i]...) } else { nodes, ok := c.selections[sName] if !ok { return nil, fmt.Errorf("selector not found: REPLICA '%s'", sName) } - result[i] = append(result[i], flattenNodes(nodes)...) + result[i] = flattenNodes(result[i], nodes) } } diff --git a/netmap/selector_test.go b/netmap/selector_test.go index ae996dd..a4ca774 100644 --- a/netmap/selector_test.go +++ b/netmap/selector_test.go @@ -191,7 +191,7 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) { nss[i] = v[i] } - ns := flattenNodes(nss) + ns := flattenNodes(nil, nss) require.Equal(t, 2, len(ns)) return ns[0].Hash(), ns[1].Hash() } From 1b12c9beaed47d48601828f9efbdbbadeaefaec0 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 10:15:12 +0300 Subject: [PATCH 12/24] [#356] netmap: Fix typo in NodeInfo.readFromV2() Signed-off-by: Evgenii Stratonikov --- netmap/node_info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netmap/node_info.go b/netmap/node_info.go index acbfaee..ce3bdb9 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -50,7 +50,7 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error if key == "" { return fmt.Errorf("empty key of the attribute #%d", i) } else if _, ok := mAttr[key]; ok { - return fmt.Errorf("duplicated attbiuted %s", key) + return fmt.Errorf("duplicate attributes %s", key) } switch { From 684050b570fb45fe246ab5966c26066101eb6f6b Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 10:29:01 +0300 Subject: [PATCH 13/24] [#356] netmap/test: Cover NodeInfo.ReadFromV2() with tests Untested code never works. Qed. Signed-off-by: Evgenii Stratonikov --- netmap/node_info_test.go | 85 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/netmap/node_info_test.go b/netmap/node_info_test.go index 990cc26..bf6e637 100644 --- a/netmap/node_info_test.go +++ b/netmap/node_info_test.go @@ -1,9 +1,11 @@ package netmap import ( + "fmt" "testing" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" ) @@ -197,3 +199,86 @@ func TestNodeInfo_Clone(t *testing.T) { require.True(t, c != &ni) require.True(t, &(c.PublicKey()[0]) != &(ni.PublicKey()[0])) } + +func TestNodeInfo_Unmarshal(t *testing.T) { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + + attrs := make([]netmap.Attribute, 2) + for i := range attrs { + attrs[i].SetKey(fmt.Sprintf("key%d", i)) + attrs[i].SetValue(fmt.Sprintf("value%d", i)) + } + goodNodeInfo := func() netmap.NodeInfo { + var nodev2 netmap.NodeInfo + nodev2.SetPublicKey(pk.PublicKey().Bytes()) + nodev2.SetAddresses("127.0.0.1:2025") + nodev2.SetState(netmap.Online) + nodev2.SetAttributes(attrs) + return nodev2 + } + + // Check that goodNodeInfo indeed returns good node. + // Otherwise, the whole test is garbage. + require.NoError(t, new(NodeInfo).ReadFromV2(goodNodeInfo())) + + t.Run("empty public key", func(t *testing.T) { + n := goodNodeInfo() + n.SetPublicKey(nil) + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), "missing public key") + }) + t.Run("missing addresses", func(t *testing.T) { + n := goodNodeInfo() + n.SetAddresses() + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), "missing network endpoints") + }) + t.Run("empty attribute key", func(t *testing.T) { + n := goodNodeInfo() + + var a netmap.Attribute + a.SetValue("non-empty") + n.SetAttributes(append(attrs, a)) + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), + fmt.Sprintf("empty key of the attribute #%d", len(attrs))) + }) + t.Run("empty attribute value", func(t *testing.T) { + n := goodNodeInfo() + + var a netmap.Attribute + a.SetKey("non-empty-key") + n.SetAttributes(append(attrs, a)) + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), + "empty value of the attribute non-empty-key") + }) + t.Run("invalid price attribute", func(t *testing.T) { + n := goodNodeInfo() + + var a netmap.Attribute + a.SetKey(attrPrice) + a.SetValue("not a number") + n.SetAttributes(append(attrs, a)) + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), + fmt.Sprintf("invalid %s attribute", attrPrice)) + }) + t.Run("invalid capacity attribute", func(t *testing.T) { + n := goodNodeInfo() + + var a netmap.Attribute + a.SetKey(attrCapacity) + a.SetValue("not a number") + n.SetAttributes(append(attrs, a)) + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), + fmt.Sprintf("invalid %s attribute", attrCapacity)) + }) + t.Run("duplicate attributes", func(t *testing.T) { + t.Skip("FIXME") + n := goodNodeInfo() + + var a netmap.Attribute + a.SetKey("key1") + a.SetValue("value3") + n.SetAttributes(append(attrs, a)) + require.ErrorContains(t, new(NodeInfo).ReadFromV2(n), + "duplicate attributes key1") + }) +} From 88bc9eeb2638fb9142b04ea4f401814bce6b5270 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 10:31:05 +0300 Subject: [PATCH 14/24] [#356] netmap: Check for attribute duplicates in NodeInfo.readFromV2() Signed-off-by: Evgenii Stratonikov --- netmap/node_info.go | 1 + netmap/node_info_test.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/netmap/node_info.go b/netmap/node_info.go index ce3bdb9..63fe047 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -52,6 +52,7 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error } else if _, ok := mAttr[key]; ok { return fmt.Errorf("duplicate attributes %s", key) } + mAttr[key] = struct{}{} switch { case key == attrCapacity: diff --git a/netmap/node_info_test.go b/netmap/node_info_test.go index bf6e637..65ea0e8 100644 --- a/netmap/node_info_test.go +++ b/netmap/node_info_test.go @@ -271,7 +271,6 @@ func TestNodeInfo_Unmarshal(t *testing.T) { fmt.Sprintf("invalid %s attribute", attrCapacity)) }) t.Run("duplicate attributes", func(t *testing.T) { - t.Skip("FIXME") n := goodNodeInfo() var a netmap.Attribute From a0e4d16dbbe96afdc8b1a43f251d9d50a30a966c Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 07:51:32 +0300 Subject: [PATCH 15/24] [#352] container: Implement iterators over attributes Signed-off-by: Evgenii Stratonikov --- container/container.go | 34 +++++++++++++ container/container_test.go | 11 ++--- container/iterators_test.go | 95 +++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 container/iterators_test.go diff --git a/container/container.go b/container/container.go index ff63adb..f10c320 100644 --- a/container/container.go +++ b/container/container.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "errors" "fmt" + "iter" "strconv" "strings" "time" @@ -337,10 +338,41 @@ func (x Container) Attribute(key string) string { return "" } +// Attributes returns an iterator over all Container attributes. +// +// See also [Container.SetAttribute], [Container.UserAttributes]. +func (x Container) Attributes() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + attrs := x.v2.GetAttributes() + for i := range attrs { + if !yield(attrs[i].GetKey(), attrs[i].GetValue()) { + return + } + } + } +} + +// Attributes returns an iterator over all non-system Container attributes. +// +// See also [Container.SetAttribute], [Container.Attributes]. +func (x Container) UserAttributes() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + for key, value := range x.Attributes() { + if !strings.HasPrefix(key, container.SysAttributePrefix) { + if !yield(key, value) { + return + } + } + } + } +} + // IterateAttributes iterates over all Container attributes and passes them // into f. The handler MUST NOT be nil. // // See also SetAttribute, Attribute. +// +// Deprecated: use [Container.Attributes] instead. func (x Container) IterateAttributes(f func(key, val string)) { attrs := x.v2.GetAttributes() for i := range attrs { @@ -352,6 +384,8 @@ func (x Container) IterateAttributes(f func(key, val string)) { // into f. The handler MUST NOT be nil. // // See also SetAttribute, Attribute. +// +// Deprecated: use [Container.UserAttributes] instead. func (x Container) IterateUserAttributes(f func(key, val string)) { attrs := x.v2.GetAttributes() for _, attr := range attrs { diff --git a/container/container_test.go b/container/container_test.go index a66a866..62422ab 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -2,6 +2,7 @@ package container_test import ( "crypto/sha256" + "maps" "strconv" "testing" "time" @@ -159,9 +160,9 @@ func TestContainer_Attribute(t *testing.T) { val.SetAttribute(attrKey2, attrVal2) var i int - val.IterateUserAttributes(func(key, val string) { + for range val.UserAttributes() { i++ - }) + } require.Equal(t, 1, i) var msg v2container.Container @@ -177,11 +178,7 @@ func TestContainer_Attribute(t *testing.T) { require.Equal(t, attrVal1, val2.Attribute(attrKey1)) require.Equal(t, attrVal2, val2.Attribute(attrKey2)) - m := map[string]string{} - - val2.IterateAttributes(func(key, val string) { - m[key] = val - }) + m := maps.Collect(val2.Attributes()) require.GreaterOrEqual(t, len(m), 2) require.Equal(t, attrVal1, m[attrKey1]) diff --git a/container/iterators_test.go b/container/iterators_test.go new file mode 100644 index 0000000..00522a5 --- /dev/null +++ b/container/iterators_test.go @@ -0,0 +1,95 @@ +package container_test + +import ( + "testing" + + containerAPI "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/container" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" + "github.com/stretchr/testify/require" +) + +func TestContainer_Attributes(t *testing.T) { + t.Run("empty", func(t *testing.T) { + var n container.Container + t.Run("attributes", func(t *testing.T) { + for range n.Attributes() { + t.Fatalf("handler is called, but it shouldn't") + } + }) + t.Run("user attributes", func(t *testing.T) { + for range n.UserAttributes() { + t.Fatalf("handler is called, but it shouldn't") + } + }) + }) + + var n container.Container + n.SetAttribute(containerAPI.SysAttributeName, "myname") + n.SetAttribute("key1", "value1") + n.SetAttribute("key2", "value2") + n.SetAttribute(containerAPI.SysAttributeZone, "test") + + t.Run("break", func(t *testing.T) { + t.Run("attributes", func(t *testing.T) { + var res [][2]string + for key, value := range n.Attributes() { + if key == "key2" { + break + } + res = append(res, [2]string{key, value}) + } + require.Equal(t, [][2]string{{containerAPI.SysAttributeName, "myname"}, {"key1", "value1"}}, res) + }) + t.Run("user attributes", func(t *testing.T) { + var res [][2]string + for key, value := range n.UserAttributes() { + if key == "key2" { + break + } + res = append(res, [2]string{key, value}) + } + require.Equal(t, [][2]string{{"key1", "value1"}}, res) + }) + }) + t.Run("continue", func(t *testing.T) { + t.Run("attributes", func(t *testing.T) { + var res [][2]string + for key, value := range n.Attributes() { + if key == "key2" { + continue + } + res = append(res, [2]string{key, value}) + } + require.Equal(t, [][2]string{{containerAPI.SysAttributeName, "myname"}, {"key1", "value1"}, {containerAPI.SysAttributeZone, "test"}}, res) + }) + t.Run("user attributes", func(t *testing.T) { + var res [][2]string + for key, value := range n.UserAttributes() { + if key == "key2" { + continue + } + res = append(res, [2]string{key, value}) + } + require.Equal(t, [][2]string{{"key1", "value1"}}, res) + }) + }) + t.Run("attributes", func(t *testing.T) { + var res [][2]string + for key, value := range n.Attributes() { + res = append(res, [2]string{key, value}) + } + require.Equal(t, [][2]string{ + {containerAPI.SysAttributeName, "myname"}, + {"key1", "value1"}, + {"key2", "value2"}, + {containerAPI.SysAttributeZone, "test"}, + }, res) + }) + t.Run("user attributes", func(t *testing.T) { + var res [][2]string + for key, value := range n.UserAttributes() { + res = append(res, [2]string{key, value}) + } + require.Equal(t, [][2]string{{"key1", "value1"}, {"key2", "value2"}}, res) + }) +} From f158a6b2e14b82c856d9c2c36fd92bc167d7fe6a Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 08:40:41 +0300 Subject: [PATCH 16/24] [#353] pool: Remove deprecated IterateNetworkEndpoints() Signed-off-by: Evgenii Stratonikov --- pool/tree/pool.go | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pool/tree/pool.go b/pool/tree/pool.go index dda74c1..dd5d826 100644 --- a/pool/tree/pool.go +++ b/pool/tree/pool.go @@ -1081,20 +1081,15 @@ func (p *Pool) deleteClientFromMap(hash uint64) { } func (p *Pool) getNewTreeClient(ctx context.Context, node netmap.NodeInfo) (*treeClient, error) { - var ( - treeCl *treeClient - err error - ) - - node.IterateNetworkEndpoints(func(endpoint string) bool { + for endpoint := range node.NetworkEndpoints() { var addr network.Address - if err = addr.FromString(endpoint); err != nil { + if err := addr.FromString(endpoint); err != nil { p.log(zap.WarnLevel, "can't parse endpoint", zap.String("endpoint", endpoint), zap.Error(err)) - return false + continue } newTreeCl := newTreeClient(addr.URIAddr(), p.dialOptions, p.nodeDialTimeout, p.streamTimeout) - if err = newTreeCl.dial(ctx); err != nil { + if err := newTreeCl.dial(ctx); err != nil { p.log(zap.WarnLevel, "failed to dial tree client", zap.Error(err)) // We have to close connection here after failed `dial()`. @@ -1106,18 +1101,13 @@ func (p *Pool) getNewTreeClient(ctx context.Context, node netmap.NodeInfo) (*tre p.log(zap.WarnLevel, "failed to close recently dialed tree client", zap.Error(err)) } - return false + continue } - treeCl = newTreeCl - return true - }) - - if treeCl == nil { - return nil, fmt.Errorf("tree client wasn't initialized") + return newTreeCl, nil } - return treeCl, nil + return nil, fmt.Errorf("tree client wasn't initialized") } func shouldTryAgain(err error) bool { From 661adf17bb54f2d3f9d16b790305876a208f5ff9 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 08:03:56 +0300 Subject: [PATCH 17/24] [#353] Format deprecated notices properly They should be in a separate paragraph, otherwise they are not recognized. Signed-off-by: Evgenii Stratonikov --- api/netmap/types.go | 1 + netmap/node_info.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/api/netmap/types.go b/api/netmap/types.go index 9d9fa09..6dfe676 100644 --- a/api/netmap/types.go +++ b/api/netmap/types.go @@ -439,6 +439,7 @@ func (ni NodeInfo) Addresses() iter.Seq[string] { // Breaks iteration on f's true return. // // Handler should not be nil. +// // Deprecated: use [NodeInfo.Addresses] instead. func (ni *NodeInfo) IterateAddresses(f func(string) bool) { if ni != nil { diff --git a/netmap/node_info.go b/netmap/node_info.go index 63fe047..7edd05c 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -202,6 +202,7 @@ func (x NodeInfo) NumberOfNetworkEndpoints() int { // FrostFS system requirements. // // See also SetNetworkEndpoints. +// // Deprecated: use [NodeInfo.NetworkEndpoints] instead. func (x NodeInfo) IterateNetworkEndpoints(f func(string) bool) { for s := range x.NetworkEndpoints() { @@ -221,6 +222,7 @@ func (x NodeInfo) NetworkEndpoints() iter.Seq[string] { // IterateNetworkEndpoints is an extra-sugared function over IterateNetworkEndpoints // method which allows to unconditionally iterate over all node's network endpoints. +// // Deprecated: use [NodeInfo.NetworkEndpoints] instead. func IterateNetworkEndpoints(node NodeInfo, f func(string)) { node.IterateNetworkEndpoints(func(addr string) bool { @@ -423,6 +425,7 @@ func (x NodeInfo) Attributes() iter.Seq2[string, string] { // IterateAttributes iterates over all node attributes and passes the into f. // Handler MUST NOT be nil. +// // Deprecated: use [NodeInfo.Attributes] instead. func (x NodeInfo) IterateAttributes(f func(key, value string)) { a := x.m.GetAttributes() From 16fd3bafe047847e2ce697a276c5325af9da9240 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 08:22:49 +0300 Subject: [PATCH 18/24] [#354] api/netmap: Return a slice of parameters directly `IterateParameters` does a poor job: - it doesn't encapsulate well, because it returns a pointer, - it has a clunky interface, compared to range loop. I have decided to return parameter slice and not `iter.Seq` for 2 reasons: 1. There already is `SetParameters`, so `NetworkConfig` struct is expected to be modified. 2. This iterator uses pointers, so even with this interface the slice can already be changed. Signed-off-by: Evgenii Stratonikov --- api/netmap/types.go | 12 +++++++ netmap/network_info.go | 65 +++++++++++++------------------------ netmap/network_info_test.go | 14 +++----- 3 files changed, 38 insertions(+), 53 deletions(-) diff --git a/api/netmap/types.go b/api/netmap/types.go index 6dfe676..b555972 100644 --- a/api/netmap/types.go +++ b/api/netmap/types.go @@ -577,6 +577,8 @@ type NetworkConfig struct { } // NumberOfParameters returns number of network parameters. +// +// Deprecated: use [NetworkConfig.Parameters] instead. func (x *NetworkConfig) NumberOfParameters() int { if x != nil { return len(x.ps) @@ -585,10 +587,20 @@ func (x *NetworkConfig) NumberOfParameters() int { return 0 } +// Parameters returns an iterator over network parameters. +func (x *NetworkConfig) Parameters() []NetworkParameter { + if x != nil { + return x.ps + } + return nil +} + // IterateParameters iterates over network parameters. // Breaks iteration on f's true return. // // Handler must not be nil. +// +// Deprecated: use [NetworkConfig.Parameters] instead. func (x *NetworkConfig) IterateParameters(f func(*NetworkParameter) bool) { if x != nil { for i := range x.ps { diff --git a/netmap/network_info.go b/netmap/network_info.go index 11b0f14..a270afe 100644 --- a/netmap/network_info.go +++ b/netmap/network_info.go @@ -30,20 +30,19 @@ func (x *NetworkInfo) readFromV2(m netmap.NetworkInfo, checkFieldPresence bool) return errors.New("missing network config") } - if checkFieldPresence && c.NumberOfParameters() <= 0 { + if checkFieldPresence && len(c.Parameters()) == 0 { return errors.New("missing network parameters") } var err error - mNames := make(map[string]struct{}, c.NumberOfParameters()) + mNames := make(map[string]struct{}, len(c.Parameters())) - c.IterateParameters(func(prm *netmap.NetworkParameter) bool { + for _, prm := range c.Parameters() { name := string(prm.GetKey()) _, was := mNames[name] if was { - err = fmt.Errorf("duplicated parameter name: %s", name) - return true + return fmt.Errorf("duplicated parameter name: %s", name) } mNames[name] = struct{}{} @@ -67,14 +66,8 @@ func (x *NetworkInfo) readFromV2(m netmap.NetworkInfo, checkFieldPresence bool) } if err != nil { - err = fmt.Errorf("invalid %s parameter: %w", name, err) + return fmt.Errorf("invalid %s parameter: %w", name, err) } - - return err != nil - }) - - if err != nil { - return err } x.m = m @@ -152,41 +145,29 @@ func (x *NetworkInfo) setConfig(name string, val []byte) { return } - found := false - prms := make([]netmap.NetworkParameter, 0, c.NumberOfParameters()) - - c.IterateParameters(func(prm *netmap.NetworkParameter) bool { - found = bytes.Equal(prm.GetKey(), []byte(name)) - if found { - prm.SetValue(val) - } else { - prms = append(prms, *prm) + prms := c.Parameters() + for i := range prms { + if bytes.Equal(prms[i].GetKey(), []byte(name)) { + prms[i].SetValue(val) + return } - - return found - }) - - if !found { - prms = append(prms, netmap.NetworkParameter{}) - prms[len(prms)-1].SetKey([]byte(name)) - prms[len(prms)-1].SetValue(val) - - c.SetParameters(prms...) } + + prms = append(prms, netmap.NetworkParameter{}) + prms[len(prms)-1].SetKey([]byte(name)) + prms[len(prms)-1].SetValue(val) + + c.SetParameters(prms...) } func (x NetworkInfo) configValue(name string) (res []byte) { - x.m.GetNetworkConfig().IterateParameters(func(prm *netmap.NetworkParameter) bool { + for _, prm := range x.m.GetNetworkConfig().Parameters() { if string(prm.GetKey()) == name { - res = prm.GetValue() - - return true + return prm.GetValue() } + } - return false - }) - - return + return nil } // SetRawNetworkParameter sets named FrostFS network parameter whose value is @@ -218,7 +199,7 @@ func (x *NetworkInfo) RawNetworkParameter(name string) []byte { func (x *NetworkInfo) IterateRawNetworkParameters(f func(name string, value []byte)) { c := x.m.GetNetworkConfig() - c.IterateParameters(func(prm *netmap.NetworkParameter) bool { + for _, prm := range c.Parameters() { name := string(prm.GetKey()) switch name { default: @@ -237,9 +218,7 @@ func (x *NetworkInfo) IterateRawNetworkParameters(f func(name string, value []by configHomomorphicHashingDisabled, configMaintenanceModeAllowed: } - - return false - }) + } } func (x *NetworkInfo) setConfigUint64(name string, num uint64) { diff --git a/netmap/network_info_test.go b/netmap/network_info_test.go index 7e6dc12..64abe5d 100644 --- a/netmap/network_info_test.go +++ b/netmap/network_info_test.go @@ -76,16 +76,10 @@ func testConfigValue(t *testing.T, var m netmap.NetworkInfo x.WriteToV2(&m) - require.EqualValues(t, 1, m.GetNetworkConfig().NumberOfParameters()) - found := false - m.GetNetworkConfig().IterateParameters(func(prm *netmap.NetworkParameter) bool { - require.False(t, found) - require.Equal(t, []byte(v2Key), prm.GetKey()) - require.Equal(t, v2Val(exp), prm.GetValue()) - found = true - return false - }) - require.True(t, found) + var p netmap.NetworkParameter + p.SetKey([]byte(v2Key)) + p.SetValue(v2Val(exp)) + require.Equal(t, []netmap.NetworkParameter{p}, m.GetNetworkConfig().Parameters()) } setter(&x, val1) From 338faaf3089b96e41ad87a735d8126455b359bce Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 09:56:22 +0300 Subject: [PATCH 19/24] [#355] netmap: Remove unnecessary err declaration Signed-off-by: Evgenii Stratonikov --- netmap/node_info.go | 1 - 1 file changed, 1 deletion(-) diff --git a/netmap/node_info.go b/netmap/node_info.go index 7edd05c..40a9fd8 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -61,7 +61,6 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error return fmt.Errorf("invalid %s attribute: %w", attrCapacity, err) } case key == attrPrice: - var err error _, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) if err != nil { return fmt.Errorf("invalid %s attribute: %w", attrPrice, err) From d282cd094fb4c757b1ecb658f653e1ced482b158 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 5 Apr 2025 10:06:20 +0300 Subject: [PATCH 20/24] [#355] netmap: Cache price and capacity attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They are used by HRW sorting and it makes sense to store them separately instead of iterating over all attributes each time we need them. It also simplifies code: we already parse them in NodeInfo.readFromV2(), so just save the result. ``` goos: linux goarch: amd64 pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz │ old │ new │ │ sec/op │ sec/op vs base │ Netmap_ContainerNodes/REP_2-8 5.923µ ± 0% 5.209µ ± 1% -12.05% (p=0.000 n=10) Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8 5.931µ ± 7% 5.088µ ± 1% -14.22% (p=0.000 n=10) geomean 5.927µ 5.148µ -13.14% │ old │ new │ │ B/op │ B/op vs base │ Netmap_ContainerNodes/REP_2-8 7.609Ki ± 0% 8.172Ki ± 0% +7.39% (p=0.000 n=10) Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8 7.031Ki ± 0% 7.469Ki ± 0% +6.22% (p=0.000 n=10) geomean 7.315Ki 7.812Ki +6.81% │ old │ new │ │ allocs/op │ allocs/op vs base │ Netmap_ContainerNodes/REP_2-8 77.00 ± 0% 77.00 ± 0% ~ (p=1.000 n=10) ¹ Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8 77.00 ± 0% 77.00 ± 0% ~ (p=1.000 n=10) ¹ geomean 77.00 77.00 +0.00% ¹ all samples are equal ``` Signed-off-by: Evgenii Stratonikov --- netmap/aggregator.go | 2 +- netmap/context.go | 4 ++-- netmap/filter.go | 4 ++-- netmap/node_info.go | 52 +++++++++++++++++++------------------------- 4 files changed, 27 insertions(+), 35 deletions(-) diff --git a/netmap/aggregator.go b/netmap/aggregator.go index 1faba9e..d0894f1 100644 --- a/netmap/aggregator.go +++ b/netmap/aggregator.go @@ -54,7 +54,7 @@ var ( // capacity and price. func newWeightFunc(capNorm, priceNorm normalizer) weightFunc { return func(n NodeInfo) float64 { - return capNorm.Normalize(float64(n.capacity())) * priceNorm.Normalize(float64(n.Price())) + return capNorm.Normalize(float64(n.capacity)) * priceNorm.Normalize(float64(n.price)) } } diff --git a/netmap/context.go b/netmap/context.go index 2772725..2d81999 100644 --- a/netmap/context.go +++ b/netmap/context.go @@ -97,8 +97,8 @@ func defaultWeightFunc(ns nodes) weightFunc { minV := newMinAgg() for i := range ns { - mean.Add(float64(ns[i].capacity())) - minV.Add(float64(ns[i].Price())) + mean.Add(float64(ns[i].capacity)) + minV.Add(float64(ns[i].price)) } return newWeightFunc( diff --git a/netmap/filter.go b/netmap/filter.go index 38230b7..9fe346c 100644 --- a/netmap/filter.go +++ b/netmap/filter.go @@ -133,9 +133,9 @@ func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool { switch f.GetKey() { case attrPrice: - attr = b.Price() + attr = b.price case attrCapacity: - attr = b.capacity() + attr = b.capacity default: var err error diff --git a/netmap/node_info.go b/netmap/node_info.go index 40a9fd8..ba65daa 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -26,6 +26,9 @@ import ( type NodeInfo struct { m netmap.NodeInfo hash uint64 + + capacity uint64 + price uint64 } // reads NodeInfo from netmap.NodeInfo message. If checkFieldPresence is set, @@ -33,6 +36,7 @@ type NodeInfo struct { // presented field according to FrostFS API V2 protocol. func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error { var err error + var capacity, price uint64 binPublicKey := m.GetPublicKey() if checkFieldPresence && len(binPublicKey) == 0 { @@ -56,12 +60,12 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error switch { case key == attrCapacity: - _, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) + capacity, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) if err != nil { return fmt.Errorf("invalid %s attribute: %w", attrCapacity, err) } case key == attrPrice: - _, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) + price, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) if err != nil { return fmt.Errorf("invalid %s attribute: %w", attrPrice, err) } @@ -74,6 +78,8 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error x.m = m x.hash = hrw.Hash(binPublicKey) + x.capacity = capacity + x.price = price return nil } @@ -252,46 +258,21 @@ func (x *NodeInfo) setNumericAttribute(key string, num uint64) { // price is announced. func (x *NodeInfo) SetPrice(price uint64) { x.setNumericAttribute(attrPrice, price) + x.price = price } // Price returns price set using SetPrice. // // Zero NodeInfo has zero price. func (x NodeInfo) Price() uint64 { - val := x.Attribute(attrPrice) - if val == "" { - return 0 - } - - price, err := strconv.ParseUint(val, 10, 64) - if err != nil { - panic(fmt.Sprintf("unexpected price parsing error %s: %v", val, err)) - } - - return price + return x.price } // SetCapacity sets the storage capacity declared by the node. By default, zero // capacity is announced. func (x *NodeInfo) SetCapacity(capacity uint64) { x.setNumericAttribute(attrCapacity, capacity) -} - -// capacity returns capacity set using SetCapacity. -// -// Zero NodeInfo has zero capacity. -func (x NodeInfo) capacity() uint64 { - val := x.Attribute(attrCapacity) - if val == "" { - return 0 - } - - capacity, err := strconv.ParseUint(val, 10, 64) - if err != nil { - panic(fmt.Sprintf("unexpected capacity parsing error %s: %v", val, err)) - } - - return capacity + x.capacity = capacity } const attrUNLOCODE = "UN-LOCODE" @@ -442,6 +423,17 @@ func (x *NodeInfo) SetAttribute(key, value string) { panic("empty value in SetAttribute") } + // NodeInfo with non-numeric `Price`` or `Capacity` attributes + // is considered invalid by NodeInfo.readFromV2(). + // Here we have no way to signal an error, and panic seems an overkill. + // So, set cached fields only if we can parse the value and 0 parsing fails. + switch key { + case attrPrice: + x.price, _ = strconv.ParseUint(value, 10, 64) + case attrCapacity: + x.capacity, _ = strconv.ParseUint(value, 10, 64) + } + a := x.m.GetAttributes() for i := range a { if a[i].GetKey() == key { From fc8f9637fedbc9c07ed6e988f382ef3241499891 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Mar 2025 15:13:11 +0300 Subject: [PATCH 21/24] [#35] go.mod: Update go-multiaddr to v0.15.0 New multiaddr release is incompatible with the old one: https://github.com/multiformats/go-multiaddr/releases/tag/v0.15.0 https://github.com/multiformats/go-multiaddr/blob/master/v015-MIGRATION.md Thankfully, it seems we are not affected by any breaking changes. Signed-off-by: Evgenii Stratonikov --- go.mod | 20 ++++++++++---------- go.sum | 59 ++++++++++++++++++++++++---------------------------------- 2 files changed, 34 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 5ee242b..76ef49e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.frostfs.info/TrueCloudLab/frostfs-sdk-go -go 1.23 +go 1.23.0 require ( git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e @@ -14,11 +14,11 @@ require ( github.com/klauspost/reedsolomon v1.12.1 github.com/mailru/easyjson v0.7.7 github.com/mr-tron/base58 v1.2.0 - github.com/multiformats/go-multiaddr v0.14.0 + github.com/multiformats/go-multiaddr v0.15.0 github.com/nspcc-dev/neo-go v0.106.2 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 - golang.org/x/sync v0.10.0 + golang.org/x/sync v0.12.0 google.golang.org/grpc v1.69.2 google.golang.org/protobuf v1.36.1 gopkg.in/yaml.v3 v3.0.1 @@ -30,9 +30,9 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/ipfs/go-cid v0.0.7 // indirect + github.com/ipfs/go-cid v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect @@ -49,12 +49,12 @@ require ( github.com/twmb/murmur3 v1.1.8 // indirect go.etcd.io/bbolt v1.3.9 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - lukechampine.com/blake3 v1.2.1 // indirect + lukechampine.com/blake3 v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 0d898c1..1fd06ca 100644 --- a/go.sum +++ b/go.sum @@ -62,12 +62,12 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= -github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= +github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/reedsolomon v1.12.1 h1:NhWgum1efX1x58daOBGCFWcxtEhOhXKKl1HAPQUp03Q= github.com/klauspost/reedsolomon v1.12.1/go.mod h1:nEi5Kjb6QqtbofI6s+cbG/j1da11c96IBYBSnVGtuBs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -76,31 +76,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= -github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.14.0 h1:bfrHrJhrRuh/NXH5mCnemjpbGjzRw/b+tJFOD41g2tU= -github.com/multiformats/go-multiaddr v0.14.0/go.mod h1:6EkVAxtznq2yC3QT5CM1UTAwG0GTP3EWAIcjHuzQ+r4= -github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo= +github.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk= @@ -167,14 +158,13 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -182,8 +172,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -194,19 +184,18 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -235,7 +224,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= -lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= +lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From 971e740ea32b4f5a819e9ce904405c4954881b99 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 17 Mar 2025 15:24:15 +0300 Subject: [PATCH 22/24] [#35] network: Use AppendComponent() for TLS encapsulation As advocated explicitly in https://github.com/multiformats/go-multiaddr/blob/master/v015-MIGRATION.md Answering a question about safety: AppendComponent() appends to `a.ma`` in place. `a.ma` is created by FromString() function, so there is only a single goroutine appending to it. Thus, assuming `FromString()` itself is called from a single goroutine, no data race is introduced. Signed-off-by: Evgenii Stratonikov --- pkg/network/address.go | 2 +- pkg/network/tls.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/network/address.go b/pkg/network/address.go index 45d5939..dff8614 100644 --- a/pkg/network/address.go +++ b/pkg/network/address.go @@ -66,7 +66,7 @@ func (a *Address) FromString(s string) error { if err == nil { a.ma, err = multiaddr.NewMultiaddr(s) if err == nil && hasTLS { - a.ma = a.ma.Encapsulate(tls) + a.ma = a.ma.AppendComponent(tls) } } } diff --git a/pkg/network/tls.go b/pkg/network/tls.go index b3e9663..f30c99e 100644 --- a/pkg/network/tls.go +++ b/pkg/network/tls.go @@ -9,7 +9,7 @@ const ( ) // tls var is used for (un)wrapping other multiaddrs around TLS multiaddr. -var tls, _ = multiaddr.NewMultiaddr("/" + tlsProtocolName) +var tls, _ = multiaddr.NewComponent(tlsProtocolName, "") // IsTLSEnabled searches for wrapped TLS protocol in multiaddr. func (a Address) IsTLSEnabled() bool { From 715b1e41001d6a8196f32c765ad89f5b371f47ed Mon Sep 17 00:00:00 2001 From: Aleksey Kravchenko Date: Fri, 18 Apr 2025 17:43:23 +0300 Subject: [PATCH 23/24] [#358] pool: Add LatestReceivedEpoch to track latest response epoch Signed-off-by: Aleksey Kravchenko --- pool/pool.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pool/pool.go b/pool/pool.go index f6588c5..53bd587 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -1110,6 +1110,12 @@ func (p *Pool) PatchObject(ctx context.Context, prm PrmObjectPatch) (ResPatchObj return res, nil } +// LatestReceivedEpoch returns the epoch number extracted from the metadata +// of responses received from the client pool's most recent request. +func (p *Pool) LatestReceivedEpoch() uint64 { + return p.cache.Epoch() +} + // PutObject writes an object through a remote server using FrostFS API protocol. func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (ResPutObject, error) { cnr, _ := prm.hdr.ContainerID() From 8822aedbbbaa1d76e6b6ffff65cc097daf479469 Mon Sep 17 00:00:00 2001 From: Airat Arifullin Date: Mon, 28 Apr 2025 12:49:40 +0300 Subject: [PATCH 24/24] [#360] bearer: Change `APEOverride` method prototype * Make `APEOverride` also return flag that indicates whether bearer token sets ape override; * Fix unit-tests. Signed-off-by: Airat Arifullin --- bearer/bearer.go | 13 ++++--------- bearer/bearer_test.go | 15 ++++++++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bearer/bearer.go b/bearer/bearer.go index d1b77ed..3c4e0e9 100644 --- a/bearer/bearer.go +++ b/bearer/bearer.go @@ -313,15 +313,10 @@ func (b *Token) SetAPEOverride(v APEOverride) { b.apeOverrideSet = true } -// APEOverride returns APE override set by SetAPEOverride. -// -// Zero Token has zero APEOverride. -func (b *Token) APEOverride() APEOverride { - if b.apeOverrideSet { - return b.apeOverride - } - - return APEOverride{} +// APEOverride returns APE override set by SetAPEOverride and a flag that indicates whether override +// is set for the token. +func (b *Token) APEOverride() (override APEOverride, isSet bool) { + return b.apeOverride, b.apeOverrideSet } // SetImpersonate mark token as impersonate to consider token signer as request owner. diff --git a/bearer/bearer_test.go b/bearer/bearer_test.go index 650dc82..cbba7c5 100644 --- a/bearer/bearer_test.go +++ b/bearer/bearer_test.go @@ -93,7 +93,8 @@ func TestToken_SetAPEOverrides(t *testing.T) { val2 := filled require.NoError(t, val2.Unmarshal(val.Marshal())) - require.Zero(t, val2.APEOverride()) + _, isSet := val2.APEOverride() + require.False(t, isSet) val2 = filled @@ -101,14 +102,16 @@ func TestToken_SetAPEOverrides(t *testing.T) { require.NoError(t, err) require.NoError(t, val2.UnmarshalJSON(jd)) - require.Zero(t, val2.APEOverride()) + _, isSet = val2.APEOverride() + require.False(t, isSet) // set value tApe := bearertest.APEOverride() val.SetAPEOverride(tApe) - require.Equal(t, tApe, val.APEOverride()) + _, isSet = val.APEOverride() + require.True(t, isSet) val.WriteToV2(&m) require.NotNil(t, m.GetBody().GetAPEOverride()) @@ -117,7 +120,8 @@ func TestToken_SetAPEOverrides(t *testing.T) { val2 = filled require.NoError(t, val2.Unmarshal(val.Marshal())) - apeOverride := val2.APEOverride() + apeOverride, isSet := val2.APEOverride() + require.True(t, isSet) require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), apeOverride.ToV2())) val2 = filled @@ -126,7 +130,8 @@ func TestToken_SetAPEOverrides(t *testing.T) { require.NoError(t, err) require.NoError(t, val2.UnmarshalJSON(jd)) - apeOverride = val.APEOverride() + apeOverride, isSet = val.APEOverride() + require.True(t, isSet) require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), apeOverride.ToV2())) }