From c0dd121d3f7eca254f959b022012a73de106d82b Mon Sep 17 00:00:00 2001 From: Andrey Butusov Date: Wed, 13 May 2026 20:19:17 +0300 Subject: [PATCH 1/3] go.mod: update SDK Deprecated `Range`, `RangeHash`. Added range parameters to `Get`. Signed-off-by: Andrey Butusov --- CHANGELOG.md | 1 + go.mod | 2 +- go.sum | 16 ++++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047c40d276..faeb03fa62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Changelog for NeoFS Node - Internal conversions of node addresses received from network map (#3981) ### Updated +- `github.com/nspcc-dev/neofs-sdk-go` module to `v1.0.0-rc.18.0.20260513135441-5c10a9626760` (#3991) ### Updating from v0.52.0 Drop `policer.max_workers` configuration, it's no-op since 0.52.0. diff --git a/go.mod b/go.mod index 304fcccea0..860873cf23 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/nspcc-dev/neo-go v0.118.0 github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea github.com/nspcc-dev/neofs-contract v0.26.1 - github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.18.0.20260417090255-d6b86c01a5af + github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.18.0.20260513135441-5c10a9626760 github.com/nspcc-dev/tzhash v1.8.4 github.com/panjf2000/ants/v2 v2.11.5 github.com/prometheus/client_golang v1.23.2 diff --git a/go.sum b/go.sum index 8ca96d45e1..0d51015911 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,8 @@ github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea h1:mK github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea/go.mod h1:YzhD4EZmC9Z/PNyd7ysC7WXgIgURc9uCG1UWDeV027Y= github.com/nspcc-dev/neofs-contract v0.26.1 h1:7Ii7Q4L3au408LOsIWKiSgfnT1g8G9jo3W7381d41T8= github.com/nspcc-dev/neofs-contract v0.26.1/go.mod h1:pevVF9OWdEN5bweKxOu6ryZv9muCEtS1ppzYM4RfBIo= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.18.0.20260417090255-d6b86c01a5af h1:ZoeUmDyZU3z4GQl5xfbM/brvYgV+9xNkcE1A92aQx4A= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.18.0.20260417090255-d6b86c01a5af/go.mod h1:aUnzNCaipQZUQaai9lcapG90OwYx7g9s4UsrvcVkBoo= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.18.0.20260513135441-5c10a9626760 h1:Bskhno+qkReNu5LZSV4JgCxgIplpd/iqhJGcxt4bv9M= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.18.0.20260513135441-5c10a9626760/go.mod h1:KtAAolqjEdTNZ8T57FvpWsE/i9/Bpj6wuRPeUs/U85I= github.com/nspcc-dev/rfc6979 v0.2.4 h1:NBgsdCjhLpEPJZqmC9rciMZDcSY297po2smeaRjw57k= github.com/nspcc-dev/rfc6979 v0.2.4/go.mod h1:86ylDw6Kss+P6v4QAJqo1Sp3mC0/Zr9G97xSjQ9TuFg= github.com/nspcc-dev/tzhash v1.8.4 h1:lvuPGWsqEo9dVEvo/kdNLKv/Cy0yxRs9z5hJp8VcBuo= @@ -282,16 +282,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= From 70e1c1ad0ccdde9ff38be19481a679d1be7f3c84 Mon Sep 17 00:00:00 2001 From: Andrey Butusov Date: Wed, 13 May 2026 20:23:29 +0300 Subject: [PATCH 2/3] object: drop rangehash Remove deprecated `GetRangeHash` support across CLI and node. Keep only compatibility handling required by current API surface. Signed-off-by: Andrey Butusov --- CHANGELOG.md | 4 + cmd/neofs-cli/modules/acl/extended/create.go | 2 +- cmd/neofs-cli/modules/object/hash.go | 154 --------------- cmd/neofs-cli/modules/object/root.go | 2 - cmd/neofs-cli/modules/object/util.go | 2 - .../modules/object/util_session_v2.go | 2 - cmd/neofs-cli/modules/session/create_v2.go | 4 +- cmd/neofs-cli/modules/util/acl.go | 6 +- cmd/neofs-node/object.go | 12 -- .../neofs-cli_acl_extended_create.md | 2 +- docs/cli-commands/neofs-cli_object.md | 1 - docs/cli-commands/neofs-cli_object_hash.md | 43 ----- pkg/core/client/client.go | 1 - pkg/metrics/object.go | 87 ++++----- pkg/network/cache/clients.go | 9 - pkg/services/object/acl/eacl/v2/headers.go | 1 - pkg/services/object/acl/v2/service.go | 16 -- pkg/services/object/acl/v2/service_test.go | 20 -- pkg/services/object/acl/v2/util.go | 7 +- pkg/services/object/acl/v2/util_test.go | 7 +- pkg/services/object/get/exec.go | 15 +- pkg/services/object/get/get.go | 84 +-------- pkg/services/object/get/prm.go | 36 ---- pkg/services/object/get/res.go | 9 - pkg/services/object/get/util.go | 14 -- pkg/services/object/put/service_test.go | 4 - pkg/services/object/server.go | 178 +----------------- pkg/services/object/server_test.go | 14 -- 28 files changed, 57 insertions(+), 679 deletions(-) delete mode 100644 cmd/neofs-cli/modules/object/hash.go delete mode 100644 docs/cli-commands/neofs-cli_object_hash.md delete mode 100644 pkg/services/object/get/res.go diff --git a/CHANGELOG.md b/CHANGELOG.md index faeb03fa62..8d0ef2b6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Changelog for NeoFS Node - `policer.max_workers` configuration (#3920) - Deprecated `Search` method support from storage nodes (#3931) - Internal conversions of node addresses received from network map (#3981) +- `GetRangeHash` method support from storage nodes and related `object hash` CLI command (#3991) ### Updated - `github.com/nspcc-dev/neofs-sdk-go` module to `v1.0.0-rc.18.0.20260513135441-5c10a9626760` (#3991) @@ -60,6 +61,9 @@ migrate to `Searchv2` if needed. Metabase will migrate to version 11 with this release to update object counters, this can take a while for shards with high object numbers. +Storage nodes no longer implement deprecated `GetRangeHash` method and +`neofs-cli object hash` command has been removed. + ## [0.52.0] - 2026-03-27 - Woodo Delivering performance optimizations and initial placement feature this diff --git a/cmd/neofs-cli/modules/acl/extended/create.go b/cmd/neofs-cli/modules/acl/extended/create.go index b007b5574b..07c3daf552 100644 --- a/cmd/neofs-cli/modules/acl/extended/create.go +++ b/cmd/neofs-cli/modules/acl/extended/create.go @@ -24,7 +24,7 @@ Rule consist of these blocks: [ ...] [ .. Action is 'allow' or 'deny'. -Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'. +Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', or 'getrange'. Filter consists of : Typ is 'obj' for object applied filter or 'req' for request applied filter. diff --git a/cmd/neofs-cli/modules/object/hash.go b/cmd/neofs-cli/modules/object/hash.go deleted file mode 100644 index 8c11d643de..0000000000 --- a/cmd/neofs-cli/modules/object/hash.go +++ /dev/null @@ -1,154 +0,0 @@ -package object - -import ( - "encoding/hex" - "fmt" - "strings" - - internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" - "github.com/nspcc-dev/neofs-sdk-go/client" - cid "github.com/nspcc-dev/neofs-sdk-go/container/id" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" - "github.com/nspcc-dev/neofs-sdk-go/user" - "github.com/spf13/cobra" -) - -const getRangeHashSaltFlag = "salt" - -const ( - hashSha256 = "sha256" - rangeSep = ":" -) - -var objectHashCmd = &cobra.Command{ - Use: "hash", - Short: "Get object hash", - Long: "Get object hash", - Args: cobra.NoArgs, - RunE: getObjectHash, -} - -func initObjectHashCmd() { - commonflags.Init(objectHashCmd) - initFlagSession(objectHashCmd, "RANGEHASH") - - flags := objectHashCmd.Flags() - - flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage) - _ = objectHashCmd.MarkFlagRequired(commonflags.CIDFlag) - - flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage) - _ = objectHashCmd.MarkFlagRequired(commonflags.OIDFlag) - - flags.String("range", "", "Range to take hash from in the form offset1:length1,... Full object payload length if not specified") - flags.String("type", hashSha256, "Hash type. Deprecated: can be omitted, only 'sha256' is currently supported") - flags.String(getRangeHashSaltFlag, "", "Salt in hex format") -} - -func getObjectHash(cmd *cobra.Command, _ []string) error { - var cnr cid.ID - var obj oid.ID - - _, err := readObjectAddress(cmd, &cnr, &obj) - if err != nil { - return err - } - - ranges, err := getRangeList(cmd) - if err != nil { - return err - } - // only validation of deprecated no-op flag - _, err = getHashType(cmd) - if err != nil { - return err - } - - strSalt := strings.TrimPrefix(cmd.Flag(getRangeHashSaltFlag).Value.String(), "0x") - - salt, err := hex.DecodeString(strSalt) - if err != nil { - return fmt.Errorf("could not decode salt: %w", err) - } - - pk, err := key.GetOrGenerate(cmd) - if err != nil { - return err - } - - ctx, cancel := commonflags.GetCommandContext(cmd) - defer cancel() - - cli, err := internalclient.GetSDKClientByFlag(ctx, commonflags.RPC) - if err != nil { - return err - } - defer cli.Close() - - fullHash := len(ranges) == 0 - if fullHash { - common.PrintVerbose(cmd, "Get the hash of the full object payload.") - var headPrm client.PrmObjectHead - err = Prepare(cmd, &headPrm) - if err != nil { - return err - } - - // get hash of full payload through HEAD (may be user can do it through dedicated command?) - hdr, err := cli.ObjectHead(ctx, cnr, obj, user.NewAutoIDSigner(*pk), headPrm) - if err != nil { - return fmt.Errorf("rpc error: read object header via client: %w", err) - } - - cs, csSet := hdr.PayloadChecksum() - if csSet { - cmd.Println(hex.EncodeToString(cs.Value())) - } else { - cmd.Println("Missing checksum in object header.") - } - - return nil - } - - var hashPrm client.PrmObjectHash - err = Prepare(cmd, &hashPrm) - if err != nil { - return err - } - err = readSession(cmd, &hashPrm, pk, cnr, obj) - if err != nil { - return err - } - hashPrm.UseSalt(salt) - - rngs := make([]uint64, 2*len(ranges)) - for i := range ranges { - rngs[2*i] = ranges[i].GetOffset() - rngs[2*i+1] = ranges[i].GetLength() - } - hashPrm.SetRangeList(rngs...) - - hs, err := cli.ObjectHash(ctx, cnr, obj, user.NewAutoIDSigner(*pk), hashPrm) - if err != nil { - return fmt.Errorf("rpc error: read payload hashes via client: %w", err) - } - - for i := range hs { - cmd.Printf("Offset=%d (Length=%d)\t: %s\n", ranges[i].GetOffset(), ranges[i].GetLength(), - hex.EncodeToString(hs[i])) - } - return nil -} - -func getHashType(cmd *cobra.Command) (string, error) { - rawType := cmd.Flag("type").Value.String() - switch typ := strings.ToLower(rawType); typ { - case hashSha256: - return typ, nil - default: - return "", fmt.Errorf("invalid hash type: %s", typ) - } -} diff --git a/cmd/neofs-cli/modules/object/root.go b/cmd/neofs-cli/modules/object/root.go index 4864453097..d49f333241 100644 --- a/cmd/neofs-cli/modules/object/root.go +++ b/cmd/neofs-cli/modules/object/root.go @@ -26,7 +26,6 @@ func init() { objectSearchCmd, searchV2Cmd, objectHeadCmd, - objectHashCmd, objectRangeCmd, objectLockCmd} @@ -43,7 +42,6 @@ func init() { initObjectGetCmd() initObjectSearchCmd() initObjectHeadCmd() - initObjectHashCmd() initObjectRangeCmd() initCommandObjectLock() initObjectNodesCmd() diff --git a/cmd/neofs-cli/modules/object/util.go b/cmd/neofs-cli/modules/object/util.go index 910e0190d8..c72151c2bd 100644 --- a/cmd/neofs-cli/modules/object/util.go +++ b/cmd/neofs-cli/modules/object/util.go @@ -224,8 +224,6 @@ func _readVerifiedSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.Private cmdVerb = session.VerbObjectSearch case *client.PrmObjectRange: cmdVerb = session.VerbObjectRange - case *client.PrmObjectHash: - cmdVerb = session.VerbObjectRangeHash } tok, err := getVerifiedSession(cmd, cmdVerb, key, cnr) diff --git a/cmd/neofs-cli/modules/object/util_session_v2.go b/cmd/neofs-cli/modules/object/util_session_v2.go index 1b87c5bd4f..f570b1256e 100644 --- a/cmd/neofs-cli/modules/object/util_session_v2.go +++ b/cmd/neofs-cli/modules/object/util_session_v2.go @@ -79,8 +79,6 @@ func attachVerifiedSessionV2(cmd *cobra.Command, tok *session.Token, dst Session cmdVerb = session.VerbObjectSearch case *client.PrmObjectRange: cmdVerb = session.VerbObjectRange - case *client.PrmObjectHash: - cmdVerb = session.VerbObjectRangeHash } err := verifySessionV2(cmd, tok, cmdVerb, key, cnr) diff --git a/cmd/neofs-cli/modules/session/create_v2.go b/cmd/neofs-cli/modules/session/create_v2.go index 4593a60b3e..2c709c19d4 100644 --- a/cmd/neofs-cli/modules/session/create_v2.go +++ b/cmd/neofs-cli/modules/session/create_v2.go @@ -368,8 +368,6 @@ func parseVerbs(verbsStr string) ([]session.Verb, error) { verb = session.VerbObjectDelete case "RANGE", "OBJECTRANGE": verb = session.VerbObjectRange - case "RANGEHASH", "OBJECTRANGEHASH", "RANGE_HASH", "OBJECT_RANGE_HASH": - verb = session.VerbObjectRangeHash case "CONTAINERSET", "CONTAINERSETACL", "CONTAINER_SET", "CONTAINER_SET_ACL": verb = session.VerbContainerSetEACL case "CONTAINERPUT", "CONTAINER_PUT": @@ -377,7 +375,7 @@ func parseVerbs(verbsStr string) ([]session.Verb, error) { case "CONTAINERDELETE", "CONTAINER_DELETE": verb = session.VerbContainerDelete default: - return nil, fmt.Errorf("unknown verb: %s (supported: GET,PUT,HEAD,SEARCH,DELETE,RANGE,RANGEHASH,CONTAINERSET,CONTAINERPUT,CONTAINERDELETE)", verbStr) + return nil, fmt.Errorf("unknown verb: %s (supported: GET,PUT,HEAD,SEARCH,DELETE,RANGE,CONTAINERSET,CONTAINERPUT,CONTAINERDELETE)", verbStr) } verbs = append(verbs, verb) diff --git a/cmd/neofs-cli/modules/util/acl.go b/cmd/neofs-cli/modules/util/acl.go index 04d0383715..6c3d7b506b 100644 --- a/cmd/neofs-cli/modules/util/acl.go +++ b/cmd/neofs-cli/modules/util/acl.go @@ -19,11 +19,11 @@ import ( func PrettyPrintTableBACL(cmd *cobra.Command, bacl *acl.Basic) { // Header w := tabwriter.NewWriter(cmd.OutOrStdout(), 1, 4, 4, ' ', 0) - fmt.Fprintln(w, "\tRangeHASH\tRange\tSearch\tDelete\tPut\tHead\tGet") + fmt.Fprintln(w, "\tRange\tSearch\tDelete\tPut\tHead\tGet") // Bits bits := []string{ boolToString(bacl.Sticky()) + " " + boolToString(!bacl.Extendable()), - getRoleBitsForOperation(bacl, acl.OpObjectHash), getRoleBitsForOperation(bacl, acl.OpObjectRange), + getRoleBitsForOperation(bacl, acl.OpObjectRange), getRoleBitsForOperation(bacl, acl.OpObjectSearch), getRoleBitsForOperation(bacl, acl.OpObjectDelete), getRoleBitsForOperation(bacl, acl.OpObjectPut), getRoleBitsForOperation(bacl, acl.OpObjectHead), getRoleBitsForOperation(bacl, acl.OpObjectGet), @@ -31,7 +31,7 @@ func PrettyPrintTableBACL(cmd *cobra.Command, bacl *acl.Basic) { fmt.Fprintln(w, strings.Join(bits, "\t")) // Footer footer := []string{"X F"} - for range 7 { + for range 6 { footer = append(footer, "U S O B") } fmt.Fprintln(w, strings.Join(footer, "\t")) diff --git a/cmd/neofs-node/object.go b/cmd/neofs-node/object.go index 6f5a0e00f6..8e70c94131 100644 --- a/cmd/neofs-node/object.go +++ b/cmd/neofs-node/object.go @@ -130,10 +130,6 @@ func (s *objectSvc) GetRange(ctx context.Context, prm getsvc.RangePrm) error { return s.get.GetRange(ctx, prm) } -func (s *objectSvc) GetRangeHash(ctx context.Context, prm getsvc.RangeHashPrm) (*getsvc.RangeHashRes, error) { - return s.get.GetRangeHash(ctx, prm) -} - type delNetInfo struct { netmap.State tsLifetime uint64 @@ -443,14 +439,6 @@ func (c *reputationClient) ObjectHead(ctx context.Context, containerID cid.ID, o return res, err } -func (c *reputationClient) ObjectHash(ctx context.Context, containerID cid.ID, objectID oid.ID, signer user.Signer, prm client.PrmObjectHash) ([][]byte, error) { - res, err := c.MultiAddressClient.ObjectHash(ctx, containerID, objectID, signer, prm) - - c.submitResult(err) - - return res, err -} - func (c *reputationClient) ObjectSearchInit(ctx context.Context, containerID cid.ID, signer user.Signer, prm client.PrmObjectSearch) (*client.ObjectListReader, error) { res, err := c.MultiAddressClient.ObjectSearchInit(ctx, containerID, signer, prm) diff --git a/docs/cli-commands/neofs-cli_acl_extended_create.md b/docs/cli-commands/neofs-cli_acl_extended_create.md index 760dcf4161..d00520eb6c 100644 --- a/docs/cli-commands/neofs-cli_acl_extended_create.md +++ b/docs/cli-commands/neofs-cli_acl_extended_create.md @@ -10,7 +10,7 @@ Rule consist of these blocks: [ ...] [ .. Action is 'allow' or 'deny'. -Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'. +Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', or 'getrange'. Filter consists of : Typ is 'obj' for object applied filter or 'req' for request applied filter. diff --git a/docs/cli-commands/neofs-cli_object.md b/docs/cli-commands/neofs-cli_object.md index 6c1d1b7a96..a12b28650a 100644 --- a/docs/cli-commands/neofs-cli_object.md +++ b/docs/cli-commands/neofs-cli_object.md @@ -24,7 +24,6 @@ Operations with Objects * [neofs-cli](neofs-cli.md) - Command Line Tool to work with NeoFS * [neofs-cli object delete](neofs-cli_object_delete.md) - Delete object from NeoFS * [neofs-cli object get](neofs-cli_object_get.md) - Get object from NeoFS -* [neofs-cli object hash](neofs-cli_object_hash.md) - Get object hash * [neofs-cli object head](neofs-cli_object_head.md) - Get object header * [neofs-cli object lock](neofs-cli_object_lock.md) - Lock object in container * [neofs-cli object nodes](neofs-cli_object_nodes.md) - Show nodes for an object diff --git a/docs/cli-commands/neofs-cli_object_hash.md b/docs/cli-commands/neofs-cli_object_hash.md deleted file mode 100644 index ce94ad00f6..0000000000 --- a/docs/cli-commands/neofs-cli_object_hash.md +++ /dev/null @@ -1,43 +0,0 @@ -## neofs-cli object hash - -Get object hash - -### Synopsis - -Get object hash - -``` -neofs-cli object hash [flags] -``` - -### Options - -``` - --address string Address of wallet account - --bearer string File with signed JSON or binary encoded bearer token - --cid string Container ID. - -g, --generate-key Generate new private key - -h, --help help for hash - --oid string Object ID. - --range string Range to take hash from in the form offset1:length1,... Full object payload length if not specified - -r, --rpc-endpoint string Remote node address (as 'multiaddr' or ':') - --salt string Salt in hex format - --session string Filepath to a JSON- or binary-encoded token of the object RANGEHASH session - -t, --timeout duration Timeout for the operation (default 15s) - --ttl uint32 TTL value in request meta header (default 2) - --type string Hash type. Deprecated: can be omitted, only 'sha256' is currently supported (default "sha256") - -w, --wallet string Path to the wallet - -x, --xhdr strings Request X-Headers in form of Key=Value -``` - -### Options inherited from parent commands - -``` - -c, --config string Config file (default is $HOME/.config/neofs-cli/config.yaml) - -v, --verbose Verbose output -``` - -### SEE ALSO - -* [neofs-cli object](neofs-cli_object.md) - Operations with Objects - diff --git a/pkg/core/client/client.go b/pkg/core/client/client.go index ee66a20a3c..eb44a377c0 100644 --- a/pkg/core/client/client.go +++ b/pkg/core/client/client.go @@ -25,7 +25,6 @@ type Client interface { ObjectSearchInit(ctx context.Context, containerID cid.ID, signer user.Signer, prm client.PrmObjectSearch) (*client.ObjectListReader, error) SearchObjects(context.Context, cid.ID, object.SearchFilters, []string, string, neofscrypto.Signer, client.SearchObjectsOptions) ([]client.SearchResultItem, string, error) ObjectRangeInit(ctx context.Context, containerID cid.ID, objectID oid.ID, offset, length uint64, signer user.Signer, prm client.PrmObjectRange) (*client.ObjectRangeReader, error) - ObjectHash(ctx context.Context, containerID cid.ID, objectID oid.ID, signer user.Signer, prm client.PrmObjectHash) ([][]byte, error) AnnounceLocalTrust(ctx context.Context, epoch uint64, trusts []reputationSDK.Trust, prm client.PrmAnnounceLocalTrust) error AnnounceIntermediateTrust(ctx context.Context, epoch uint64, trust reputationSDK.PeerToPeerTrust, prm client.PrmAnnounceIntermediateTrust) error } diff --git a/pkg/metrics/object.go b/pkg/metrics/object.go index ac8cde6ff9..ea834c1d89 100644 --- a/pkg/metrics/object.go +++ b/pkg/metrics/object.go @@ -17,21 +17,19 @@ type ( } objectServiceMetrics struct { - getCounter methodCount - putCounter methodCount - headCounter methodCount - searchCounter methodCount - deleteCounter methodCount - rangeCounter methodCount - rangeHashCounter methodCount - - getDuration prometheus.Histogram - putDuration prometheus.Histogram - headDuration prometheus.Histogram - searchDuration prometheus.Histogram - deleteDuration prometheus.Histogram - rangeDuration prometheus.Histogram - rangeHashDuration prometheus.Histogram + getCounter methodCount + putCounter methodCount + headCounter methodCount + searchCounter methodCount + deleteCounter methodCount + rangeCounter methodCount + + getDuration prometheus.Histogram + putDuration prometheus.Histogram + headDuration prometheus.Histogram + searchDuration prometheus.Histogram + deleteDuration prometheus.Histogram + rangeDuration prometheus.Histogram putPayload prometheus.Counter getPayload prometheus.Counter @@ -78,13 +76,12 @@ func (m methodCount) inc(success bool) { func newObjectServiceMetrics() objectServiceMetrics { var ( // Request counter metrics. - getCounter = newMethodCallCounter("get") - putCounter = newMethodCallCounter("put") - headCounter = newMethodCallCounter("head") - searchCounter = newMethodCallCounter("search") - deleteCounter = newMethodCallCounter("delete") - rangeCounter = newMethodCallCounter("range") - rangeHashCounter = newMethodCallCounter("range_hash") + getCounter = newMethodCallCounter("get") + putCounter = newMethodCallCounter("put") + headCounter = newMethodCallCounter("head") + searchCounter = newMethodCallCounter("search") + deleteCounter = newMethodCallCounter("delete") + rangeCounter = newMethodCallCounter("range") ) var ( // Request duration metrics. @@ -129,13 +126,6 @@ func newObjectServiceMetrics() objectServiceMetrics { Name: "rpc_range_time", Help: "RPC 'range request' handling time", }) - - rangeHashDuration = prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: storageNodeNameSpace, - Subsystem: objectSubsystem, - Name: "rpc_range_hash_time", - Help: "RPC 'range hash' handling time", - }) ) var ( // Object payload metrics. @@ -173,24 +163,22 @@ func newObjectServiceMetrics() objectServiceMetrics { ) return objectServiceMetrics{ - getCounter: getCounter, - putCounter: putCounter, - headCounter: headCounter, - searchCounter: searchCounter, - deleteCounter: deleteCounter, - rangeCounter: rangeCounter, - rangeHashCounter: rangeHashCounter, - getDuration: getDuration, - putDuration: putDuration, - headDuration: headDuration, - searchDuration: searchDuration, - deleteDuration: deleteDuration, - rangeDuration: rangeDuration, - rangeHashDuration: rangeHashDuration, - putPayload: putPayload, - getPayload: getPayload, - shardMetrics: shardsMetrics, - shardsReadonly: shardsReadonly, + getCounter: getCounter, + putCounter: putCounter, + headCounter: headCounter, + searchCounter: searchCounter, + deleteCounter: deleteCounter, + rangeCounter: rangeCounter, + getDuration: getDuration, + putDuration: putDuration, + headDuration: headDuration, + searchDuration: searchDuration, + deleteDuration: deleteDuration, + rangeDuration: rangeDuration, + putPayload: putPayload, + getPayload: getPayload, + shardMetrics: shardsMetrics, + shardsReadonly: shardsReadonly, } } @@ -201,7 +189,6 @@ func (m objectServiceMetrics) register() { m.searchCounter.mustRegister() m.deleteCounter.mustRegister() m.rangeCounter.mustRegister() - m.rangeHashCounter.mustRegister() prometheus.MustRegister(m.getDuration) prometheus.MustRegister(m.putDuration) @@ -209,7 +196,6 @@ func (m objectServiceMetrics) register() { prometheus.MustRegister(m.searchDuration) prometheus.MustRegister(m.deleteDuration) prometheus.MustRegister(m.rangeDuration) - prometheus.MustRegister(m.rangeHashDuration) prometheus.MustRegister(m.putPayload) prometheus.MustRegister(m.getPayload) @@ -240,9 +226,6 @@ func (m objectServiceMetrics) HandleOpExecResult(op stat.Method, success bool, d case stat.MethodObjectRange: m.rangeCounter.inc(success) m.rangeDuration.Observe(d.Seconds()) - case stat.MethodObjectHash: - m.rangeHashCounter.inc(success) - m.rangeHashDuration.Observe(d.Seconds()) } } diff --git a/pkg/network/cache/clients.go b/pkg/network/cache/clients.go index 9864045180..67690d102b 100644 --- a/pkg/network/cache/clients.go +++ b/pkg/network/cache/clients.go @@ -377,15 +377,6 @@ func (x *connections) ObjectRangeInit(ctx context.Context, cnr cid.ID, id oid.ID }) } -func (x *connections) ObjectHash(ctx context.Context, cnr cid.ID, id oid.ID, signer user.Signer, opts client.PrmObjectHash) ([][]byte, error) { - var res [][]byte - return res, x.forAny(ctx, func(ctx context.Context, c *client.Client) error { - var err error - res, err = c.ObjectHash(ctx, cnr, id, signer, opts) - return err - }) -} - func (x *connections) AnnounceLocalTrust(ctx context.Context, epoch uint64, ts []apireputation.Trust, opts client.PrmAnnounceLocalTrust) error { return x.forAny(ctx, func(ctx context.Context, c *client.Client) error { return c.AnnounceLocalTrust(ctx, epoch, ts, opts) diff --git a/pkg/services/object/acl/eacl/v2/headers.go b/pkg/services/object/acl/eacl/v2/headers.go index dd1d418eee..cd49c288d5 100644 --- a/pkg/services/object/acl/eacl/v2/headers.go +++ b/pkg/services/object/acl/eacl/v2/headers.go @@ -140,7 +140,6 @@ func (h *cfg) readObjectHeaders(dst *headerSource) error { dst.incompleteObjectHeaders = !completed case *protoobject.GetRangeRequest, - *protoobject.GetRangeHashRequest, *protoobject.DeleteRequest: if h.obj == nil { return errMissingOID diff --git a/pkg/services/object/acl/v2/service.go b/pkg/services/object/acl/v2/service.go index 6310775322..e657f07d25 100644 --- a/pkg/services/object/acl/v2/service.go +++ b/pkg/services/object/acl/v2/service.go @@ -506,22 +506,6 @@ func (b Service) RangeRequestToInfo(request *protoobject.GetRangeRequest) (Reque return b.findRequestInfo(request, cnr, acl.OpObjectRange, sessionSDK.VerbObjectRange, sessionv2.VerbObjectRange, *obj) } -// HashRequestToInfo resolves RequestInfo from the request to check it using -// [ACLChecker]. -func (b Service) HashRequestToInfo(request *protoobject.GetRangeHashRequest) (RequestInfo, error) { - cnr, err := getContainerIDFromRequest(request) - if err != nil { - return RequestInfo{}, err - } - - obj, err := getObjectIDFromRequestBody(request.GetBody()) - if err != nil { - return RequestInfo{}, err - } - - return b.findRequestInfo(request, cnr, acl.OpObjectHash, sessionSDK.VerbObjectRangeHash, sessionv2.VerbObjectRangeHash, *obj) -} - var ErrSkipRequest = errors.New("skip request") // PutRequestToInfo resolves RequestInfo from the request to check it using diff --git a/pkg/services/object/acl/v2/service_test.go b/pkg/services/object/acl/v2/service_test.go index 95ff2f9c93..f764644167 100644 --- a/pkg/services/object/acl/v2/service_test.go +++ b/pkg/services/object/acl/v2/service_test.go @@ -255,26 +255,6 @@ func TestService_RangeRequestToInfo_BearerTokenIssuer(t *testing.T) { }) } -func TestService_HashRequestToInfo_BearerTokenIssuer(t *testing.T) { - testBearerTokenIssuer(t, (*aclsvc.Service).HashRequestToInfo, func(t *testing.T, signer neofscrypto.Signer, cnrID cid.ID, meta *protosession.RequestMetaHeader) *protoobject.GetRangeHashRequest { - req := &protoobject.GetRangeHashRequest{ - Body: &protoobject.GetRangeHashRequest_Body{ - Address: &refs.Address{ - ContainerId: cnrID.ProtoMessage(), - ObjectId: oidtest.ID().ProtoMessage(), - }, - }, - MetaHeader: meta, - } - - var err error - req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer(signer, req, nil) - require.NoError(t, err) - - return req - }) -} - func TestService_PutRequestToInfo_BearerTokenIssuer(t *testing.T) { testBearerTokenIssuer(t, func(svc *aclsvc.Service, req *protoobject.PutRequest) (aclsvc.RequestInfo, error) { res, _, err := svc.PutRequestToInfo(req) diff --git a/pkg/services/object/acl/v2/util.go b/pkg/services/object/acl/v2/util.go index 7c05ff99e5..e9aef86b9a 100644 --- a/pkg/services/object/acl/v2/util.go +++ b/pkg/services/object/acl/v2/util.go @@ -38,8 +38,6 @@ func getContainerIDFromRequest(req any) (cid.ID, error) { mID = v.GetBody().GetAddress().GetContainerId() case *protoobject.GetRangeRequest: mID = v.GetBody().GetAddress().GetContainerId() - case *protoobject.GetRangeHashRequest: - mID = v.GetBody().GetAddress().GetContainerId() default: return cid.ID{}, errors.New("unknown request type") } @@ -78,12 +76,11 @@ func assertVerb(tok sessionSDK.Object, reqVerb sessionSDK.ObjectVerb) bool { sessionSDK.VerbObjectHead, sessionSDK.VerbObjectGet, sessionSDK.VerbObjectDelete, - sessionSDK.VerbObjectRange, - sessionSDK.VerbObjectRangeHash) + sessionSDK.VerbObjectRange) case sessionSDK.VerbObjectSearch: return tok.AssertVerb(sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete) case sessionSDK.VerbObjectRange: - return tok.AssertVerb(sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash) + return tok.AssertVerb(sessionSDK.VerbObjectRange) } } diff --git a/pkg/services/object/acl/v2/util_test.go b/pkg/services/object/acl/v2/util_test.go index 34de23e055..172fbfe658 100644 --- a/pkg/services/object/acl/v2/util_test.go +++ b/pkg/services/object/acl/v2/util_test.go @@ -22,11 +22,9 @@ func TestIsVerbCompatible(t *testing.T) { sessionSDK.VerbObjectGet, sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectRange, - sessionSDK.VerbObjectRangeHash, }, - sessionSDK.VerbObjectRange: {sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash}, - sessionSDK.VerbObjectRangeHash: {sessionSDK.VerbObjectRangeHash}, - sessionSDK.VerbObjectSearch: {sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete}, + sessionSDK.VerbObjectRange: {sessionSDK.VerbObjectRange}, + sessionSDK.VerbObjectSearch: {sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete}, } verbs := []sessionSDK.ObjectVerb{ @@ -34,7 +32,6 @@ func TestIsVerbCompatible(t *testing.T) { sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectHead, sessionSDK.VerbObjectRange, - sessionSDK.VerbObjectRangeHash, sessionSDK.VerbObjectGet, sessionSDK.VerbObjectSearch, } diff --git a/pkg/services/object/get/exec.go b/pkg/services/object/get/exec.go index d801354975..41054bda4f 100644 --- a/pkg/services/object/get/exec.go +++ b/pkg/services/object/get/exec.go @@ -33,8 +33,7 @@ type execCtx struct { ctx context.Context - prm RangePrm - prmRangeHash *RangeHashPrm + prm RangePrm statusError @@ -100,11 +99,6 @@ func withPayloadRange(r *object.Range) execOption { } } -func withHash(p *RangeHashPrm) execOption { - return func(ctx *execCtx) { - ctx.prmRangeHash = p - } -} func withLogger(l *zap.Logger) execOption { return func(ctx *execCtx) { @@ -467,16 +461,9 @@ func (exec *execCtx) writeCollectedObject() { } } -// isRangeHashForwardingEnabled returns true if common execution -// parameters has GETRANGEHASH request forwarding closure set. -func (exec execCtx) isRangeHashForwardingEnabled() bool { - return exec.prm.rangeForwarder != nil -} - // disableForwarding removes request forwarding closure from common // parameters, so it won't be inherited in new execution contexts. func (exec *execCtx) disableForwarding() { exec.prm.SetRequestForwarder(nil) - exec.prm.SetRangeHashRequestForwarder(nil) exec.forwardGetRequestFn = nil } diff --git a/pkg/services/object/get/get.go b/pkg/services/object/get/get.go index 12b1ce342e..93945f2820 100644 --- a/pkg/services/object/get/get.go +++ b/pkg/services/object/get/get.go @@ -109,11 +109,10 @@ func (s *Service) GetRange(ctx context.Context, prm RangePrm) error { return fmt.Errorf("get nodes for object: %w", err) } - return s.getRange(ctx, prm, nodeLists, repRules, ecRules, nil) + return s.getRange(ctx, prm, nodeLists, repRules, ecRules) } -func (s *Service) getRange(ctx context.Context, prm RangePrm, nodeLists [][]netmap.NodeInfo, repRules []uint, ecRules []iec.Rule, - hashPrm *RangeHashPrm) error { +func (s *Service) getRange(ctx context.Context, prm RangePrm, nodeLists [][]netmap.NodeInfo, repRules []uint, ecRules []iec.Rule) error { if len(repRules) > 0 { // REP format does not require encoding bufOpt := withLocalRangeBuffer(prm.localBuffer, prm.submitLocalStreamFn) forwardOpt := withForwardRangeRequestFunc(prm.forwardRequestFn) @@ -124,14 +123,6 @@ func (s *Service) getRange(ctx context.Context, prm RangePrm, nodeLists [][]netm } ecNodeLists := nodeLists[len(repRules):] - if hashPrm != nil && prm.rangeForwarder != nil && !localNodeInSets(s.neoFSNet, nodeLists) { - hashes, err := s.proxyHashRequest(ctx, ecNodeLists, prm.rangeForwarder) - if err == nil { - hashPrm.forwardedRangeHashResponse = hashes - } - return err - } - if prm.forwardRequestFn != nil && !localNodeInSets(s.neoFSNet, ecNodeLists) { return s.forwardRangeRequest(ctx, ecNodeLists, prm.forwardRequestFn) } @@ -148,77 +139,6 @@ func (s *Service) getRange(ctx context.Context, prm RangePrm, nodeLists [][]netm ecRules, ecNodeLists, prm.rng.GetOffset(), prm.rng.GetLength()) } -func (s *Service) GetRangeHash(ctx context.Context, prm RangeHashPrm) (*RangeHashRes, error) { - nodeLists, repRules, ecRules, err := s.neoFSNet.GetNodesForObject(prm.addr) - if err != nil { - return nil, fmt.Errorf("get nodes for object: %w", err) - } - - hashes := make([][]byte, 0, len(prm.rngs)) - - for _, rng := range prm.rngs { - h := prm.hashGen() - - // For big ranges we could fetch range-hashes from different nodes and concatenate them locally. - // However, - // 1. Potential gains are insignificant when operating in the Internet given typical latencies and losses. - // 2. Parallel solution is more complex in terms of code. - // 3. TZ-hash is likely to be disabled in private installations. - rngPrm := RangePrm{ - commonPrm: prm.commonPrm, - } - - rngPrm.SetRange(&rng) - rngPrm.SetChunkWriter(&hasherWrapper{ - hash: util.NewSaltingWriter(h, prm.salt), - }) - - if err := s.getRange(ctx, rngPrm, nodeLists, repRules, ecRules, &prm); err != nil { - return nil, err - } - - if prm.forwardedRangeHashResponse != nil { - // forwarder request case; no need to collect the other - // parts, the whole response has already been received - hashes = prm.forwardedRangeHashResponse - break - } - - hashes = append(hashes, h.Sum(nil)) - } - - return &RangeHashRes{ - hashes: hashes, - }, nil -} - -func (s *Service) proxyHashRequest(ctx context.Context, sortedNodeLists [][]netmap.NodeInfo, proxyFn RangeRequestForwarder) ([][]byte, error) { - for i := range sortedNodeLists { - for j := range sortedNodeLists[i] { - conn, err := s.conns.(*clientCacheWrapper).connect(ctx, sortedNodeLists[i][j]) - if err != nil { - s.log.Debug("get conn to remote node", - zap.Strings("addresses", slices.Collect(sortedNodeLists[i][j].NetworkEndpoints())), zap.Error(err)) - continue - } - - hashes, err := proxyFn(ctx, conn) - if err == nil { - return hashes, nil - } - - if errors.Is(err, apistatus.ErrObjectAlreadyRemoved) || errors.Is(err, apistatus.ErrObjectAccessDenied) || - errors.Is(err, apistatus.ErrObjectOutOfRange) || errors.Is(err, ctx.Err()) { - return nil, err - } - - s.log.Info("request proxy failed", zap.String("request", "HASH"), zap.Error(err)) - } - } - - return nil, apistatus.ErrObjectNotFound -} - // Head reads object header from container. // // Returns ErrNotFound if the header was not received for the call. diff --git a/pkg/services/object/get/prm.go b/pkg/services/object/get/prm.go index 9c08858667..8496aec597 100644 --- a/pkg/services/object/get/prm.go +++ b/pkg/services/object/get/prm.go @@ -3,7 +3,6 @@ package getsvc import ( "context" "crypto/ecdsa" - "hash" "io" iprotobuf "github.com/nspcc-dev/neofs-node/internal/protobuf" @@ -44,21 +43,7 @@ type RangePrm struct { forwardRequestFn ForwardRangeRequestFunc } -// RangeHashPrm groups parameters of GetRange service call. -type RangeHashPrm struct { - commonPrm - - hashGen func() hash.Hash - - rngs []object.Range - - salt []byte - - forwardedRangeHashResponse [][]byte -} - type RequestForwarder func(context.Context, coreclient.MultiAddressClient) (*object.Object, error) -type RangeRequestForwarder func(context.Context, coreclient.MultiAddressClient) ([][]byte, error) // ForwardHeadRequestFunc sends currently served HEAD request to remote node // through passed connection and returns buffered response with requested @@ -98,8 +83,6 @@ type commonPrm struct { raw bool - rangeForwarder RangeRequestForwarder - // signerKey is a cached key that should be used for spawned // requests (if any), could be nil if incoming request handling // routine does not include any key fetching operations @@ -135,30 +118,11 @@ func (p *RangePrm) SetRange(rng *object.Range) { p.rng = rng } -// SetRangeList sets a list of object payload ranges. -func (p *RangeHashPrm) SetRangeList(rngs []object.Range) { - p.rngs = rngs -} - -// SetHashGenerator sets constructor of hashing algorithm. -func (p *RangeHashPrm) SetHashGenerator(v func() hash.Hash) { - p.hashGen = v -} - -// SetSalt sets binary salt to XOR object's payload ranges before hash calculation. -func (p *RangeHashPrm) SetSalt(salt []byte) { - p.salt = salt -} - // SetCommonParameters sets common parameters of the operation. func (p *commonPrm) SetCommonParameters(common *util.CommonPrm) { p.common = common } -func (p *commonPrm) SetRangeHashRequestForwarder(f RangeRequestForwarder) { - p.rangeForwarder = f -} - // WithAddress sets object address to be read. func (p *commonPrm) WithAddress(addr oid.Address) { p.addr = addr diff --git a/pkg/services/object/get/res.go b/pkg/services/object/get/res.go deleted file mode 100644 index 75a5aaedde..0000000000 --- a/pkg/services/object/get/res.go +++ /dev/null @@ -1,9 +0,0 @@ -package getsvc - -type RangeHashRes struct { - hashes [][]byte -} - -func (r *RangeHashRes) Hashes() [][]byte { - return r.hashes -} diff --git a/pkg/services/object/get/util.go b/pkg/services/object/get/util.go index 890acd6f00..bba295bc82 100644 --- a/pkg/services/object/get/util.go +++ b/pkg/services/object/get/util.go @@ -62,10 +62,6 @@ type partWriter struct { chunkWriter ChunkWriter } -type hasherWrapper struct { - hash io.Writer -} - // fallbackRangeReader wraps a range reader obtained via ObjectRangeInit and // falls back to a full GET in case apistatus.ErrObjectAccessDenied is // returned while reading. @@ -229,11 +225,6 @@ func (c *clientWrapper) getObject(exec *execCtx) (*object.Object, io.ReadCloser, return hdr, nil, nil } - if rngH := exec.prmRangeHash; rngH != nil && exec.isRangeHashForwardingEnabled() { - exec.prmRangeHash.forwardedRangeHashResponse, err = exec.prm.rangeForwarder(exec.ctx, c.client) - return nil, nil, err - } - // we don't specify payload writer because we accumulate // the object locally (even huge). if rng := exec.ctxRange(); rng != nil { @@ -342,11 +333,6 @@ func (w *partWriter) WriteHeader(o *object.Object) error { return w.headWriter.WriteHeader(o) } -func (h *hasherWrapper) WriteChunk(p []byte) error { - _, err := h.hash.Write(p) - return err -} - func prettyRange(rng *object.Range) string { return fmt.Sprintf("[%d:%d]", rng.GetOffset(), rng.GetLength()) } diff --git a/pkg/services/object/put/service_test.go b/pkg/services/object/put/service_test.go index 6aeb7c06ec..708a53feda 100644 --- a/pkg/services/object/put/service_test.go +++ b/pkg/services/object/put/service_test.go @@ -1112,10 +1112,6 @@ func (m *serviceClient) ObjectRangeInit(context.Context, cid.ID, oid.ID, uint64, panic("unimplemented") } -func (m *serviceClient) ObjectHash(context.Context, cid.ID, oid.ID, user.Signer, client.PrmObjectHash) ([][]byte, error) { - panic("unimplemented") -} - func (m *serviceClient) AnnounceLocalTrust(context.Context, uint64, []apireputation.Trust, client.PrmAnnounceLocalTrust) error { // TODO: interfaces are oversaturated. This will never be needed to server object PUT. Refactor this. panic("unimplemented") diff --git a/pkg/services/object/server.go b/pkg/services/object/server.go index 619da94f57..5e63b1d923 100644 --- a/pkg/services/object/server.go +++ b/pkg/services/object/server.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/ecdsa" - "crypto/sha256" "encoding/base64" "encoding/binary" "errors" @@ -48,7 +47,6 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" - "github.com/nspcc-dev/tzhash/tz" "github.com/panjf2000/ants/v2" "google.golang.org/grpc" grpccodes "google.golang.org/grpc/codes" @@ -65,7 +63,6 @@ type Handlers interface { Head(context.Context, getsvc.HeadPrm) error Delete(context.Context, deletesvc.Prm) error GetRange(context.Context, getsvc.RangePrm) error - GetRangeHash(context.Context, getsvc.RangeHashPrm) (*getsvc.RangeHashRes, error) } // Various NeoFS protocol status codes. @@ -83,7 +80,6 @@ const ( // - [stat.MethodObjectDelete] // - [stat.MethodObjectSearch] // - [stat.MethodObjectRange] -// - [stat.MethodObjectHash] type MetricCollector interface { // HandleOpExecResult handles measured execution results of the given op. HandleOpExecResult(_ stat.Method, success bool, _ time.Duration) @@ -160,7 +156,6 @@ type ACLInfoExtractor interface { PutRequestToInfo(*protoobject.PutRequest) (aclsvc.RequestInfo, user.ID, error) DeleteRequestToInfo(*protoobject.DeleteRequest) (aclsvc.RequestInfo, error) HeadRequestToInfo(*protoobject.HeadRequest) (aclsvc.RequestInfo, error) - HashRequestToInfo(*protoobject.GetRangeHashRequest) (aclsvc.RequestInfo, error) GetRequestToInfo(*protoobject.GetRequest) (aclsvc.RequestInfo, error) RangeRequestToInfo(*protoobject.GetRangeRequest) (aclsvc.RequestInfo, error) SearchV2RequestToInfo(*protoobject.SearchV2Request) (aclsvc.RequestInfo, error) @@ -836,174 +831,11 @@ func (s *Server) makeStatusHashResponse(err error, req *protoobject.GetRangeHash }, req) } -// GetRangeHash converts gRPC GetRangeHashRequest message and passes it to internal Object service. -func (s *Server) GetRangeHash(ctx context.Context, req *protoobject.GetRangeHashRequest) (*protoobject.GetRangeHashResponse, error) { - var ( - err error - t = time.Now() - ) - defer func() { s.pushOpExecResult(stat.MethodObjectHash, err, t) }() - if err = icrypto.VerifyRequestSignaturesN3(req, s.fsChain); err != nil { - return s.makeStatusHashResponse(err, req), nil - } - - if s.fsChain.LocalNodeUnderMaintenance() { - return s.makeStatusHashResponse(apistatus.ErrNodeUnderMaintenance, req), nil - } - - reqInfo, err := s.reqInfoProc.HashRequestToInfo(req) - if err != nil { - if !errors.Is(err, apistatus.Error) { - var bad = new(apistatus.BadRequest) - bad.SetMessage(err.Error()) - err = bad // defer - } - return s.makeStatusHashResponse(err, req), nil - } - if !s.aclChecker.CheckBasicACL(reqInfo) { - err = basicACLErr(reqInfo) // needed for defer - return s.makeStatusHashResponse(err, req), nil - } - err = s.aclChecker.CheckEACL(req, reqInfo) - if err != nil && !errors.Is(err, aclsvc.ErrNotMatched) { // Not matched -> follow basic ACL. - err = eACLErr(reqInfo, err) // needed for defer - return s.makeStatusHashResponse(err, req), nil - } - - p, err := convertHashPrm(s.signer, reqInfo.Container, s.storage, req) - if err != nil { - if !errors.Is(err, apistatus.Error) { - var bad = new(apistatus.BadRequest) - bad.SetMessage(err.Error()) - err = bad // defer - } - return s.makeStatusHashResponse(err, req), nil - } - res, err := s.handlers.GetRangeHash(ctx, p) - if err != nil { - return s.makeStatusHashResponse(err, req), nil - } - - return s.signHashResponse(&protoobject.GetRangeHashResponse{ - Body: &protoobject.GetRangeHashResponse_Body{ - Type: req.Body.Type, - HashList: res.Hashes(), - }}, req), nil -} - -// converts original request into parameters accepted by the internal handler. -func convertHashPrm(signer ecdsa.PrivateKey, cnr container.Container, ss sessions, req *protoobject.GetRangeHashRequest) (getsvc.RangeHashPrm, error) { - body := req.GetBody() - ma := body.GetAddress() - if ma == nil { // includes nil body - return getsvc.RangeHashPrm{}, errors.New("missing object address") - } - - var addr oid.Address - if err := addr.FromProtoMessage(ma); err != nil { - return getsvc.RangeHashPrm{}, fmt.Errorf("invalid object address: %w", err) - } - - cp, err := objutil.CommonPrmFromRequest(req) - if err != nil { - return getsvc.RangeHashPrm{}, err - } - - var p getsvc.RangeHashPrm - - switch t := body.GetType(); t { - default: - return getsvc.RangeHashPrm{}, fmt.Errorf("unknown checksum type %v", t) - case refs.ChecksumType_SHA256: - p.SetHashGenerator(sha256.New) - case refs.ChecksumType_TZ: - p.SetHashGenerator(tz.New) - } - - if tokV2 := cp.SessionTokenV2(); tokV2 != nil { - signerKey, err := ss.GetSessionV2PrivateKey(tokV2.Subjects()) - if err != nil { - if !errors.Is(err, apistatus.ErrSessionTokenNotFound) { - return getsvc.RangeHashPrm{}, fmt.Errorf("fetching session v2 key: %w", err) - } - cp.ForgetTokens() - signerKey = signer - } - p.WithCachedSignerKey(&signerKey) - } else if tok := cp.SessionToken(); tok != nil { - authUser, err := tok.AuthUser() - if err != nil { - return getsvc.RangeHashPrm{}, fmt.Errorf("getting auth user from token: %w", err) - } - signerKey, err := ss.GetSessionPrivateKey(authUser) - if err != nil { - if !errors.Is(err, apistatus.ErrSessionTokenNotFound) { - return getsvc.RangeHashPrm{}, fmt.Errorf("fetching session key: %w", err) - } - cp.ForgetTokens() - signerKey = signer - } - p.WithCachedSignerKey(&signerKey) - } - - mr := body.GetRanges() - rngs := make([]object.Range, len(mr)) - for i := range mr { - rngs[i].SetOffset(mr[i].Offset) - rngs[i].SetLength(mr[i].Length) - } - - p.SetCommonParameters(cp) - p.WithAddress(addr) - p.WithContainer(cnr) - p.SetRangeList(rngs) - p.SetSalt(body.GetSalt()) - - if cp.LocalOnly() { - return p, nil - } - - var onceResign sync.Once - meta := req.GetMetaHeader() - if meta == nil { - return getsvc.RangeHashPrm{}, errors.New("missing meta header") - } - p.SetRangeHashRequestForwarder(func(ctx context.Context, c client.MultiAddressClient) ([][]byte, error) { - var err error - onceResign.Do(func() { - req.MetaHeader = &protosession.RequestMetaHeader{ - // TODO: #1165 think how to set the other fields - Version: newCurrentProtoVersionMessage(), - Ttl: meta.GetTtl() - 1, - Origin: meta, - } - req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer(neofsecdsa.Signer(signer), req, nil) - }) - if err != nil { - return nil, err - } - - var hs [][]byte - return hs, c.ForAnyGRPCConn(ctx, func(ctx context.Context, conn *grpc.ClientConn) error { - var err error - hs, err = getHashesFromRemoteNode(ctx, conn, req) - return err // TODO: log error - }) - }) - return p, nil -} - -func getHashesFromRemoteNode(ctx context.Context, conn *grpc.ClientConn, req *protoobject.GetRangeHashRequest) ([][]byte, error) { - resp, err := protoobject.NewObjectServiceClient(conn).GetRangeHash(ctx, req) - if err != nil { - return nil, fmt.Errorf("GetRangeHash rpc failure: %w", err) - } - - if err := checkStatus(resp.GetMetaHeader().GetStatus()); err != nil { - return nil, err - } - // TODO: verify number of hashes - return resp.GetBody().GetHashList(), nil +// GetRangeHash is deprecated and no longer supported by the node. +func (s *Server) GetRangeHash(_ context.Context, req *protoobject.GetRangeHashRequest) (*protoobject.GetRangeHashResponse, error) { + var err apistatus.BadRequest + err.SetMessage("GetRangeHash operation is no longer supported") + return s.makeStatusHashResponse(&err, req), nil } func (s *Server) sendGetResponse(stream protoobject.ObjectService_GetServer, resp *protoobject.GetResponse, sign bool) error { diff --git a/pkg/services/object/server_test.go b/pkg/services/object/server_test.go index 0ef9805384..9c66ae890d 100644 --- a/pkg/services/object/server_test.go +++ b/pkg/services/object/server_test.go @@ -85,10 +85,6 @@ func (x noCallObjectService) GetRange(context.Context, getsvc.RangePrm) error { panic("must not be called") } -func (x noCallObjectService) GetRangeHash(context.Context, getsvc.RangeHashPrm) (*getsvc.RangeHashRes, error) { - panic("must not be called") -} - type noCallTestFSChain struct{} func (*noCallTestFSChain) ForEachContainerNodePublicKeyInLastTwoEpochs(cid.ID, func([]byte) bool) error { @@ -141,9 +137,6 @@ func (noCallTestReqInfoExtractor) DeleteRequestToInfo(*protoobject.DeleteRequest func (noCallTestReqInfoExtractor) HeadRequestToInfo(*protoobject.HeadRequest) (v2.RequestInfo, error) { panic("must not be called") } -func (noCallTestReqInfoExtractor) HashRequestToInfo(*protoobject.GetRangeHashRequest) (v2.RequestInfo, error) { - panic("must not be called") -} func (noCallTestReqInfoExtractor) GetRequestToInfo(*protoobject.GetRequest) (v2.RequestInfo, error) { panic("must not be called") } @@ -180,9 +173,6 @@ func (nopReqInfoExtractor) DeleteRequestToInfo(*protoobject.DeleteRequest) (v2.R func (nopReqInfoExtractor) HeadRequestToInfo(*protoobject.HeadRequest) (v2.RequestInfo, error) { return v2.RequestInfo{}, nil } -func (nopReqInfoExtractor) HashRequestToInfo(*protoobject.GetRangeHashRequest) (v2.RequestInfo, error) { - return v2.RequestInfo{}, nil -} func (nopReqInfoExtractor) GetRequestToInfo(*protoobject.GetRequest) (v2.RequestInfo, error) { return v2.RequestInfo{}, nil } @@ -805,10 +795,6 @@ func (unimplementedConn) ObjectRangeInit(context.Context, cid.ID, oid.ID, uint64 panic("unimplemented") } -func (unimplementedConn) ObjectHash(context.Context, cid.ID, oid.ID, user.Signer, client.PrmObjectHash) ([][]byte, error) { - panic("unimplemented") -} - func (unimplementedConn) AnnounceLocalTrust(context.Context, uint64, []reputation.Trust, client.PrmAnnounceLocalTrust) error { panic("unimplemented") } From 26b400a28dcfab1e00bcf74b81ab385a94fe7e97 Mon Sep 17 00:00:00 2001 From: Andrey Butusov Date: Wed, 13 May 2026 20:44:06 +0300 Subject: [PATCH 3/3] object: support ranged get Allow using payload ranges with object GET. Keep object range as a separate operation for compatibility. Closes #3977. Signed-off-by: Andrey Butusov --- CHANGELOG.md | 4 ++ cmd/neofs-cli/modules/object/get.go | 18 ++++++- cmd/neofs-cli/modules/object/range.go | 7 +++ docs/cli-commands/neofs-cli_object_get.md | 1 + pkg/network/cache/clients.go | 1 + pkg/services/object/get/exec.go | 16 +++++- pkg/services/object/get/get.go | 46 +++++++++++++---- pkg/services/object/get/prm.go | 23 +++++++++ pkg/services/object/get/util.go | 61 ++++++++++++++++++++--- pkg/services/object/server.go | 39 +++++++++++++-- 10 files changed, 193 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d0ef2b6b6..255e83cfda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Changelog for NeoFS Node - Optimized netmap caching in node (#3966) - Store in metabase associated object ID in bytes instead of Base58 (#3971) - Optimized local RANGE request execution (#3967) +- GET now supports payload ranges (#3991) ### Removed - `policer.max_workers` configuration (#3920) @@ -64,6 +65,9 @@ counters, this can take a while for shards with high object numbers. Storage nodes no longer implement deprecated `GetRangeHash` method and `neofs-cli object hash` command has been removed. +GET now supports payload ranges. Migrate from deprecated RANGE requests to GET +with range parameters. + ## [0.52.0] - 2026-03-27 - Woodo Delivering performance optimizations and initial placement feature this diff --git a/cmd/neofs-cli/modules/object/get.go b/cmd/neofs-cli/modules/object/get.go index 497d3ae548..42ce5eb7a9 100644 --- a/cmd/neofs-cli/modules/object/get.go +++ b/cmd/neofs-cli/modules/object/get.go @@ -38,6 +38,7 @@ func initObjectGetCmd() { _ = objectGetCmd.MarkFlagRequired(commonflags.OIDFlag) flags.String(fileFlag, "", "File to write object payload to(with -b together with signature and header). Default: stdout.") + flags.String(rangeFlag, "", rangeFlagUsage) flags.Bool(rawFlag, false, rawFlagDesc) flags.Bool(noProgressFlag, false, "Do not show progress bar") flags.Bool(binaryFlag, false, "Serialize whole object structure into given file(id + signature + header + payload).") @@ -67,6 +68,14 @@ func getObject(cmd *cobra.Command, _ []string) error { out = f } + ranges, err := getRangeList(cmd) + if err != nil { + return err + } + if len(ranges) > 1 { + return fmt.Errorf("at most one range can be specified, got: %d", len(ranges)) + } + pk, err := key.GetOrGenerate(cmd) if err != nil { return err @@ -100,6 +109,13 @@ func getObject(cmd *cobra.Command, _ []string) error { noProgress, _ := cmd.Flags().GetBool(noProgressFlag) binary, _ := cmd.Flags().GetBool(binaryFlag) + if len(ranges) != 0 { + if binary { + return fmt.Errorf("--%s cannot be used with --%s", binaryFlag, rangeFlag) + } + prm.SetRange(ranges[0].GetOffset(), ranges[0].GetLength()) + prm.MarkPayloadOnly() + } hdr, rdr, err := cli.ObjectGetInit(ctx, cnr, obj, user.NewAutoIDSigner(*pk), prm) if err == nil { @@ -141,7 +157,7 @@ func getObject(cmd *cobra.Command, _ []string) error { } // Print header only if file is not streamed to stdout. - if filename != "" { + if filename != "" && len(ranges) == 0 { err = printHeader(cmd, &hdr) if err != nil { return err diff --git a/cmd/neofs-cli/modules/object/range.go b/cmd/neofs-cli/modules/object/range.go index 0d2b0507be..327b4bad9b 100644 --- a/cmd/neofs-cli/modules/object/range.go +++ b/cmd/neofs-cli/modules/object/range.go @@ -20,6 +20,12 @@ import ( "github.com/spf13/cobra" ) +const ( + rangeSep = ":" + rangeFlag = "range" + rangeFlagUsage = "Range to take data from in the form offset:length" +) + var objectRangeCmd = &cobra.Command{ Use: "range", Short: "Get payload range data of an object", @@ -108,6 +114,7 @@ func getObjectRange(cmd *cobra.Command, _ []string) error { prm.MarkRaw() } + //nolint:staticcheck rdr, err := cli.ObjectRangeInit(ctx, cnr, obj, ranges[0].GetOffset(), ranges[0].GetLength(), user.NewAutoIDSigner(*pk), prm) if err != nil { err = fmt.Errorf("init payload reading: %w", err) diff --git a/docs/cli-commands/neofs-cli_object_get.md b/docs/cli-commands/neofs-cli_object_get.md index 1a5c7c01a9..bfb5ded215 100644 --- a/docs/cli-commands/neofs-cli_object_get.md +++ b/docs/cli-commands/neofs-cli_object_get.md @@ -22,6 +22,7 @@ neofs-cli object get [flags] -h, --help help for get --no-progress Do not show progress bar --oid string Object ID. + --range string Range to take data from in the form offset:length --raw Set raw request option -r, --rpc-endpoint string Remote node address (as 'multiaddr' or ':') --session string Filepath to a JSON- or binary-encoded token of the object GET session diff --git a/pkg/network/cache/clients.go b/pkg/network/cache/clients.go index 67690d102b..f17afdcac0 100644 --- a/pkg/network/cache/clients.go +++ b/pkg/network/cache/clients.go @@ -372,6 +372,7 @@ func (x *connections) ObjectRangeInit(ctx context.Context, cnr cid.ID, id oid.ID var res *client.ObjectRangeReader return res, x.forAny(ctx, func(ctx context.Context, c *client.Client) error { var err error + //nolint:staticcheck res, err = c.ObjectRangeInit(ctx, cnr, id, off, ln, signer, opts) return err }) diff --git a/pkg/services/object/get/exec.go b/pkg/services/object/get/exec.go index 41054bda4f..2b5a8aeb02 100644 --- a/pkg/services/object/get/exec.go +++ b/pkg/services/object/get/exec.go @@ -73,6 +73,9 @@ type execCtx struct { localRangeBuffer []byte submitLocalRangeStreamFn SubmitDataStreamFunc + + payloadOnly bool + legacyRange bool } type execOption func(*execCtx) @@ -99,6 +102,17 @@ func withPayloadRange(r *object.Range) execOption { } } +func withPayloadOnly(v bool) execOption { + return func(c *execCtx) { + c.payloadOnly = v + } +} + +func withLegacyRange(v bool) execOption { + return func(c *execCtx) { + c.legacyRange = v + } +} func withLogger(l *zap.Logger) execOption { return func(ctx *execCtx) { @@ -349,7 +363,7 @@ func mergeSplitInfo(dst, src *object.SplitInfo) { } func (exec *execCtx) writeCollectedHeader() bool { - if exec.ctxRange() != nil { + if exec.payloadOnly { return true } diff --git a/pkg/services/object/get/get.go b/pkg/services/object/get/get.go index 93945f2820..acb6baf1f9 100644 --- a/pkg/services/object/get/get.go +++ b/pkg/services/object/get/get.go @@ -4,10 +4,8 @@ import ( "context" "errors" "fmt" - "slices" iec "github.com/nspcc-dev/neofs-node/internal/ec" - "github.com/nspcc-dev/neofs-node/pkg/util" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -25,6 +23,17 @@ func (s *Service) Get(ctx context.Context, prm Prm) error { if pi.RuleIndex >= 0 { // TODO: deny if node is not in the container? + if prm.rng != nil { + if prm.payloadOnly && prm.localGetBuffer != nil { + stream, err := s.localObjects.ReadECPartRange(prm.addr.Container(), prm.addr.Object(), pi, prm.rng.GetOffset(), prm.rng.GetLength(), prm.localGetBuffer) + if err == nil { + prm.submitLocalGetStreamFn(0, stream) + } + return err + } + return s.copyLocalECPartRange(prm.objWriter, prm.addr.Container(), prm.addr.Object(), pi, prm.rng.GetOffset(), prm.rng.GetLength()) + } + if prm.localGetBuffer != nil { n, stream, err := s.localObjects.ReadECPart(prm.addr.Container(), prm.addr.Object(), pi, prm.localGetBuffer) if err == nil { @@ -39,8 +48,11 @@ func (s *Service) Get(ctx context.Context, prm Prm) error { if prm.common.LocalOnly() && len(prm.container.PlacementPolicy().ECRules()) == 0 && // EC breaks TTL requirements currently. len(prm.container.PlacementPolicy().Replicas()) != 0 { - bufOpt := withLocalGetBuffer(prm.localGetBuffer, prm.submitLocalGetStreamFn) - return s.get(ctx, prm.commonPrm, bufOpt).err // It handles locality internally. + opts := []execOption{withPayloadRange(prm.rng), withPayloadOnly(prm.payloadOnly)} + if prm.rng == nil && !prm.payloadOnly { + opts = append(opts, withLocalGetBuffer(prm.localGetBuffer, prm.submitLocalGetStreamFn)) + } + return s.get(ctx, prm.commonPrm, opts...).err // It handles locality internally. } nodeLists, repRules, ecRules, err := s.neoFSNet.GetNodesForObject(prm.addr) @@ -49,9 +61,16 @@ func (s *Service) Get(ctx context.Context, prm Prm) error { } if len(repRules) > 0 { // REP format does not require encoding - bufOpt := withLocalGetBuffer(prm.localGetBuffer, prm.submitLocalGetStreamFn) - forwardOpt := withForwardGetRequestFunc(prm.forwardRequestFn) - err := s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(nodeLists[:len(repRules)], repRules), bufOpt, forwardOpt).err + opts := []execOption{ + withPreSortedContainerNodes(nodeLists[:len(repRules)], repRules), + withPayloadRange(prm.rng), + withPayloadOnly(prm.payloadOnly), + withForwardGetRequestFunc(prm.forwardRequestFn), + } + if prm.rng == nil && !prm.payloadOnly { + opts = append(opts, withLocalGetBuffer(prm.localGetBuffer, prm.submitLocalGetStreamFn)) + } + err := s.get(ctx, prm.commonPrm, opts...).err if len(ecRules) == 0 || !errors.Is(err, apistatus.ErrObjectNotFound) { return err } @@ -67,7 +86,12 @@ func (s *Service) Get(ctx context.Context, prm Prm) error { for i := range ecRules { repRules[i] = uint(ecRules[i].DataPartNum + ecRules[i].ParityPartNum) } - return s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(ecNodeLists, repRules)).err + return s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(ecNodeLists, repRules), withPayloadRange(prm.rng), withPayloadOnly(prm.payloadOnly)).err + } + + if prm.rng != nil { + return s.copyECObjectRange(ctx, prm.objWriter, prm.addr.Container(), prm.addr.Object(), prm.common.SessionToken(), + ecRules, ecNodeLists, prm.rng.GetOffset(), prm.rng.GetLength()) } return s.copyECObject(ctx, prm.addr.Container(), prm.addr.Object(), prm.common.SessionToken(), @@ -101,7 +125,7 @@ func (s *Service) GetRange(ctx context.Context, prm RangePrm) error { len(prm.container.PlacementPolicy().Replicas()) != 0 { // It handles locality internally. bufOpt := withLocalRangeBuffer(prm.localBuffer, prm.submitLocalStreamFn) - return s.get(ctx, prm.commonPrm, withPayloadRange(prm.rng), bufOpt).err + return s.get(ctx, prm.commonPrm, withPayloadRange(prm.rng), withPayloadOnly(true), withLegacyRange(true), bufOpt).err } nodeLists, repRules, ecRules, err := s.neoFSNet.GetNodesForObject(prm.addr) @@ -116,7 +140,7 @@ func (s *Service) getRange(ctx context.Context, prm RangePrm, nodeLists [][]netm if len(repRules) > 0 { // REP format does not require encoding bufOpt := withLocalRangeBuffer(prm.localBuffer, prm.submitLocalStreamFn) forwardOpt := withForwardRangeRequestFunc(prm.forwardRequestFn) - err := s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(nodeLists[:len(repRules)], repRules), withPayloadRange(prm.rng), withHash(hashPrm), bufOpt, forwardOpt).err + err := s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(nodeLists[:len(repRules)], repRules), withPayloadRange(prm.rng), withPayloadOnly(true), withLegacyRange(true), bufOpt, forwardOpt).err if len(ecRules) == 0 || !errors.Is(err, apistatus.ErrObjectNotFound) { return err } @@ -132,7 +156,7 @@ func (s *Service) getRange(ctx context.Context, prm RangePrm, nodeLists [][]netm for i := range ecRules { repRules[i] = uint(ecRules[i].DataPartNum + ecRules[i].ParityPartNum) } - return s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(ecNodeLists, repRules), withPayloadRange(prm.rng)).err + return s.get(ctx, prm.commonPrm, withPreSortedContainerNodes(ecNodeLists, repRules), withPayloadRange(prm.rng), withPayloadOnly(true), withLegacyRange(true)).err } return s.copyECObjectRange(ctx, prm.objWriter, prm.addr.Container(), prm.addr.Object(), prm.common.SessionToken(), diff --git a/pkg/services/object/get/prm.go b/pkg/services/object/get/prm.go index 8496aec597..55f06976ec 100644 --- a/pkg/services/object/get/prm.go +++ b/pkg/services/object/get/prm.go @@ -25,6 +25,9 @@ type SubmitDataStreamFunc = func(io.ReadCloser) type Prm struct { commonPrm + rng *object.Range + payloadOnly bool + localGetBuffer []byte submitLocalGetStreamFn SubmitStreamFunc @@ -106,6 +109,16 @@ func (p *Prm) SetObjectWriter(w ObjectWriter) { p.objWriter = w } +// SetRange sets range of the requested payload data. +func (p *Prm) SetRange(rng *object.Range) { + p.rng = rng +} + +// MarkPayloadOnly requests payload without an object header. +func (p *Prm) MarkPayloadOnly() { + p.payloadOnly = true +} + // SetChunkWriter sets target component to write the object payload range. func (p *RangePrm) SetChunkWriter(w ChunkWriter) { p.objWriter = &partWriter{ @@ -173,6 +186,16 @@ func (p Prm) GetBuffer() ([]byte, SubmitStreamFunc) { return p.localGetBuffer, p.submitLocalGetStreamFn } +// Range returns payload range settings. +func (p Prm) Range() *object.Range { + return p.rng +} + +// PayloadOnly reports whether only payload was requested. +func (p Prm) PayloadOnly() bool { + return p.payloadOnly +} + // SetRequestForwarder specifies request transport callback to use for receiving // response from remote node. // diff --git a/pkg/services/object/get/util.go b/pkg/services/object/get/util.go index bba295bc82..1d3929013e 100644 --- a/pkg/services/object/get/util.go +++ b/pkg/services/object/get/util.go @@ -230,14 +230,42 @@ func (c *clientWrapper) getObject(exec *execCtx) (*object.Object, io.ReadCloser, if rng := exec.ctxRange(); rng != nil { addr := exec.address() id := addr.Object() - ln := rng.GetLength() - var opts client.PrmObjectRange + if exec.legacyRange { + ln := rng.GetLength() + + var opts client.PrmObjectRange + if exec.prm.common.TTL() < 2 { + opts.MarkLocal() + } + if stV2 := exec.prm.common.SessionTokenV2(); stV2 != nil { + if stV2.AssertVerb(sessionv2.VerbObjectRange, addr.Container()) { + opts.WithinSessionV2(*stV2) + } + } else if st := exec.prm.common.SessionToken(); st != nil && st.AssertObject(id) { + opts.WithinSession(*st) + } + if bt := exec.prm.common.BearerToken(); bt != nil { + opts.WithBearerToken(*bt) + } + opts.WithXHeaders(exec.prm.common.XHeaders()...) + if exec.isRaw() { + opts.MarkRaw() + } + + rdr, err := c.client.ObjectRangeInit(exec.context(), addr.Container(), id, rng.GetOffset(), ln, user.NewAutoIDSigner(*key), opts) + if err != nil { + return nil, nil, fmt.Errorf("init payload reading: %w", err) + } + return nil, newFallbackRangeReader(exec, c, key, rng, rdr), nil + } + + var opts client.PrmObjectGet if exec.prm.common.TTL() < 2 { opts.MarkLocal() } if stV2 := exec.prm.common.SessionTokenV2(); stV2 != nil { - if stV2.AssertVerb(sessionv2.VerbObjectRange, addr.Container()) { + if stV2.AssertVerb(sessionv2.VerbObjectGet, addr.Container()) { opts.WithinSessionV2(*stV2) } } else if st := exec.prm.common.SessionToken(); st != nil && st.AssertObject(id) { @@ -250,13 +278,19 @@ func (c *clientWrapper) getObject(exec *execCtx) (*object.Object, io.ReadCloser, if exec.isRaw() { opts.MarkRaw() } + opts.SetRange(rng.GetOffset(), rng.GetLength()) + if exec.payloadOnly { + opts.MarkPayloadOnly() + } - rdr, err := c.client.ObjectRangeInit(exec.context(), addr.Container(), id, rng.GetOffset(), ln, user.NewAutoIDSigner(*key), opts) + hdr, rdr, err := c.client.ObjectGetInit(exec.context(), addr.Container(), id, user.NewAutoIDSigner(*key), opts) if err != nil { return nil, nil, fmt.Errorf("init payload reading: %w", err) } - // fallback to full GET in case of access denial error. - return nil, newFallbackRangeReader(exec, c, key, rng, rdr), nil + if exec.payloadOnly { + return nil, newFallbackRangeReader(exec, c, key, rng, rdr), nil + } + return &hdr, newFallbackRangeReader(exec, c, key, rng, rdr), nil } return c.get(exec, key) @@ -284,6 +318,9 @@ func (c *clientWrapper) get(exec *execCtx, key *ecdsa.PrivateKey) (*object.Objec if exec.isRaw() { opts.MarkRaw() } + if exec.payloadOnly { + opts.MarkPayloadOnly() + } hdr, rdr, err := c.client.ObjectGetInit(exec.context(), addr.Container(), id, user.NewAutoIDSigner(*key), opts) if err != nil { @@ -311,7 +348,17 @@ func (e *storageEngineWrapper) get(exec *execCtx) (*object.Object, io.ReadCloser return nil, nil, err } r, err := e.engine.GetRangeStream(exec.address(), rng.GetOffset(), rng.GetLength()) - return nil, r, err + if err != nil || exec.payloadOnly { + return nil, r, err + } + h, hErr := e.engine.Head(exec.address(), exec.isRaw()) + if hErr != nil { + if r != nil { + _ = r.Close() + } + return nil, nil, hErr + } + return h, r, nil } if exec.localGetBuffer != nil { diff --git a/pkg/services/object/server.go b/pkg/services/object/server.go index 5e63b1d923..72c73084f3 100644 --- a/pkg/services/object/server.go +++ b/pkg/services/object/server.go @@ -868,9 +868,14 @@ type getStream struct { recheckEACL bool signResponse bool + payloadOnly bool } func (s *getStream) WriteHeader(hdr *object.Object) error { + if s.payloadOnly { + return nil + } + mo := hdr.ProtoMessage() resp := &protoobject.GetResponse{ Body: &protoobject.GetResponse_Body{ @@ -953,6 +958,7 @@ func (s *Server) Get(req *protoobject.GetRequest, gStream protoobject.ObjectServ reqInfo: reqInfo, recheckEACL: recheckEACL, signResponse: needSignResp, + payloadOnly: req.GetBody().GetPayloadOnly(), }) if err != nil { if !errors.Is(err, apistatus.Error) { @@ -967,8 +973,12 @@ func (s *Server) Get(req *protoobject.GetRequest, gStream protoobject.ObjectServ // We could acquire ~256K buffer (like for chunks) if storage would try to read it full. // Then small objects would fit into a single buffer, and for large ones it'd be possible to // encode the first chunk response using the heading buffer. - hdrRespBuf, hdrBuf := getBufferForHeadResponse() - defer hdrRespBuf.Free() + var hdrRespBuf *iprotobuf.MemBuffer + var hdrBuf []byte + if p.Range() == nil && !p.PayloadOnly() { + hdrRespBuf, hdrBuf = getBufferForHeadResponse() + defer hdrRespBuf.Free() + } hdrLen := -1 var stream io.ReadCloser @@ -978,7 +988,9 @@ func (s *Server) Get(req *protoobject.GetRequest, gStream protoobject.ObjectServ } }() - p.WithBuffer(hdrBuf, func(ln int, s io.ReadCloser) { hdrLen, stream = ln, s }) + if hdrBuf != nil { + p.WithBuffer(hdrBuf, func(ln int, s io.ReadCloser) { hdrLen, stream = ln, s }) + } err = s.handlers.Get(gStream.Context(), p) if err != nil { @@ -1128,6 +1140,18 @@ func convertGetPrm(signer ecdsa.PrivateKey, cnr container.Container, req *protoo return getsvc.Prm{}, fmt.Errorf("invalid object address: %w", err) } + rng := body.GetRange() + if rng != nil { + rln := rng.GetLength() + if rln == 0 { + if rng.GetOffset() != 0 { + return getsvc.Prm{}, errors.New("zero range length") + } + } else if rng.GetOffset()+rln <= rng.GetOffset() { + return getsvc.Prm{}, errors.New("range overflow") + } + } + cp, err := objutil.CommonPrmFromRequest(req) if err != nil { return getsvc.Prm{}, err @@ -1139,6 +1163,15 @@ func convertGetPrm(signer ecdsa.PrivateKey, cnr container.Container, req *protoo p.WithContainer(cnr) p.WithRawFlag(body.Raw) p.SetObjectWriter(stream) + if rng != nil { + var objRng object.Range + objRng.SetOffset(rng.GetOffset()) + objRng.SetLength(rng.GetLength()) + p.SetRange(&objRng) + } + if body.GetPayloadOnly() { + p.MarkPayloadOnly() + } if cp.LocalOnly() { return p, nil }