Compare commits

...

68 commits

Author SHA1 Message Date
6353df8bca [#142] Fix unwrapErr for go 1.20
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
2023-08-05 15:58:25 +00:00
936e6d230b [#121] pool: Add wait params validation for containerPut method
* Add WaitParams.CheckValidity() check because SetWaitParams is deprecated,
  but parameters were checked within this setter with checkForPositive()

Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-05 15:58:14 +00:00
be28b89312 [#121] pool: Make PrmContainerDelete fields public
* Refactor client PrmContainerDelete usage
* Introduce WaitParams CheckValidity method

Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-05 15:58:14 +00:00
9e5faaf829 [#121] client: Make PrmContainerDelete fields public
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-05 15:58:14 +00:00
3dc8129ed7 [#135] Make all error status receivers pointers
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
2023-08-04 08:35:01 +00:00
55c52c8d5d [#121] pool: Make PrmContainerGet fields public
* Also refactor client PrmContainerGet usage

Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-03 10:54:13 +03:00
d376302a3b [#121] client: Make PrmContainerGet fields public
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-02 18:23:32 +03:00
363f153eaf [#136] pool: Set order field to get subtree
With new revision of tree service protocol, getSubTree
requires to use explicit order field.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-08-02 10:32:37 +00:00
95b987b818 [#137] go.mod: Update api-go
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-08-02 12:04:10 +03:00
13d0b170d2 [#121] client: Make pool PrmContainerPut struct fields public
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-01 09:59:57 +00:00
18a9e4bceb [#121] client: Make PrmContainerPut struct fields public
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-08-01 09:59:57 +00:00
0fe0d71678 [#133] .forgejo: Add names to actions
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-07-31 11:33:39 +00:00
78d1439b2c [#133] go.mod: Update api-go
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-07-31 11:33:39 +00:00
0886d80083 [#69] Add close for nns.Dial
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2023-07-31 11:05:26 +03:00
ecb1fef78c [#129] client: Do not override error status WriteObject()
Here is a scenario:
1. `resolveFrostFSErrors` is false.
2. The first object part was not written, which was signified in status.
3. The second part was written correctly.

Client now thinks that the object is written even though it was not.
In theory we could also return only status, but client-side splitting
is not a single RPC, so it makes sense.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-07-26 14:38:43 +03:00
5defed4ab4 [#126] go.mod: Update frostfs-api-go package version
Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-07-19 16:03:56 +03:00
fb05f7dc5e [#112] Add basic documentation for placement policies
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
2023-07-18 10:53:54 +00:00
b91f9d8c79 [#xx] Add support for SELECT-FILTER expressions
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
2023-07-17 10:29:48 +00:00
b9afe7a2f9 [#42] Add ResolveContractHash method
Signed-off-by: Pavel Pogodaev <p.pogodaev@yadro.com>
2023-07-12 22:22:58 +03:00
998fe1a7ab [#102] netmap: properly process multiple REP
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-07-11 14:21:35 +00:00
c359a7465a [#64] transformer: Simplify interface
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-07-11 07:33:12 +00:00
d70ef2187b [#97] Add a method IterateUserAttributes in Container
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-07-10 09:05:01 +00:00
ac95b87e7c [#101] Add Equals for Address
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-07-10 09:05:01 +00:00
863be6034f [#104] Update neo-go/pkg/interop version
neo-go module uses broken commit of interop package.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-07-10 09:04:10 +00:00
35346a01c9 [#109] Bump neo-go version
Synced version with frostfs-node, see frostfs-node#417

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-07-10 09:04:10 +00:00
fe35373d8f [#107] go.mod: Tidy dependencies
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-07-07 14:57:16 +03:00
388d1ca1de [#107] pool/tree: Support grpc schemas
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-07-07 14:57:13 +03:00
14ed3e177d [#104] nemtap: Escape special symbols in filters
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-07-07 09:33:00 +00:00
fe28c33277 [#104] netmap: Add test with quote escaping
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-07-07 09:33:00 +00:00
98cab7ed61 [#103] object: Add PutSingle method code
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-07-06 17:06:17 +03:00
37e22b33ad [#103] sdk-go: Update api-go version
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-07-05 18:10:42 +03:00
769f6eec05 [#105] client: Fix revive linter warning
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-07-05 15:52:06 +03:00
5d62cef27e [#98] Fix linter issues
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-06-28 12:13:02 +00:00
c0c0c588b5 [#98] Add forgejo workflows
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2023-06-28 12:13:02 +00:00
2f88460172 [#83] Allow to split objects in the client
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-06-28 12:12:37 +00:00
66cb5dcf34 [#83] Update .gitignore
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-06-28 12:12:37 +00:00
91e80ba743 [#73] pool/tree: Fix index in retry loop
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-28 13:06:23 +03:00
c243b443bc [#80] objectwriter: Allow custimize maxChunkLen
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-06-26 18:13:04 +03:00
aa8ffebc63 [#95] transformer: Set parent version
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-06-23 13:48:02 +03:00
9d40228cec [#73] pool/tree: Fix client healthy status
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 17:01:55 +03:00
af40dc68f0 [#84] pool/tree: Allow to pass gRPC dial options
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 15:55:07 +03:00
981d24a493 [#84] pool: Allow to pass gRPC dial options
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 15:54:48 +03:00
19adb4dffa [#73] pool/tree: Add tests
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 14:37:57 +03:00
51e022ab8c [#73] pool/tree: Add tree pool
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 14:37:57 +03:00
0d3dacb515 [#73] pool: Add getters for NodeParam
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 14:37:57 +03:00
b2e302624d [#73] pool/tree: Add proto tree service client
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-06-08 14:37:57 +03:00
fcbf96add6 [#76] Add UNIQUE keyword
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
2023-06-06 13:54:07 +03:00
4f48f6c9e0 [#78] netmap: Add new keywords NOT and UNIQUE
* Add the rule for NOT operation to the policy parser grammar
* Regenerate query parse
* Implement NOT in filter
* Add unit-tests

Signed-off-by: Airat Arifullin a.arifullin@yadro.com
2023-06-02 17:47:20 +03:00
ec59ebfd88 [#87] go.mod: Update hrw
```
                                                              │      old      │                 new                  │
                                                              │    sec/op     │    sec/op     vs base                │
Netmap_ContainerNodes/REP_2-8                                    13.07µ ± 12%   11.03µ ± 12%  -15.63% (p=0.003 n=10)
Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8   11.383µ ±  9%   9.970µ ± 19%  -12.42% (p=0.005 n=10)
geomean                                                          12.20µ         10.49µ        -14.04%

                                                              │      old      │                 new                  │
                                                              │     B/op      │     B/op      vs base                │
Netmap_ContainerNodes/REP_2-8                                   10.203Ki ± 0%   7.711Ki ± 0%  -24.43% (p=0.000 n=10)
Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8    9.641Ki ± 0%   7.148Ki ± 0%  -25.85% (p=0.000 n=10)
geomean                                                          9.918Ki        7.424Ki       -25.14%

                                                              │    old     │                new                 │
                                                              │ allocs/op  │ allocs/op   vs base                │
Netmap_ContainerNodes/REP_2-8                                   216.0 ± 0%   131.0 ± 0%  -39.35% (p=0.000 n=10)
Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8   215.0 ± 0%   130.0 ± 0%  -39.53% (p=0.000 n=10)
geomean                                                         215.5        130.5       -39.44%
```

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-06-02 14:54:40 +03:00
030ff2f122 [#87] netmap: Add benchmark for ContainerNodes()
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-06-02 14:54:40 +03:00
0f7455ff7a [#75] Update antlr4 version to 4.13.0
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-06-01 13:15:11 +00:00
e6b662cfa6 [#75] Add make targets policy and docker/%
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-06-01 13:15:11 +00:00
406c2324d4 [#85] go.mod: Tidy
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-06-01 10:02:23 +03:00
10482ffbed [#82] client: Allow to pass gRPC dial options
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
2023-05-30 16:51:22 +03:00
f5b23eb225 [#74] netmap/parser: Update antlr4 generator
Also, add -no-listener option, as we use visitor only.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-19 17:47:24 +03:00
70f23dd1ea [#74] pre-commit: Exclude auto-generated files
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-19 17:47:24 +03:00
57f874048b [#74] go.mod: Update antlr4 dependency
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-19 17:47:24 +03:00
a397d1fd15 [#74] go.mod: Update dependencies
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-19 17:47:23 +03:00
9803c2816a [#74] pool: move to sync/atomic
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-19 14:58:30 +03:00
d04d96b42e [#74] go.mod: Move to go1.19
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-19 14:53:47 +03:00
9a072a8f49 [#68] Replace interface{} with any
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
2023-05-15 17:21:49 +03:00
15b4287092 [#49] bearer: Allow empty eacl if token is impersonated
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-05-05 12:45:39 +03:00
d4fe9a193d [#66] transformer: Accept constructor in NextTarget
The code of frostfs-node is not yet ready to reuse egress target for
multiple objects, let's postpone until #64.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-03 11:22:09 +03:00
c42a6119ff [#66] transformer: Extend tests
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-03 11:22:09 +03:00
29b188db57 [#52] Remove storage groups and audit
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-05-03 08:20:37 +00:00
38b03ff28b [#63] transformer: Resolve funlen linter
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-04-25 13:29:42 +03:00
0fa23a9b14 [#63] transformer: Add context to methods
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-04-25 13:29:42 +03:00
d0762d037d [#44] pool: Add copies number vector when putting object
Signed-off-by: Artem Tataurov <a.tataurov@yadro.com>
2023-04-20 13:41:05 +03:00
93 changed files with 9029 additions and 3205 deletions

View file

@ -0,0 +1,21 @@
name: DCO
on: [pull_request]
jobs:
dco:
name: DCO
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
- name: Run commit format checker
uses: https://git.alexvan.in/alexvanin/dco-go@v1
with:
from: 406c2324

View file

@ -0,0 +1,32 @@
name: Tests and linters
on: [pull_request]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: golangci-lint
uses: https://github.com/golangci/golangci-lint-action@v2
with:
version: latest
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.19', '1.20' ]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.go_versions }}'
- name: Run tests
run: make test

View file

@ -1,21 +0,0 @@
name: DCO check
on:
pull_request:
branches:
- master
jobs:
commits_check_job:
runs-on: ubuntu-latest
name: Commits Check
steps:
- name: Get PR Commits
id: 'get-pr-commits'
uses: tim-actions/get-pr-commits@master
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: DCO Check
uses: tim-actions/dco@master
with:
commits: ${{ steps.get-pr-commits.outputs.commits }}

View file

@ -1,52 +0,0 @@
name: neofs-sdk-go tests
on:
pull_request:
branches:
- master
types: [opened, synchronize]
paths-ignore:
- '**/*.md'
workflow_dispatch:
jobs:
tests:
name: Tests
runs-on: ubuntu-20.04
strategy:
matrix:
go_versions: [ '1.18.x', '1.19.x', '1.20.x' ]
fail-fast: false
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.go_versions }}'
- name: Restore Go modules from cache
uses: actions/cache@v3
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}
- name: Update Go modules
run: make dep
- name: Run tests
run: make test
lint:
runs-on: ubuntu-20.04
steps:
- name: Check out code
uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
only-new-issues: true

6
.gitignore vendored
View file

@ -21,3 +21,9 @@ vendor/
# coverage # coverage
coverage.txt coverage.txt
coverage.html coverage.html
# antlr tool jar
antlr-*.jar
# tempfiles
.cache

View file

@ -16,7 +16,7 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
args: [--markdown-linebreak-ext=md] args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: ".key$" exclude: "(.key|.interp|.tokens)$"
- repo: https://github.com/golangci/golangci-lint - repo: https://github.com/golangci/golangci-lint
rev: v1.51.2 rev: v1.51.2

4
Dockerfile Normal file
View file

@ -0,0 +1,4 @@
FROM golang:1.19
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install make openjdk-11-jre -y
WORKDIR /work

View file

@ -1,5 +1,7 @@
#!/usr/bin/make -f #!/usr/bin/make -f
ANTLR_VERSION="4.13.0"
# Run tests # Run tests
test: test:
@go test ./... -cover @go test ./... -cover
@ -29,6 +31,23 @@ format:
@echo "⇒ Processing goimports check" @echo "⇒ Processing goimports check"
@goimports -w ./ @goimports -w ./
policy:
@wget -q https://www.antlr.org/download/antlr-${ANTLR_VERSION}-complete.jar -O antlr4-tool.jar
@java -Xmx500M -cp "`pwd`/antlr4-tool.jar" "org.antlr.v4.Tool" -o `pwd`/netmap/parser/ -Dlanguage=Go -no-listener -visitor `pwd`/netmap/parser/Query.g4 `pwd`/netmap/parser/QueryLexer.g4
# Run `make %` in truecloudlab/frostfs-sdk-go container(Golang+Java)
docker/%:
@docker build -t truecloudlab/frostfs-sdk-go --platform linux/amd64 . > /dev/null
@docker run --rm -t \
-v `pwd`:/work \
-u "$$(id -u):$$(id -g)" \
--env HOME=/work \
truecloudlab/frostfs-sdk-go make $*
# Synchronize tree service
sync-tree:
@./syncTree.sh
# Show this help prompt # Show this help prompt
help: help:
@echo ' Usage:' @echo ' Usage:'

View file

@ -1,26 +0,0 @@
/*
Package audit provides features to process data audit in FrostFS system.
Result type groups values which can be gathered during data audit process:
var res audit.Result
res.ForEpoch(32)
res.ForContainer(cnr)
// ...
res.Complete()
Result instances can be stored in a binary format. On reporter side:
data := res.Marshal()
// send data
On receiver side:
var res audit.Result
err := res.Unmarshal(data)
// ...
Using package types in an application is recommended to potentially work with
different protocol versions with which these types are compatible.
*/
package audit

View file

@ -1,377 +0,0 @@
package audit
import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/audit"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
)
// Result represents report on the results of the data audit in FrostFS system.
//
// Result is mutually binary-compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/audit.DataAuditResult
// message. See Marshal / Unmarshal methods.
//
// Instances can be created using built-in var declaration.
type Result struct {
versionEncoded bool
v2 audit.DataAuditResult
}
// Marshal encodes Result into a canonical FrostFS binary format (Protocol Buffers
// with direct field order).
//
// Writes version.Current() protocol version into the resulting message if Result
// hasn't been already decoded from such a message using Unmarshal.
//
// See also Unmarshal.
func (r *Result) Marshal() []byte {
if !r.versionEncoded {
var verV2 refs.Version
version.Current().WriteToV2(&verV2)
r.v2.SetVersion(&verV2)
r.versionEncoded = true
}
return r.v2.StableMarshal(nil)
}
var errCIDNotSet = errors.New("container ID is not set")
// Unmarshal decodes Result from its canonical FrostFS binary format (Protocol Buffers
// with direct field order). Returns an error describing a format violation.
//
// See also Marshal.
func (r *Result) Unmarshal(data []byte) error {
err := r.v2.Unmarshal(data)
if err != nil {
return err
}
r.versionEncoded = true
// format checks
var cID cid.ID
cidV2 := r.v2.GetContainerID()
if cidV2 == nil {
return errCIDNotSet
}
err = cID.ReadFromV2(*cidV2)
if err != nil {
return fmt.Errorf("could not convert V2 container ID: %w", err)
}
var (
oID oid.ID
oidV2 refs.ObjectID
)
for _, oidV2 = range r.v2.GetPassSG() {
err = oID.ReadFromV2(oidV2)
if err != nil {
return fmt.Errorf("invalid passed storage group ID: %w", err)
}
}
for _, oidV2 = range r.v2.GetFailSG() {
err = oID.ReadFromV2(oidV2)
if err != nil {
return fmt.Errorf("invalid failed storage group ID: %w", err)
}
}
return nil
}
// Epoch returns FrostFS epoch when the data associated with the Result was audited.
//
// Zero Result has zero epoch.
//
// See also ForEpoch.
func (r Result) Epoch() uint64 {
return r.v2.GetAuditEpoch()
}
// ForEpoch specifies FrostFS epoch when the data associated with the Result was audited.
//
// See also Epoch.
func (r *Result) ForEpoch(epoch uint64) {
r.v2.SetAuditEpoch(epoch)
}
// Container returns identifier of the container with which the data audit Result
// is associated and a bool that indicates container ID field presence in the Result.
//
// Zero Result does not have container ID.
//
// See also ForContainer.
func (r Result) Container() (cid.ID, bool) {
var cID cid.ID
cidV2 := r.v2.GetContainerID()
if cidV2 != nil {
_ = cID.ReadFromV2(*cidV2)
return cID, true
}
return cID, false
}
// ForContainer sets identifier of the container with which the data audit Result
// is associated.
//
// See also Container.
func (r *Result) ForContainer(cnr cid.ID) {
var cidV2 refs.ContainerID
cnr.WriteToV2(&cidV2)
r.v2.SetContainerID(&cidV2)
}
// AuditorKey returns public key of the auditing FrostFS Inner Ring node in
// a FrostFS binary key format.
//
// Zero Result has nil key. Return value MUST NOT be mutated: to do this,
// first make a copy.
//
// See also SetAuditorPublicKey.
func (r Result) AuditorKey() []byte {
return r.v2.GetPublicKey()
}
// SetAuditorKey specifies public key of the auditing FrostFS Inner Ring node in
// a FrostFS binary key format.
//
// Argument MUST NOT be mutated at least until the end of using the Result.
//
// See also AuditorKey.
func (r *Result) SetAuditorKey(key []byte) {
r.v2.SetPublicKey(key)
}
// Completed returns completion state of the data audit associated with the Result.
//
// Zero Result corresponds to incomplete data audit.
//
// See also Complete.
func (r Result) Completed() bool {
return r.v2.GetComplete()
}
// Complete marks the data audit associated with the Result as completed.
//
// See also Completed.
func (r *Result) Complete() {
r.v2.SetComplete(true)
}
// RequestsPoR returns number of requests made by Proof-of-Retrievability
// audit check to get all headers of the objects inside storage groups.
//
// Zero Result has zero requests.
//
// See also SetRequestsPoR.
func (r Result) RequestsPoR() uint32 {
return r.v2.GetRequests()
}
// SetRequestsPoR sets number of requests made by Proof-of-Retrievability
// audit check to get all headers of the objects inside storage groups.
//
// See also RequestsPoR.
func (r *Result) SetRequestsPoR(v uint32) {
r.v2.SetRequests(v)
}
// RetriesPoR returns number of retries made by Proof-of-Retrievability
// audit check to get all headers of the objects inside storage groups.
//
// Zero Result has zero retries.
//
// See also SetRetriesPoR.
func (r Result) RetriesPoR() uint32 {
return r.v2.GetRetries()
}
// SetRetriesPoR sets number of retries made by Proof-of-Retrievability
// audit check to get all headers of the objects inside storage groups.
//
// See also RetriesPoR.
func (r *Result) SetRetriesPoR(v uint32) {
r.v2.SetRetries(v)
}
// IteratePassedStorageGroups iterates over all storage groups that passed
// Proof-of-Retrievability audit check and passes them into f. Breaks on f's
// false return, f MUST NOT be nil.
//
// Zero Result has no passed storage groups and doesn't call f.
//
// See also SubmitPassedStorageGroup.
func (r Result) IteratePassedStorageGroups(f func(oid.ID) bool) {
r2 := r.v2.GetPassSG()
var id oid.ID
for i := range r2 {
_ = id.ReadFromV2(r2[i])
if !f(id) {
return
}
}
}
// SubmitPassedStorageGroup marks storage group as passed Proof-of-Retrievability
// audit check.
//
// See also IteratePassedStorageGroups.
func (r *Result) SubmitPassedStorageGroup(sg oid.ID) {
var idV2 refs.ObjectID
sg.WriteToV2(&idV2)
r.v2.SetPassSG(append(r.v2.GetPassSG(), idV2))
}
// IterateFailedStorageGroups is similar to IteratePassedStorageGroups but for failed groups.
//
// See also SubmitFailedStorageGroup.
func (r Result) IterateFailedStorageGroups(f func(oid.ID) bool) {
v := r.v2.GetFailSG()
var id oid.ID
for i := range v {
_ = id.ReadFromV2(v[i])
if !f(id) {
return
}
}
}
// SubmitFailedStorageGroup is similar to SubmitPassedStorageGroup but for failed groups.
//
// See also IterateFailedStorageGroups.
func (r *Result) SubmitFailedStorageGroup(sg oid.ID) {
var idV2 refs.ObjectID
sg.WriteToV2(&idV2)
r.v2.SetFailSG(append(r.v2.GetFailSG(), idV2))
}
// Hits returns number of sampled objects under audit placed
// in an optimal way according to the container's placement policy
// when checking Proof-of-Placement.
//
// Zero result has zero hits.
//
// See also SetHits.
func (r Result) Hits() uint32 {
return r.v2.GetHit()
}
// SetHits sets number of sampled objects under audit placed
// in an optimal way according to the containers placement policy
// when checking Proof-of-Placement.
//
// See also Hits.
func (r *Result) SetHits(hit uint32) {
r.v2.SetHit(hit)
}
// Misses returns number of sampled objects under audit placed
// in suboptimal way according to the container's placement policy,
// but still at a satisfactory level when checking Proof-of-Placement.
//
// Zero Result has zero misses.
//
// See also SetMisses.
func (r Result) Misses() uint32 {
return r.v2.GetMiss()
}
// SetMisses sets number of sampled objects under audit placed
// in suboptimal way according to the container's placement policy,
// but still at a satisfactory level when checking Proof-of-Placement.
//
// See also Misses.
func (r *Result) SetMisses(miss uint32) {
r.v2.SetMiss(miss)
}
// Failures returns number of sampled objects under audit stored
// in a way not confirming placement policy or not found at all
// when checking Proof-of-Placement.
//
// Zero result has zero failures.
//
// See also SetFailures.
func (r Result) Failures() uint32 {
return r.v2.GetFail()
}
// SetFailures sets number of sampled objects under audit stored
// in a way not confirming placement policy or not found at all
// when checking Proof-of-Placement.
//
// See also Failures.
func (r *Result) SetFailures(fail uint32) {
r.v2.SetFail(fail)
}
// IteratePassedStorageNodes iterates over all storage nodes that passed at least one
// Proof-of-Data-Possession audit check and passes their public keys into f. Breaks on
// f's false return.
//
// f MUST NOT be nil and MUST NOT mutate parameter passed into it at least until
// the end of using the Result.
//
// Zero Result has no passed storage nodes and doesn't call f.
//
// See also SubmitPassedStorageNode.
func (r Result) IteratePassedStorageNodes(f func([]byte) bool) {
v := r.v2.GetPassNodes()
for i := range v {
if !f(v[i]) {
return
}
}
}
// SubmitPassedStorageNodes marks storage node list as passed Proof-of-Data-Possession
// audit check. The list contains public keys.
//
// Argument and its elements MUST NOT be mutated at least until the end of using the Result.
//
// See also IteratePassedStorageNodes.
func (r *Result) SubmitPassedStorageNodes(list [][]byte) {
r.v2.SetPassNodes(list)
}
// IterateFailedStorageNodes is similar to IteratePassedStorageNodes but for failed nodes.
//
// See also SubmitPassedStorageNodes.
func (r Result) IterateFailedStorageNodes(f func([]byte) bool) {
v := r.v2.GetFailNodes()
for i := range v {
if !f(v[i]) {
return
}
}
}
// SubmitFailedStorageNodes is similar to SubmitPassedStorageNodes but for failed nodes.
//
// See also IterateFailedStorageNodes.
func (r *Result) SubmitFailedStorageNodes(list [][]byte) {
r.v2.SetFailNodes(list)
}

View file

@ -1,191 +0,0 @@
package audit_test
import (
"bytes"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit"
audittest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit/test"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"github.com/stretchr/testify/require"
)
func TestResultData(t *testing.T) {
var r audit.Result
countSG := func(passed bool, f func(oid.ID)) int {
called := 0
ff := func(arg oid.ID) bool {
called++
if f != nil {
f(arg)
}
return true
}
if passed {
r.IteratePassedStorageGroups(ff)
} else {
r.IterateFailedStorageGroups(ff)
}
return called
}
countPassSG := func(f func(oid.ID)) int { return countSG(true, f) }
countFailSG := func(f func(oid.ID)) int { return countSG(false, f) }
countNodes := func(passed bool, f func([]byte)) int {
called := 0
ff := func(arg []byte) bool {
called++
if f != nil {
f(arg)
}
return true
}
if passed {
r.IteratePassedStorageNodes(ff)
} else {
r.IterateFailedStorageNodes(ff)
}
return called
}
countPassNodes := func(f func([]byte)) int { return countNodes(true, f) }
countFailNodes := func(f func([]byte)) int { return countNodes(false, f) }
require.Zero(t, r.Epoch())
_, set := r.Container()
require.False(t, set)
require.Nil(t, r.AuditorKey())
require.False(t, r.Completed())
require.Zero(t, r.RequestsPoR())
require.Zero(t, r.RetriesPoR())
require.Zero(t, countPassSG(nil))
require.Zero(t, countFailSG(nil))
require.Zero(t, countPassNodes(nil))
require.Zero(t, countFailNodes(nil))
epoch := uint64(13)
r.ForEpoch(epoch)
require.Equal(t, epoch, r.Epoch())
cnr := cidtest.ID()
r.ForContainer(cnr)
cID, set := r.Container()
require.True(t, set)
require.Equal(t, cnr, cID)
key := []byte{1, 2, 3}
r.SetAuditorKey(key)
require.Equal(t, key, r.AuditorKey())
r.Complete()
require.True(t, r.Completed())
requests := uint32(2)
r.SetRequestsPoR(requests)
require.Equal(t, requests, r.RequestsPoR())
retries := uint32(1)
r.SetRetriesPoR(retries)
require.Equal(t, retries, r.RetriesPoR())
passSG1, passSG2 := oidtest.ID(), oidtest.ID()
r.SubmitPassedStorageGroup(passSG1)
r.SubmitPassedStorageGroup(passSG2)
called1, called2 := false, false
require.EqualValues(t, 2, countPassSG(func(id oid.ID) {
if id.Equals(passSG1) {
called1 = true
} else if id.Equals(passSG2) {
called2 = true
}
}))
require.True(t, called1)
require.True(t, called2)
failSG1, failSG2 := oidtest.ID(), oidtest.ID()
r.SubmitFailedStorageGroup(failSG1)
r.SubmitFailedStorageGroup(failSG2)
called1, called2 = false, false
require.EqualValues(t, 2, countFailSG(func(id oid.ID) {
if id.Equals(failSG1) {
called1 = true
} else if id.Equals(failSG2) {
called2 = true
}
}))
require.True(t, called1)
require.True(t, called2)
hit := uint32(1)
r.SetHits(hit)
require.Equal(t, hit, r.Hits())
miss := uint32(2)
r.SetMisses(miss)
require.Equal(t, miss, r.Misses())
fail := uint32(3)
r.SetFailures(fail)
require.Equal(t, fail, r.Failures())
passNodes := [][]byte{{1}, {2}}
r.SubmitPassedStorageNodes(passNodes)
called1, called2 = false, false
require.EqualValues(t, 2, countPassNodes(func(arg []byte) {
if bytes.Equal(arg, passNodes[0]) {
called1 = true
} else if bytes.Equal(arg, passNodes[1]) {
called2 = true
}
}))
require.True(t, called1)
require.True(t, called2)
failNodes := [][]byte{{3}, {4}}
r.SubmitFailedStorageNodes(failNodes)
called1, called2 = false, false
require.EqualValues(t, 2, countFailNodes(func(arg []byte) {
if bytes.Equal(arg, failNodes[0]) {
called1 = true
} else if bytes.Equal(arg, failNodes[1]) {
called2 = true
}
}))
require.True(t, called1)
require.True(t, called2)
}
func TestResultEncoding(t *testing.T) {
r := *audittest.Result()
t.Run("binary", func(t *testing.T) {
data := r.Marshal()
var r2 audit.Result
require.NoError(t, r2.Unmarshal(data))
require.Equal(t, r, r2)
})
}

View file

@ -1,13 +0,0 @@
/*
Package audittest provides functions for convenient testing of audit package API.
Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import audittest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit/test"
dec := audittest.Result()
// test the value
*/
package audittest

View file

@ -1,36 +0,0 @@
package audittest
import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
)
// Result returns random audit.Result.
func Result() *audit.Result {
var x audit.Result
x.ForContainer(cidtest.ID())
x.SetAuditorKey([]byte("key"))
x.Complete()
x.ForEpoch(44)
x.SetHits(55)
x.SetMisses(66)
x.SetFailures(77)
x.SetRequestsPoR(88)
x.SetRequestsPoR(99)
x.SubmitFailedStorageNodes([][]byte{
[]byte("node1"),
[]byte("node2"),
})
x.SubmitPassedStorageNodes([][]byte{
[]byte("node3"),
[]byte("node4"),
})
x.SubmitPassedStorageGroup(oidtest.ID())
x.SubmitPassedStorageGroup(oidtest.ID())
x.SubmitFailedStorageGroup(oidtest.ID())
x.SubmitFailedStorageGroup(oidtest.ID())
return &x
}

View file

@ -46,10 +46,12 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
return errors.New("missing token body") return errors.New("missing token body")
} }
b.impersonate = body.GetImpersonate()
eaclTable := body.GetEACL() eaclTable := body.GetEACL()
if b.eaclTableSet = eaclTable != nil; b.eaclTableSet { if b.eaclTableSet = eaclTable != nil; b.eaclTableSet {
b.eaclTable = *eacl.NewTableFromV2(eaclTable) b.eaclTable = *eacl.NewTableFromV2(eaclTable)
} else if checkFieldPresence { } else if checkFieldPresence && !b.impersonate {
return errors.New("missing eACL table") return errors.New("missing eACL table")
} }
@ -70,8 +72,6 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
return errors.New("missing token lifetime") return errors.New("missing token lifetime")
} }
b.impersonate = body.GetImpersonate()
sig := m.GetSignature() sig := m.GetSignature()
if b.sigSet = sig != nil; sig != nil { if b.sigSet = sig != nil; sig != nil {
b.sig = *sig b.sig = *sig

View file

@ -323,6 +323,10 @@ func TestToken_ReadFromV2(t *testing.T) {
require.NoError(t, val.ReadFromV2(m)) require.NoError(t, val.ReadFromV2(m))
body.SetEACL(nil)
body.SetImpersonate(true)
require.NoError(t, val.ReadFromV2(m))
var m2 acl.BearerToken var m2 acl.BearerToken
val.WriteToV2(&m2) val.WriteToV2(&m2)

View file

@ -10,6 +10,7 @@ import (
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting" v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"google.golang.org/grpc"
) )
// Client represents virtual connection to the FrostFS network to communicate // Client represents virtual connection to the FrostFS network to communicate
@ -101,6 +102,7 @@ func (c *Client) Dial(ctx context.Context, prm PrmDial) error {
client.WithNetworkURIAddress(prm.endpoint, prm.tlsConfig), client.WithNetworkURIAddress(prm.endpoint, prm.tlsConfig),
client.WithDialTimeout(prm.timeoutDial), client.WithDialTimeout(prm.timeoutDial),
client.WithRWTimeout(prm.streamTimeout), client.WithRWTimeout(prm.streamTimeout),
client.WithGRPCDialOptions(prm.dialOptions),
)...) )...)
c.setFrostFSAPIServer((*coreServer)(&c.c)) c.setFrostFSAPIServer((*coreServer)(&c.c))
@ -186,6 +188,8 @@ type PrmDial struct {
streamTimeoutSet bool streamTimeoutSet bool
streamTimeout time.Duration streamTimeout time.Duration
dialOptions []grpc.DialOption
} }
// SetServerURI sets server URI in the FrostFS network. // SetServerURI sets server URI in the FrostFS network.
@ -226,3 +230,8 @@ func (x *PrmDial) SetStreamTimeout(timeout time.Duration) {
x.streamTimeoutSet = true x.streamTimeoutSet = true
x.streamTimeout = timeout x.streamTimeout = timeout
} }
// SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
func (x *PrmDial) SetGRPCDialOptions(opts ...grpc.DialOption) {
x.dialOptions = opts
}

View file

@ -82,6 +82,7 @@ var (
errorMissingAnnouncements = errors.New("missing announcements") errorMissingAnnouncements = errors.New("missing announcements")
errorZeroRangeLength = errors.New("zero range length") errorZeroRangeLength = errors.New("zero range length")
errorMissingRanges = errors.New("missing ranges") errorMissingRanges = errors.New("missing ranges")
errorInvalidXHeaders = errors.New("xheaders must be presented only as key-value pairs")
) )
// groups all the details required to send a single request and process a response to it. // groups all the details required to send a single request and process a response to it.

View file

@ -18,29 +18,33 @@ import (
// PrmContainerDelete groups parameters of ContainerDelete operation. // PrmContainerDelete groups parameters of ContainerDelete operation.
type PrmContainerDelete struct { type PrmContainerDelete struct {
prmCommonMeta // FrostFS request X-Headers
XHeaders []string
idSet bool ContainerID *cid.ID
id cid.ID
tokSet bool Session *session.Container
tok session.Container
} }
// SetContainer sets identifier of the FrostFS container to be removed. // SetContainer sets identifier of the FrostFS container to be removed.
// Required parameter. // Required parameter.
func (x *PrmContainerDelete) SetContainer(id cid.ID) { //
x.id = id // Deprecated: Use PrmContainerDelete.Container instead.
x.idSet = true func (prm *PrmContainerDelete) SetContainer(id cid.ID) {
prm.ContainerID = &id
} }
func (x *PrmContainerDelete) buildRequest(c *Client) (*v2container.DeleteRequest, error) { func (prm *PrmContainerDelete) buildRequest(c *Client) (*v2container.DeleteRequest, error) {
if !x.idSet { if prm.ContainerID == nil {
return nil, errorMissingContainer return nil, errorMissingContainer
} }
if len(prm.XHeaders)%2 != 0 {
return nil, errorInvalidXHeaders
}
var cidV2 refs.ContainerID var cidV2 refs.ContainerID
x.id.WriteToV2(&cidV2) prm.ContainerID.WriteToV2(&cidV2)
// Container contract expects signature of container ID value, // Container contract expects signature of container ID value,
// don't get confused with stable marshaled protobuf container.ID structure. // don't get confused with stable marshaled protobuf container.ID structure.
@ -61,11 +65,11 @@ func (x *PrmContainerDelete) buildRequest(c *Client) (*v2container.DeleteRequest
reqBody.SetSignature(&sigv2) reqBody.SetSignature(&sigv2)
var meta v2session.RequestMetaHeader var meta v2session.RequestMetaHeader
writeXHeadersToMeta(x.prmCommonMeta.xHeaders, &meta) writeXHeadersToMeta(prm.XHeaders, &meta)
if x.tokSet { if prm.Session != nil {
var tokv2 v2session.Token var tokv2 v2session.Token
x.tok.WriteToV2(&tokv2) prm.Session.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2) meta.SetSessionToken(&tokv2)
} }
@ -82,9 +86,10 @@ func (x *PrmContainerDelete) buildRequest(c *Client) (*v2container.DeleteRequest
// This may affect the execution of an operation (e.g. access control). // This may affect the execution of an operation (e.g. access control).
// //
// Must be signed. // Must be signed.
func (x *PrmContainerDelete) WithinSession(tok session.Container) { //
x.tok = tok // Deprecated: Use PrmContainerDelete.Session instead.
x.tokSet = true func (prm *PrmContainerDelete) WithinSession(tok session.Container) {
prm.Session = &tok
} }
// ResContainerDelete groups resulting values of ContainerDelete operation. // ResContainerDelete groups resulting values of ContainerDelete operation.

View file

@ -18,26 +18,31 @@ import (
// PrmContainerGet groups parameters of ContainerGet operation. // PrmContainerGet groups parameters of ContainerGet operation.
type PrmContainerGet struct { type PrmContainerGet struct {
prmCommonMeta // FrostFS request X-Headers.
XHeaders []string
idSet bool ContainerID *cid.ID
id cid.ID
} }
// SetContainer sets identifier of the container to be read. // SetContainer sets identifier of the container to be read.
// Required parameter. // Required parameter.
func (x *PrmContainerGet) SetContainer(id cid.ID) { //
x.id = id // Deprecated: Use PrmContainerGet.ContainerID instead.
x.idSet = true func (prm *PrmContainerGet) SetContainer(cid cid.ID) {
prm.ContainerID = &cid
} }
func (x *PrmContainerGet) buildRequest(c *Client) (*v2container.GetRequest, error) { func (prm *PrmContainerGet) buildRequest(c *Client) (*v2container.GetRequest, error) {
if !x.idSet { if prm.ContainerID == nil {
return nil, errorMissingContainer return nil, errorMissingContainer
} }
if len(prm.XHeaders)%2 != 0 {
return nil, errorInvalidXHeaders
}
var cidV2 refs.ContainerID var cidV2 refs.ContainerID
x.id.WriteToV2(&cidV2) prm.ContainerID.WriteToV2(&cidV2)
reqBody := new(v2container.GetRequestBody) reqBody := new(v2container.GetRequestBody)
reqBody.SetContainerID(&cidV2) reqBody.SetContainerID(&cidV2)

View file

@ -19,20 +19,20 @@ import (
// PrmContainerPut groups parameters of ContainerPut operation. // PrmContainerPut groups parameters of ContainerPut operation.
type PrmContainerPut struct { type PrmContainerPut struct {
prmCommonMeta // FrostFS request X-Headers
XHeaders []string
cnrSet bool Container *container.Container
cnr container.Container
sessionSet bool Session *session.Container
session session.Container
} }
// SetContainer sets structured information about new FrostFS container. // SetContainer sets structured information about new FrostFS container.
// Required parameter. // Required parameter.
//
// Deprecated: Use PrmContainerPut.Container instead.
func (x *PrmContainerPut) SetContainer(cnr container.Container) { func (x *PrmContainerPut) SetContainer(cnr container.Container) {
x.cnr = cnr x.Container = &cnr
x.cnrSet = true
} }
// WithinSession specifies session within which container should be saved. // WithinSession specifies session within which container should be saved.
@ -43,23 +43,28 @@ func (x *PrmContainerPut) SetContainer(cnr container.Container) {
// Session is optional, if set the following requirements apply: // Session is optional, if set the following requirements apply:
// - session operation MUST be session.VerbContainerPut (ForVerb) // - session operation MUST be session.VerbContainerPut (ForVerb)
// - token MUST be signed using private key of the owner of the container to be saved // - token MUST be signed using private key of the owner of the container to be saved
//
// Deprecated: Use PrmContainerPut.Session instead.
func (x *PrmContainerPut) WithinSession(s session.Container) { func (x *PrmContainerPut) WithinSession(s session.Container) {
x.session = s x.Session = &s
x.sessionSet = true
} }
func (x *PrmContainerPut) buildRequest(c *Client) (*v2container.PutRequest, error) { func (x *PrmContainerPut) buildRequest(c *Client) (*v2container.PutRequest, error) {
if !x.cnrSet { if x.Container == nil {
return nil, errorMissingContainer return nil, errorMissingContainer
} }
if len(x.XHeaders)%2 != 0 {
return nil, errorInvalidXHeaders
}
// TODO: check private key is set before forming the request // TODO: check private key is set before forming the request
var cnr v2container.Container var cnr v2container.Container
x.cnr.WriteToV2(&cnr) x.Container.WriteToV2(&cnr)
var sig frostfscrypto.Signature var sig frostfscrypto.Signature
err := container.CalculateSignature(&sig, x.cnr, c.prm.key) err := container.CalculateSignature(&sig, *x.Container, c.prm.key)
if err != nil { if err != nil {
return nil, fmt.Errorf("calculate container signature: %w", err) return nil, fmt.Errorf("calculate container signature: %w", err)
} }
@ -72,11 +77,11 @@ func (x *PrmContainerPut) buildRequest(c *Client) (*v2container.PutRequest, erro
reqBody.SetSignature(&sigv2) reqBody.SetSignature(&sigv2)
var meta v2session.RequestMetaHeader var meta v2session.RequestMetaHeader
writeXHeadersToMeta(x.prmCommonMeta.xHeaders, &meta) writeXHeadersToMeta(x.XHeaders, &meta)
if x.sessionSet { if x.Session != nil {
var tokv2 v2session.Token var tokv2 v2session.Token
x.session.WriteToV2(&tokv2) x.Session.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2) meta.SetSessionToken(&tokv2)
} }

View file

@ -1,97 +1,64 @@
package client package client
import ( import (
"errors"
"fmt" "fmt"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
) )
// unwraps err using errors.Unwrap and returns the result. // wrapsErrType returns true if any error in the error tree of err is of type E.
func unwrapErr(err error) error { func wrapsErrType[E error](err error) bool {
for e := errors.Unwrap(err); e != nil; e = errors.Unwrap(err) { switch e := err.(type) {
err = e case E:
return true
case interface{ Unwrap() error }:
return wrapsErrType[E](e.Unwrap())
case interface{ Unwrap() []error }:
for _, ei := range e.Unwrap() {
if wrapsErrType[E](ei) {
return true
}
}
return false
default:
return false
} }
return err
} }
// IsErrContainerNotFound checks if err corresponds to FrostFS status // IsErrContainerNotFound checks if err corresponds to FrostFS status
// return corresponding to missing container. Supports wrapped errors. // return corresponding to missing container. Supports wrapped errors.
func IsErrContainerNotFound(err error) bool { func IsErrContainerNotFound(err error) bool {
switch unwrapErr(err).(type) { return wrapsErrType[*apistatus.ContainerNotFound](err)
default:
return false
case
apistatus.ContainerNotFound,
*apistatus.ContainerNotFound:
return true
}
} }
// IsErrEACLNotFound checks if err corresponds to FrostFS status // IsErrEACLNotFound checks if err corresponds to FrostFS status
// return corresponding to missing eACL table. Supports wrapped errors. // return corresponding to missing eACL table. Supports wrapped errors.
func IsErrEACLNotFound(err error) bool { func IsErrEACLNotFound(err error) bool {
switch unwrapErr(err).(type) { return wrapsErrType[*apistatus.EACLNotFound](err)
default:
return false
case
apistatus.EACLNotFound,
*apistatus.EACLNotFound:
return true
}
} }
// IsErrObjectNotFound checks if err corresponds to FrostFS status // IsErrObjectNotFound checks if err corresponds to FrostFS status
// return corresponding to missing object. Supports wrapped errors. // return corresponding to missing object. Supports wrapped errors.
func IsErrObjectNotFound(err error) bool { func IsErrObjectNotFound(err error) bool {
switch unwrapErr(err).(type) { return wrapsErrType[*apistatus.ObjectNotFound](err)
default:
return false
case
apistatus.ObjectNotFound,
*apistatus.ObjectNotFound:
return true
}
} }
// IsErrObjectAlreadyRemoved checks if err corresponds to FrostFS status // IsErrObjectAlreadyRemoved checks if err corresponds to FrostFS status
// return corresponding to already removed object. Supports wrapped errors. // return corresponding to already removed object. Supports wrapped errors.
func IsErrObjectAlreadyRemoved(err error) bool { func IsErrObjectAlreadyRemoved(err error) bool {
switch unwrapErr(err).(type) { return wrapsErrType[*apistatus.ObjectAlreadyRemoved](err)
default:
return false
case
apistatus.ObjectAlreadyRemoved,
*apistatus.ObjectAlreadyRemoved:
return true
}
} }
// IsErrSessionExpired checks if err corresponds to FrostFS status return // IsErrSessionExpired checks if err corresponds to FrostFS status return
// corresponding to expired session. Supports wrapped errors. // corresponding to expired session. Supports wrapped errors.
func IsErrSessionExpired(err error) bool { func IsErrSessionExpired(err error) bool {
switch unwrapErr(err).(type) { return wrapsErrType[*apistatus.SessionTokenExpired](err)
default:
return false
case
apistatus.SessionTokenExpired,
*apistatus.SessionTokenExpired:
return true
}
} }
// IsErrSessionNotFound checks if err corresponds to FrostFS status return // IsErrSessionNotFound checks if err corresponds to FrostFS status return
// corresponding to missing session. Supports wrapped errors. // corresponding to missing session. Supports wrapped errors.
func IsErrSessionNotFound(err error) bool { func IsErrSessionNotFound(err error) bool {
switch unwrapErr(err).(type) { return wrapsErrType[*apistatus.SessionTokenNotFound](err)
default:
return false
case
apistatus.SessionTokenNotFound,
*apistatus.SessionTokenNotFound:
return true
}
} }
// returns error describing missing field with the given name. // returns error describing missing field with the given name.

View file

@ -10,59 +10,40 @@ import (
) )
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
for _, tc := range []struct { errs := []struct {
check func(error) bool check func(error) bool
errs []error err error
}{ }{
{ {
check: client.IsErrContainerNotFound, check: client.IsErrContainerNotFound,
errs: []error{ err: new(apistatus.ContainerNotFound),
apistatus.ContainerNotFound{},
new(apistatus.ContainerNotFound),
},
}, },
{ {
check: client.IsErrEACLNotFound, check: client.IsErrEACLNotFound,
errs: []error{ err: new(apistatus.EACLNotFound),
apistatus.EACLNotFound{},
new(apistatus.EACLNotFound),
},
}, },
{ {
check: client.IsErrObjectNotFound, check: client.IsErrObjectNotFound,
errs: []error{ err: new(apistatus.ObjectNotFound),
apistatus.ObjectNotFound{},
new(apistatus.ObjectNotFound),
},
}, },
{ {
check: client.IsErrObjectAlreadyRemoved, check: client.IsErrObjectAlreadyRemoved,
errs: []error{ err: new(apistatus.ObjectAlreadyRemoved),
apistatus.ObjectAlreadyRemoved{},
new(apistatus.ObjectAlreadyRemoved),
},
}, },
{ {
check: client.IsErrSessionExpired, check: client.IsErrSessionExpired,
errs: []error{ err: new(apistatus.SessionTokenExpired),
apistatus.SessionTokenExpired{},
new(apistatus.SessionTokenExpired),
},
}, { }, {
check: client.IsErrSessionNotFound, check: client.IsErrSessionNotFound,
errs: []error{ err: new(apistatus.SessionTokenNotFound),
apistatus.SessionTokenNotFound{},
new(apistatus.SessionTokenNotFound),
}, },
}, }
} {
require.NotEmpty(t, tc.errs)
for i := range tc.errs { for i := range errs {
require.True(t, tc.check(tc.errs[i]), tc.errs[i]) for j := range errs {
require.True(t, tc.check(fmt.Errorf("top-level context: :%w", nestedErr := fmt.Errorf("top-level context: :%w", fmt.Errorf("inner context: %w", errs[j].err))
fmt.Errorf("inner context: %w", tc.errs[i])), require.Equal(t, i == j, errs[i].check(errs[j].err))
), tc.errs[i]) require.Equal(t, i == j, errs[i].check(nestedErr))
} }
} }
} }

View file

@ -3,28 +3,29 @@ package client
import ( import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"errors"
"fmt"
"io"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
) )
// defaultGRPCPayloadChunkLen default value for maxChunkLen.
// See PrmObjectPutInit.SetGRPCPayloadChunkLen for details.
const defaultGRPCPayloadChunkLen = 3 << 20
// PrmObjectPutInit groups parameters of ObjectPutInit operation. // PrmObjectPutInit groups parameters of ObjectPutInit operation.
type PrmObjectPutInit struct { type PrmObjectPutInit struct {
copyNum []uint32 copyNum []uint32
key *ecdsa.PrivateKey key *ecdsa.PrivateKey
meta v2session.RequestMetaHeader meta v2session.RequestMetaHeader
maxChunkLen int
maxSize uint64
epochSource transformer.EpochSource
withoutHomomorphicHash bool
} }
// SetCopiesNumber sets number of object copies that is enough to consider put successful. // SetCopiesNumber sets number of object copies that is enough to consider put successful.
@ -39,6 +40,15 @@ func (x *PrmObjectPutInit) SetCopiesNumberByVectors(copiesNumbers []uint32) {
x.copyNum = copiesNumbers x.copyNum = copiesNumbers
} }
// SetGRPCPayloadChunkLen sets maximum chunk length value for gRPC Put request.
// Maximum chunk length restricts maximum byte length of the chunk
// transmitted in a single stream message. It depends on
// server settings and other message fields.
// If not specified or negative value set, default value of 3MiB will be used.
func (x *PrmObjectPutInit) SetGRPCPayloadChunkLen(v int) {
x.maxChunkLen = v
}
// ResObjectPut groups the final result values of ObjectPutInit operation. // ResObjectPut groups the final result values of ObjectPutInit operation.
type ResObjectPut struct { type ResObjectPut struct {
statusRes statusRes
@ -51,29 +61,35 @@ func (x ResObjectPut) StoredObjectID() oid.ID {
return x.obj return x.obj
} }
// ObjectWriter is designed to write one object to FrostFS system. // ObjectWriter is designed to write one object or
// multiple parts of one object to FrostFS system.
// //
// Must be initialized using Client.ObjectPutInit, any other // Must be initialized using Client.ObjectPutInit, any other
// usage is unsafe. // usage is unsafe.
type ObjectWriter struct { type ObjectWriter interface {
cancelCtxStream context.CancelFunc // WriteHeader writes header of the object. Result means success.
// Failure reason can be received via Close.
client *Client WriteHeader(context.Context, object.Object) bool
stream interface { // WritePayloadChunk writes chunk of the object payload. Result means success.
Write(*v2object.PutRequest) error // Failure reason can be received via Close.
Close() error WritePayloadChunk(context.Context, []byte) bool
} // Close ends writing the object and returns the result of the operation
// along with the final results. Must be called after using the ObjectWriter.
key *ecdsa.PrivateKey //
res ResObjectPut // Exactly one return value is non-nil. By default, server status is returned in res structure.
err error // Any client's internal or transport errors are returned as Go built-in error.
// If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
chunkCalled bool // codes are returned as error.
//
respV2 v2object.PutResponse // Return statuses:
req v2object.PutRequest // - global (see Client docs);
partInit v2object.PutObjectPartInit // - *apistatus.ContainerNotFound;
partChunk v2object.PutObjectPartChunk // - *apistatus.ObjectAccessDenied;
// - *apistatus.ObjectLocked;
// - *apistatus.LockNonRegularObject;
// - *apistatus.SessionTokenNotFound;
// - *apistatus.SessionTokenExpired.
Close(context.Context) (*ResObjectPut, error)
} }
// UseKey specifies private key to sign the requests. // UseKey specifies private key to sign the requests.
@ -112,129 +128,21 @@ func (x *PrmObjectPutInit) WithXHeaders(hs ...string) {
writeXHeadersToMeta(hs, &x.meta) writeXHeadersToMeta(hs, &x.meta)
} }
// WriteHeader writes header of the object. Result means success. // WithObjectMaxSize specifies max object size value and use it during object splitting.
// Failure reason can be received via Close. // When specified, start writing to the stream only after the object is formed.
func (x *ObjectWriter) WriteHeader(hdr object.Object) bool { // Continue processing the input only when the previous formed object has been successfully written.
v2Hdr := hdr.ToV2() func (x *PrmObjectPutInit) WithObjectMaxSize(maxSize uint64) {
x.maxSize = maxSize
x.partInit.SetObjectID(v2Hdr.GetObjectID())
x.partInit.SetHeader(v2Hdr.GetHeader())
x.partInit.SetSignature(v2Hdr.GetSignature())
x.req.GetBody().SetObjectPart(&x.partInit)
x.req.SetVerificationHeader(nil)
x.err = signature.SignServiceMessage(x.key, &x.req)
if x.err != nil {
x.err = fmt.Errorf("sign message: %w", x.err)
return false
} }
x.err = x.stream.Write(&x.req) // WithoutHomomorphicHash if set to true do not use Tillich-Zémor hash for payload.
return x.err == nil func (x *PrmObjectPutInit) WithoutHomomorphicHash(v bool) {
x.withoutHomomorphicHash = v
} }
// WritePayloadChunk writes chunk of the object payload. Result means success. // WithEpochSource specifies epoch for object when split it on client side.
// Failure reason can be received via Close. func (x *PrmObjectPutInit) WithEpochSource(es transformer.EpochSource) {
func (x *ObjectWriter) WritePayloadChunk(chunk []byte) bool { x.epochSource = es
if !x.chunkCalled {
x.chunkCalled = true
x.req.GetBody().SetObjectPart(&x.partChunk)
}
for ln := len(chunk); ln > 0; ln = len(chunk) {
// maxChunkLen restricts maximum byte length of the chunk
// transmitted in a single stream message. It depends on
// server settings and other message fields, but for now
// we simply assume that 3MB is large enough to reduce the
// number of messages, and not to exceed the limit
// (4MB by default for gRPC servers).
const maxChunkLen = 3 << 20
if ln > maxChunkLen {
ln = maxChunkLen
}
// we deal with size limit overflow above, but there is another case:
// what if method is called with "small" chunk many times? We write
// a message to the stream on each call. Alternatively, we could use buffering.
// In most cases, the chunk length does not vary between calls. Given this
// assumption, as well as the length of the payload from the header, it is
// possible to buffer the data of intermediate chunks, and send a message when
// the allocated buffer is filled, or when the last chunk is received.
// It is mentally assumed that allocating and filling the buffer is better than
// synchronous sending, but this needs to be tested.
x.partChunk.SetChunk(chunk[:ln])
x.req.SetVerificationHeader(nil)
x.err = signature.SignServiceMessage(x.key, &x.req)
if x.err != nil {
x.err = fmt.Errorf("sign message: %w", x.err)
return false
}
x.err = x.stream.Write(&x.req)
if x.err != nil {
return false
}
chunk = chunk[ln:]
}
return true
}
// Close ends writing the object and returns the result of the operation
// along with the final results. Must be called after using the ObjectWriter.
//
// Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as Go built-in error.
// If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
// codes are returned as error.
//
// Return statuses:
// - global (see Client docs);
// - *apistatus.ContainerNotFound;
// - *apistatus.ObjectAccessDenied;
// - *apistatus.ObjectLocked;
// - *apistatus.LockNonRegularObject;
// - *apistatus.SessionTokenNotFound;
// - *apistatus.SessionTokenExpired.
func (x *ObjectWriter) Close() (*ResObjectPut, error) {
defer x.cancelCtxStream()
// Ignore io.EOF error, because it is expected error for client-side
// stream termination by the server. E.g. when stream contains invalid
// message. Server returns an error in response message (in status).
if x.err != nil && !errors.Is(x.err, io.EOF) {
return nil, x.err
}
if x.err = x.stream.Close(); x.err != nil {
return nil, x.err
}
x.res.st, x.err = x.client.processResponse(&x.respV2)
if x.err != nil {
return nil, x.err
}
if !apistatus.IsSuccessful(x.res.st) {
return &x.res, nil
}
const fieldID = "ID"
idV2 := x.respV2.GetBody().GetObjectID()
if idV2 == nil {
return nil, newErrMissingResponseField(fieldID)
}
x.err = x.res.obj.ReadFromV2(*idV2)
if x.err != nil {
x.err = newErrInvalidResponseField(fieldID, x.err)
}
return &x.res, nil
} }
// ObjectPutInit initiates writing an object through a remote server using FrostFS API protocol. // ObjectPutInit initiates writing an object through a remote server using FrostFS API protocol.
@ -244,26 +152,9 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) {
// //
// Returns an error if parameters are set incorrectly. // Returns an error if parameters are set incorrectly.
// Context is required and must not be nil. It is used for network communication. // Context is required and must not be nil. It is used for network communication.
func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*ObjectWriter, error) { func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (ObjectWriter, error) {
var w ObjectWriter if prm.maxSize > 0 {
return c.objectPutInitTransformer(prm)
ctx, cancel := context.WithCancel(ctx)
stream, err := rpcapi.PutObject(&c.c, &w.respV2, client.WithContext(ctx))
if err != nil {
cancel()
return nil, fmt.Errorf("open stream: %w", err)
} }
return c.objectPutInitRaw(ctx, prm)
w.key = &c.prm.key
if prm.key != nil {
w.key = prm.key
}
w.cancelCtxStream = cancel
w.client = c
w.stream = stream
w.partInit.SetCopiesNumber(prm.copyNum)
w.req.SetBody(new(v2object.PutRequestBody))
c.prepareRequest(&w.req, &prm.meta)
return &w, nil
} }

154
client/object_put_raw.go Normal file
View file

@ -0,0 +1,154 @@
package client
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"io"
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
)
func (c *Client) objectPutInitRaw(ctx context.Context, prm PrmObjectPutInit) (*objectWriterRaw, error) {
var w objectWriterRaw
stream, err := rpcapi.PutObject(&c.c, &w.respV2, client.WithContext(ctx))
if err != nil {
return nil, fmt.Errorf("open stream: %w", err)
}
w.key = &c.prm.key
if prm.key != nil {
w.key = prm.key
}
w.client = c
w.stream = stream
w.partInit.SetCopiesNumber(prm.copyNum)
w.req.SetBody(new(v2object.PutRequestBody))
if prm.maxChunkLen > 0 {
w.maxChunkLen = prm.maxChunkLen
} else {
w.maxChunkLen = defaultGRPCPayloadChunkLen
}
c.prepareRequest(&w.req, &prm.meta)
return &w, nil
}
type objectWriterRaw struct {
client *Client
stream interface {
Write(*v2object.PutRequest) error
Close() error
}
key *ecdsa.PrivateKey
res ResObjectPut
err error
chunkCalled bool
respV2 v2object.PutResponse
req v2object.PutRequest
partInit v2object.PutObjectPartInit
partChunk v2object.PutObjectPartChunk
maxChunkLen int
}
func (x *objectWriterRaw) WriteHeader(_ context.Context, hdr object.Object) bool {
v2Hdr := hdr.ToV2()
x.partInit.SetObjectID(v2Hdr.GetObjectID())
x.partInit.SetHeader(v2Hdr.GetHeader())
x.partInit.SetSignature(v2Hdr.GetSignature())
x.req.GetBody().SetObjectPart(&x.partInit)
x.req.SetVerificationHeader(nil)
x.err = signature.SignServiceMessage(x.key, &x.req)
if x.err != nil {
x.err = fmt.Errorf("sign message: %w", x.err)
return false
}
x.err = x.stream.Write(&x.req)
return x.err == nil
}
func (x *objectWriterRaw) WritePayloadChunk(_ context.Context, chunk []byte) bool {
if !x.chunkCalled {
x.chunkCalled = true
x.req.GetBody().SetObjectPart(&x.partChunk)
}
for ln := len(chunk); ln > 0; ln = len(chunk) {
if ln > x.maxChunkLen {
ln = x.maxChunkLen
}
// we deal with size limit overflow above, but there is another case:
// what if method is called with "small" chunk many times? We write
// a message to the stream on each call. Alternatively, we could use buffering.
// In most cases, the chunk length does not vary between calls. Given this
// assumption, as well as the length of the payload from the header, it is
// possible to buffer the data of intermediate chunks, and send a message when
// the allocated buffer is filled, or when the last chunk is received.
// It is mentally assumed that allocating and filling the buffer is better than
// synchronous sending, but this needs to be tested.
x.partChunk.SetChunk(chunk[:ln])
x.req.SetVerificationHeader(nil)
x.err = signature.SignServiceMessage(x.key, &x.req)
if x.err != nil {
x.err = fmt.Errorf("sign message: %w", x.err)
return false
}
x.err = x.stream.Write(&x.req)
if x.err != nil {
return false
}
chunk = chunk[ln:]
}
return true
}
func (x *objectWriterRaw) Close(_ context.Context) (*ResObjectPut, error) {
// Ignore io.EOF error, because it is expected error for client-side
// stream termination by the server. E.g. when stream contains invalid
// message. Server returns an error in response message (in status).
if x.err != nil && !errors.Is(x.err, io.EOF) {
return nil, x.err
}
if x.err = x.stream.Close(); x.err != nil {
return nil, x.err
}
x.res.st, x.err = x.client.processResponse(&x.respV2)
if x.err != nil {
return nil, x.err
}
if !apistatus.IsSuccessful(x.res.st) {
return &x.res, nil
}
const fieldID = "ID"
idV2 := x.respV2.GetBody().GetObjectID()
if idV2 == nil {
return nil, newErrMissingResponseField(fieldID)
}
x.err = x.res.obj.ReadFromV2(*idV2)
if x.err != nil {
x.err = newErrInvalidResponseField(fieldID, x.err)
}
return &x.res, nil
}

116
client/object_put_single.go Normal file
View file

@ -0,0 +1,116 @@
package client
import (
"context"
"crypto/ecdsa"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
)
// PrmObjectPutSingle groups parameters of PutSingle operation.
type PrmObjectPutSingle struct {
copyNum []uint32
meta v2session.RequestMetaHeader
object *v2object.Object
key *ecdsa.PrivateKey
}
// SetCopiesNumber sets ordered list of minimal required object copies numbers
// per placement vector. List's length MUST equal container's placement vector number,
// otherwise request will fail.
func (x *PrmObjectPutSingle) SetCopiesNumber(v []uint32) {
x.copyNum = v
}
// UseKey specifies private key to sign the requests.
// If key is not provided, then Client default key is used.
func (x *PrmObjectPutSingle) UseKey(key *ecdsa.PrivateKey) {
x.key = key
}
// WithBearerToken attaches bearer token to be used for the operation.
// Should be called once before any writing steps.
func (x *PrmObjectPutSingle) WithBearerToken(t bearer.Token) {
v2token := &acl.BearerToken{}
t.WriteToV2(v2token)
x.meta.SetBearerToken(v2token)
}
// WithinSession specifies session within which object should be stored.
// Should be called once before any writing steps.
func (x *PrmObjectPutSingle) WithinSession(t session.Object) {
tv2 := &v2session.Token{}
t.WriteToV2(tv2)
x.meta.SetSessionToken(tv2)
}
// ExecuteLocal tells the server to execute the operation locally.
func (x *PrmObjectPutSingle) ExecuteLocal() {
x.meta.SetTTL(1)
}
// WithXHeaders specifies list of extended headers (string key-value pairs)
// to be attached to the request. Must have an even length.
//
// Slice must not be mutated until the operation completes.
func (x *PrmObjectPutSingle) WithXHeaders(hs ...string) {
writeXHeadersToMeta(hs, &x.meta)
}
// SetObject specifies prepared object to put.
func (x *PrmObjectPutSingle) SetObject(o *v2object.Object) {
x.object = o
}
// ResObjectPutSingle groups resulting values of PutSingle operation.
type ResObjectPutSingle struct {
statusRes
}
// ObjectPutSingle writes prepared object to FrostFS.
// Object must have payload, also containerID, objectID, ownerID, payload hash, payload length of an object must be set.
// Exactly one return value is non-nil. By default, server status is returned in res structure.
// Any client's internal or transport errors are returned as Go built-in error.
// If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
// codes are returned as error.
func (c *Client) ObjectPutSingle(ctx context.Context, prm PrmObjectPutSingle) (*ResObjectPutSingle, error) {
body := &v2object.PutSingleRequestBody{}
body.SetCopiesNumber(prm.copyNum)
body.SetObject(prm.object)
req := &v2object.PutSingleRequest{}
req.SetBody(body)
c.prepareRequest(req, &prm.meta)
key := &c.prm.key
if prm.key != nil {
key = prm.key
}
err := signature.SignServiceMessage(key, req)
if err != nil {
return nil, fmt.Errorf("sign request: %w", err)
}
resp, err := rpcapi.PutSingleObject(&c.c, req, client.WithContext(ctx))
if err != nil {
return nil, err
}
var res ResObjectPutSingle
res.st, err = c.processResponse(resp)
if err != nil {
return nil, err
}
return &res, nil
}

View file

@ -0,0 +1,119 @@
package client
import (
"context"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (c *Client) objectPutInitTransformer(prm PrmObjectPutInit) (*objectWriterTransformer, error) {
var w objectWriterTransformer
w.it = internalTarget{
client: c,
prm: prm,
}
key := &c.prm.key
if prm.key != nil {
key = prm.key
}
w.ot = transformer.NewPayloadSizeLimiter(transformer.Params{
Key: key,
NextTargetInit: func() transformer.ObjectWriter { return &w.it },
MaxSize: prm.maxSize,
WithoutHomomorphicHash: prm.withoutHomomorphicHash,
NetworkState: prm.epochSource,
})
return &w, nil
}
type objectWriterTransformer struct {
ot transformer.ChunkedObjectWriter
it internalTarget
err error
}
func (x *objectWriterTransformer) WriteHeader(ctx context.Context, hdr object.Object) bool {
x.err = x.ot.WriteHeader(ctx, &hdr)
return x.err == nil
}
func (x *objectWriterTransformer) WritePayloadChunk(ctx context.Context, chunk []byte) bool {
_, x.err = x.ot.Write(ctx, chunk)
return x.err == nil
}
func (x *objectWriterTransformer) Close(ctx context.Context) (*ResObjectPut, error) {
ai, err := x.ot.Close(ctx)
if err != nil {
return nil, err
}
if ai != nil && ai.ParentID != nil {
x.it.res.obj = *ai.ParentID
}
return x.it.res, nil
}
type internalTarget struct {
client *Client
res *ResObjectPut
prm PrmObjectPutInit
useStream bool
}
func (it *internalTarget) WriteObject(ctx context.Context, o *object.Object) error {
putSingleImplemented, err := it.tryPutSingle(ctx, o)
if putSingleImplemented {
return err
}
it.useStream = true
return it.putAsStream(ctx, o)
}
func (it *internalTarget) putAsStream(ctx context.Context, o *object.Object) error {
wrt, err := it.client.objectPutInitRaw(ctx, it.prm)
if err != nil {
return err
}
if wrt.WriteHeader(ctx, *o) {
wrt.WritePayloadChunk(ctx, o.Payload())
}
it.res, err = wrt.Close(ctx)
if err == nil && !it.client.prm.resolveFrostFSErrors && !apistatus.IsSuccessful(it.res.st) {
err = apistatus.ErrFromStatus(it.res.st)
}
return err
}
func (it *internalTarget) tryPutSingle(ctx context.Context, o *object.Object) (bool, error) {
if it.useStream {
return false, nil
}
var prm PrmObjectPutSingle
prm.SetCopiesNumber(it.prm.copyNum)
prm.SetObject(o.ToV2())
prm.UseKey(prm.key)
prm.meta = it.prm.meta
res, err := it.client.ObjectPutSingle(ctx, prm)
if err != nil && status.Code(err) == codes.Unimplemented {
return false, err
}
if err == nil {
id, _ := o.ID()
it.res = &ResObjectPut{
statusRes: res.statusRes,
obj: id,
}
if !it.client.prm.resolveFrostFSErrors && !apistatus.IsSuccessful(it.res.st) {
return true, apistatus.ErrFromStatus(it.res.st)
}
return true, nil
}
return true, err
}

View file

@ -14,7 +14,7 @@ type ServerInternal struct {
v2 status.Status v2 status.Status
} }
func (x ServerInternal) Error() string { func (x *ServerInternal) Error() string {
return errMessageStatusV2( return errMessageStatusV2(
globalizeCodeV2(status.Internal, status.GlobalizeCommonFail), globalizeCodeV2(status.Internal, status.GlobalizeCommonFail),
x.v2.Message(), x.v2.Message(),
@ -62,7 +62,7 @@ type WrongMagicNumber struct {
v2 status.Status v2 status.Status
} }
func (x WrongMagicNumber) Error() string { func (x *WrongMagicNumber) Error() string {
return errMessageStatusV2( return errMessageStatusV2(
globalizeCodeV2(status.WrongMagicNumber, status.GlobalizeCommonFail), globalizeCodeV2(status.WrongMagicNumber, status.GlobalizeCommonFail),
x.v2.Message(), x.v2.Message(),
@ -132,7 +132,7 @@ type SignatureVerification struct {
const defaultSignatureVerificationMsg = "signature verification failed" const defaultSignatureVerificationMsg = "signature verification failed"
func (x SignatureVerification) Error() string { func (x *SignatureVerification) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultSignatureVerificationMsg msg = defaultSignatureVerificationMsg
@ -191,7 +191,7 @@ type NodeUnderMaintenance struct {
const defaultNodeUnderMaintenanceMsg = "node is under maintenance" const defaultNodeUnderMaintenanceMsg = "node is under maintenance"
// Error implements the error interface. // Error implements the error interface.
func (x NodeUnderMaintenance) Error() string { func (x *NodeUnderMaintenance) Error() string {
msg := x.Message() msg := x.Message()
if msg == "" { if msg == "" {
msg = defaultNodeUnderMaintenanceMsg msg = defaultNodeUnderMaintenanceMsg

View file

@ -13,7 +13,7 @@ type ContainerNotFound struct {
const defaultContainerNotFoundMsg = "container not found" const defaultContainerNotFoundMsg = "container not found"
func (x ContainerNotFound) Error() string { func (x *ContainerNotFound) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultContainerNotFoundMsg msg = defaultContainerNotFoundMsg
@ -51,7 +51,7 @@ type EACLNotFound struct {
const defaultEACLNotFoundMsg = "eACL not found" const defaultEACLNotFoundMsg = "eACL not found"
func (x EACLNotFound) Error() string { func (x *EACLNotFound) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultEACLNotFoundMsg msg = defaultEACLNotFoundMsg

View file

@ -13,7 +13,7 @@ type ObjectLocked struct {
const defaultObjectLockedMsg = "object is locked" const defaultObjectLockedMsg = "object is locked"
func (x ObjectLocked) Error() string { func (x *ObjectLocked) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultObjectLockedMsg msg = defaultObjectLockedMsg
@ -50,7 +50,7 @@ type LockNonRegularObject struct {
const defaultLockNonRegularObjectMsg = "locking non-regular object is forbidden" const defaultLockNonRegularObjectMsg = "locking non-regular object is forbidden"
func (x LockNonRegularObject) Error() string { func (x *LockNonRegularObject) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultLockNonRegularObjectMsg msg = defaultLockNonRegularObjectMsg
@ -87,7 +87,7 @@ type ObjectAccessDenied struct {
const defaultObjectAccessDeniedMsg = "access to object operation denied" const defaultObjectAccessDeniedMsg = "access to object operation denied"
func (x ObjectAccessDenied) Error() string { func (x *ObjectAccessDenied) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultObjectAccessDeniedMsg msg = defaultObjectAccessDeniedMsg
@ -135,7 +135,7 @@ type ObjectNotFound struct {
const defaultObjectNotFoundMsg = "object not found" const defaultObjectNotFoundMsg = "object not found"
func (x ObjectNotFound) Error() string { func (x *ObjectNotFound) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultObjectNotFoundMsg msg = defaultObjectNotFoundMsg
@ -172,7 +172,7 @@ type ObjectAlreadyRemoved struct {
const defaultObjectAlreadyRemovedMsg = "object already removed" const defaultObjectAlreadyRemovedMsg = "object already removed"
func (x ObjectAlreadyRemoved) Error() string { func (x *ObjectAlreadyRemoved) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultObjectAlreadyRemovedMsg msg = defaultObjectAlreadyRemovedMsg
@ -210,7 +210,7 @@ type ObjectOutOfRange struct {
const defaultObjectOutOfRangeMsg = "out of range" const defaultObjectOutOfRangeMsg = "out of range"
func (x ObjectOutOfRange) Error() string { func (x *ObjectOutOfRange) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultObjectOutOfRangeMsg msg = defaultObjectOutOfRangeMsg

View file

@ -13,7 +13,7 @@ type SessionTokenNotFound struct {
const defaultSessionTokenNotFoundMsg = "session token not found" const defaultSessionTokenNotFoundMsg = "session token not found"
func (x SessionTokenNotFound) Error() string { func (x *SessionTokenNotFound) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultSessionTokenNotFoundMsg msg = defaultSessionTokenNotFoundMsg
@ -50,7 +50,7 @@ type SessionTokenExpired struct {
const defaultSessionTokenExpiredMsg = "expired session token" const defaultSessionTokenExpiredMsg = "expired session token"
func (x SessionTokenExpired) Error() string { func (x *SessionTokenExpired) Error() string {
msg := x.v2.Message() msg := x.v2.Message()
if msg == "" { if msg == "" {
msg = defaultSessionTokenExpiredMsg msg = defaultSessionTokenExpiredMsg

View file

@ -15,7 +15,7 @@ package apistatus
// It should be noted that using direct typecasting is not a compatible approach. // It should be noted that using direct typecasting is not a compatible approach.
// //
// To transport statuses using the FrostFS API V2 protocol, see StatusV2 interface and FromStatusV2 and ToStatusV2 functions. // To transport statuses using the FrostFS API V2 protocol, see StatusV2 interface and FromStatusV2 and ToStatusV2 functions.
type Status interface{} type Status any
// ErrFromStatus converts Status instance to error if it is failed. Returns nil on successful Status. // ErrFromStatus converts Status instance to error if it is failed. Returns nil on successful Status.
// //

View file

@ -8,7 +8,7 @@ type unrecognizedStatusV2 struct {
v2 status.Status v2 status.Status
} }
func (x unrecognizedStatusV2) Error() string { func (x *unrecognizedStatusV2) Error() string {
return errMessageStatusV2("unrecognized", x.v2.Message()) return errMessageStatusV2("unrecognized", x.v2.Message())
} }

View file

@ -123,7 +123,7 @@ func ToStatusV2(st Status) *status.Status {
return internalErrorStatus return internalErrorStatus
} }
func errMessageStatusV2(code interface{}, msg string) string { func errMessageStatusV2(code any, msg string) string {
const ( const (
noMsgFmt = "status: code = %v" noMsgFmt = "status: code = %v"
msgFmt = noMsgFmt + " message = %s" msgFmt = noMsgFmt + " message = %s"

View file

@ -12,7 +12,7 @@ func TestToStatusV2(t *testing.T) {
type statusConstructor func() apistatus.Status type statusConstructor func() apistatus.Status
for _, testItem := range [...]struct { for _, testItem := range [...]struct {
status interface{} // Status or statusConstructor status any // Status or statusConstructor
codeV2 uint64 codeV2 uint64
messageV2 string messageV2 string
}{ }{
@ -165,7 +165,7 @@ func TestFromStatusV2(t *testing.T) {
type statusConstructor func() apistatus.Status type statusConstructor func() apistatus.Status
for _, testItem := range [...]struct { for _, testItem := range [...]struct {
status interface{} // Status or statusConstructor status any // Status or statusConstructor
codeV2 uint64 codeV2 uint64
messageV2 string messageV2 string
}{ }{

View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
@ -296,7 +297,7 @@ func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) {
// //
// SetAttribute overwrites existing attribute value. // SetAttribute overwrites existing attribute value.
// //
// See also Attribute, IterateAttributes. // See also Attribute, IterateAttributes, IterateUserAttributes.
func (x *Container) SetAttribute(key, value string) { func (x *Container) SetAttribute(key, value string) {
if key == "" { if key == "" {
panic("empty attribute key") panic("empty attribute key")
@ -324,7 +325,7 @@ func (x *Container) SetAttribute(key, value string) {
// Attribute reads value of the Container attribute by key. Empty result means // Attribute reads value of the Container attribute by key. Empty result means
// attribute absence. // attribute absence.
// //
// See also SetAttribute, IterateAttributes. // See also SetAttribute, IterateAttributes, IterateUserAttributes.
func (x Container) Attribute(key string) string { func (x Container) Attribute(key string) string {
attrs := x.v2.GetAttributes() attrs := x.v2.GetAttributes()
for i := range attrs { for i := range attrs {
@ -347,6 +348,21 @@ func (x Container) IterateAttributes(f func(key, val string)) {
} }
} }
// IterateUserAttributes iterates over user Container attributes and passes them
// into f. The handler MUST NOT be nil.
//
// See also SetAttribute, Attribute.
func (x Container) IterateUserAttributes(f func(key, val string)) {
attrs := x.v2.GetAttributes()
for _, attr := range attrs {
var key = attr.GetKey()
if !strings.HasPrefix(key, container.SysAttributePrefix) &&
!strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
f(key, attr.GetValue())
}
}
}
// SetName sets human-readable name of the Container. Name MUST NOT be empty. // SetName sets human-readable name of the Container. Name MUST NOT be empty.
// //
// See also Name. // See also Name.

View file

@ -150,7 +150,7 @@ func assertContainsAttribute(t *testing.T, m v2container.Container, key, val str
} }
func TestContainer_Attribute(t *testing.T) { func TestContainer_Attribute(t *testing.T) {
const attrKey1, attrKey2 = "key1", "key2" const attrKey1, attrKey2 = v2container.SysAttributePrefix + "key1", v2container.SysAttributePrefixNeoFS + "key2"
const attrVal1, attrVal2 = "val1", "val2" const attrVal1, attrVal2 = "val1", "val2"
val := containertest.Container() val := containertest.Container()
@ -158,6 +158,12 @@ func TestContainer_Attribute(t *testing.T) {
val.SetAttribute(attrKey1, attrVal1) val.SetAttribute(attrKey1, attrVal1)
val.SetAttribute(attrKey2, attrVal2) val.SetAttribute(attrKey2, attrVal2)
var i int
val.IterateUserAttributes(func(key, val string) {
i++
})
require.Equal(t, 1, i)
var msg v2container.Container var msg v2container.Container
val.WriteToV2(&msg) val.WriteToV2(&msg)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

445
doc/policy.md Normal file
View file

@ -0,0 +1,445 @@
# Placement Policy
This document describes placement policies, their purpose, syntax and semantics.
## Index
- [Introduction](#introduction)
- [Operations](#operations)
- [Basic Expressions](#basic-expressions)
- [`FILTER`](#filter)
- [`SELECT`](#select)
- [`REP`](#rep)
- [Policies](#policies)
- [The policy playground](#the-policy-playground)
- [`CBF`](#cbf)
- [`UNIQUE`](#unique)
- [More examples](#more-examples)
- [Appendix 1: Operators](#appendix-1-operators)
- [Appendix 2: Policy playground commands](#appendix-2-policy-playground-commands)
## Introduction
The purpose of a **placement policy** is to determine whichs node(s) of a frostfs system will store an object. Namely, given a **netmap** (a set of nodes) and a placement policy, a subset of those nodes is selected to store a given object. An important aspect is that since nodes in a netmap come and go due to distributed nature of the system, this selection must be deterministic and consistent, i.e. different nodes must come to the same conclusion as long as they share the same view of the netmap.
> Throughout this document, we will consider each node as a dictionary of attributes and a global unique ID.
One way to think about the placement policy, is as a pipeline of operations which begins with a set of nodes (the entire netmap) and gradually refine it into only the nodes that will be in charge of storing the object. More specifically, each operation in this pipeline takes a set of nodes and transforms it into a subset of those nodes. The transformation is done purely on the basis of the node attributes.
![Placement policy as a pipeline](./image/placement_policy.svg)
The three main operations are:
1. `FILTER`: filters a set of nodes based on their attributes.
2. `SELECT`: selects a specific amount of nodes from a set of nodes based on certain conditions.
3. `REP`: specifies how many nodes (and which ones) from a set of nodes are used to store an object.
In the next sections, we will explore each of them in detail.
## Operations
### Basic Expressions
Before exploring the operations in detail, we must get acquainted with the basic expressions that appear in a placement policy. As mentioned above, the placement policy operates solely on the basis of node attributes, and as such, basic expressions mostly revolve around node attribute comparison.
A comparison expression expresses whether a node attribute equals a specified value:
```
AttributeName Operation AttributeValue
```
For example, the following expression
```sql
City EQ 'Moscow'
```
asserts that the node attribute `City` equals the value `Moscow`.
Comparison expressions can be nested via boolean operators and parentheses. For example, the following expression:
```sql
(City EQ 'Moscow') AND (Disks GT 2)
```
asserts that the node attribute `City` equals the value `Moscow` and the node attribute `Disks` must be greater than `2`. Note that the arguments can be either a string or a number.
See [Appendix 1](#appendix-1-operators) for a complete list of supported operators.
### `FILTER`
A `FILTER` operation takes as input a set of nodes and returns a subset of those nodes. It's useful for selecting nodes that have (or lack) specific attributes. Its basic syntax is as follows:
```bnf
FILTER <expr> AS <id>
```
For example, the following filter
```sql
FILTER Color EQ 'Red' AS RedNodes
```
selects those nodes for which the `Color` attribute equals `Red`, and discards the rest. The filter's identifier is `RedNodes`, which can be used to reference it in other parts of the placement policy. For example, you could reference the above filter in another filter as follows
```sql
FILTER @RedNodes AND (City EQ 'Moscow') AS RedMoscowNodes
```
which would select those nodes for which the `Color` attribute equals `Red` and the `City` attribute equals `Moscow`. You can think of the `@` operator as embedding the referenced filter expression verbatim where it's used. This makes it easy to compose filters. However, filters can be referenced via `@` only within filter expressions. In other places you can simply use the filter identifier directly.
> ⚠️ Every filter requires a unique identifier. What would be the use of a filter that you cannot reference?
The following diagram illustrates the filter operation
![FILTER](./image/filter_illustration.svg)
where the nodes are represented as colored circles, with their color representing the value of their `Color` attribute, respectively.
> A filter referring to all nodes in the netmap always exists and can be referenced by `*`.
### `SELECT`
A `SELECT` operation specifies how many and which nodes from a subset previously obtained from a `FILTER` will be available to build replica groups for object storage. It's not that different from a `FILTER` in that it transforms a set of nodes into a subset of those, but while a `FILTER` cannot control the size of the resulting subset and other characteristics, a `SELECT` can.
Its basic syntax is as follows:
```bnf
SELECT <count> {IN (SAME|DISTINCT) <attribute>} FROM <filter> {AS <id>}
```
In a nutshell, a `SELECT` takes a filter result as input and outputs a specific number of nodes, optionally enforcing that all output nodes must either share or differ in a specific attribute. Note that only the output node count and the source filter are required.
Let's see some examples
```sql
-- Selects exactly one node from the entire netmap
SELECT 1 FROM *
-- Same as above, but with an identifier for the selection
SELECT 1 FROM * AS ONE
-- Selects two nodes from the RedOrBlueNodes filter, such that both selected nodes
-- share the same value for the Color attribute, i.e. both red or both blue.
SELECT 2 IN SAME Color FROM RedOrBlueNodes
-- Selects two nodes from the RedOrBlueNodes filter, such that the selected nodes
-- have distinct values for the Color attribute, i.e. one red and one blue.
-- The selection is also given an identifier.
SELECT 2 IN DISTINCT Color FROM RedOrBlueNodes AS MyNodes
```
The last example is illustrated in the following diagram:
![SELECT](./image/select_illustration.svg)
> At this point, notice that while `FILTER`'s output is always unique (namely, every node in the input is either filtered in or out), that is not always the case for `SELECT`. In the last example above, there is more than one way to select two nodes with distinct `Color` attribute. Because we require the output to be deterministic and consistent (so that all nodes agree on which nodes to store a given object without having to commmunicate with each other), we need a way to reach this consensus efficiently. Internally, the policy engine uses [Rendezvouz Hashing](https://en.wikipedia.org/wiki/Rendezvous_hashing) to ensure this. If you want more control over what nodes are actually selected, you can always use narrower filters/selections to ensure this.
### `REP`
A `REP` operation specifies how many copies of an object need to be stored (`REP` stands for "replica"). A placement policy can contain multiple replica operations, with each of them representing a replica group, i.e. a group of objects associated with the same replica. Following our analogy with a pipeline, `REP` operations are the sink or output nodes.
Its basic syntax is as follows:
```bnf
REP <count> {IN <select>}
```
If a select is not specified, then the entire netmap is used as input. The resulting nodes will be used to actually store objects and they constitute a replica group (or simply, "a replica").
Examples
```sql
-- A replica consisting of a single copy, stored in an arbitrary node of the netmap.
REP 1
-- A replica consisting of three copies, each stored in a different node from the selection
-- identified as 'MyNodes'.
REP 3 IN MyNodes
```
The following diagram illustrates the `REP` operation:
![REP](./image/rep_illustration.svg)
> ⚠️ Notice that although we use `REP 1` in the examples, in real life scenarios you almost always want to have more than a single node in each replica for redundancy.
## Policies
In order to specify a complete placement policy, we just need to assemble it from the operations described above.
Its basic (simplified) syntax is as follows:
```bnf
<rep>+ <select>* <filter>*
```
We begin by stating all our `REP` operations, followed by all the `SELECT` operations and finally all the `FILTER` operations. Note that this is the reverse order in which they are applied. Also note that at least one `REP` operation is required.
Here's a complete example:
```sql
REP 1 IN MyNodes
SELECT 2 IN DISTINCT Color FROM RedOrBlueNodes AS MyNodes
FILTER Color EQ 'Red' AS RedNodes
FILTER Color EQ 'Blue' AS BlueNodes
FILTER @RedNodes OR @BlueNodes AS RedOrBlueNodes
```
In additional to this basic syntax, there are a couple of additional useful options to specify which nodes and how many nodes are actually selected to store objects. We explore these in the next sections.
### The policy playground
> This section assumes you have an up-to-date version of the `frostfs-cli`.
While simple placement policies have predictable results that can be understood at a glance, more complex ones need careful consideration before deployment. In order to simplify understanding a policy's outcome and experimenting while learning, a builtin tool is provided as part of the `frostfs-cli` for this purpose: the policy playground.
For the remainder of this guide, we will use the policy playground to setup a virtual netmap (that is, one that doesn't require any networking or deployment) and test various policies. In order to visualize this netmap easily, each node will have three attributes: a character, a shape and a color
![Sample Netmap](./image/sample_netmap.svg)
We can start the policy playground as follows:
```sh
$ frostfs-cli container policy-playground
>
```
Since we didn't pass any endpoint, the initial netmap is empty, which we can verify with the `ls` command (to list the nodes in the netmap):
```sh
> ls
>
```
Nows let's add virtual nodes to represent our test netmap in the figure above
```sh
> add 01 Char:A Shape:Circle Color:Blue
> add 02 Char:B Shape:Circle Color:Green
> add 03 Char:C Shape:Circle Color:Red
> add 04 Char:D Shape:Square Color:Blue
> add 05 Char:E Shape:Square Color:Green
> add 06 Char:F Shape:Square Color:Red
> add 07 Char:G Shape:Diamond Color:Blue
> add 08 Char:H Shape:Diamond Color:Green
> add 09 Char:I Shape:Diamond Color:Red
```
and verify that the netmap now contains what we expect
```sh
> ls
1: id=06 attrs={Char:F Shape:Square Color:Red}
2: id=08 attrs={Char:H Shape:Diamond Color:Green}
3: id=01 attrs={Char:A Shape:Circle Color:Blue}
4: id=04 attrs={Char:D Shape:Square Color:Blue}
5: id=05 attrs={Char:E Shape:Square Color:Green}
6: id=09 attrs={Char:I Shape:Diamond Color:Red}
7: id=02 attrs={Char:B Shape:Circle Color:Green}
8: id=03 attrs={Char:C Shape:Circle Color:Red}
9: id=07 attrs={Char:G Shape:Diamond Color:Blue}
```
With our sample netmap setup, we can now continue.
### `CBF`
Consider the following policy:
```sql
REP 1
```
It builds a replica consisting of one copy, selected from the entire netmap. If we evaluate this policy in our sample netmap, we obtain a result which is probably unexpected:
```sh
> eval REP 1
1: [06 05 02]
```
The `eval` commands evaluates a policy and lists in a separate line the nodes selected for each `REP` operation, in the order they appear in the policy. We were expecting a single node, but we got three instead. The reason is that there's a policy-wide parameter called **container backup factor** (or CBF). This parameter is a multiplier which controls the maximum number of storage nodes: for example, if a policy requires 10 nodes and the CBF is 3, it means that the policy can store an object in up to 10 × 3 nodes. However, if there are not enough nodes and fewer (but at least 10) are used, this is not considered an error.
The default value for CBF is `3`, which explains our result above, given than every node in the netmap agrees with the policy. The CBF can be explicitly set in the policy right after the `REP` operations. For example
```sh
> eval REP 1 CBF 1
1: [06]
```
results in what we expected in the first example. On the other hand
```sh
> eval REP 1 IN MyNodes SELECT 1 IN SAME Char FROM * AS MyNodes
1: [01]
```
results in a single node despite the default CBF, because there are not enough nodes compatible with the selection.
### `UNIQUE`
Consider the following policy:
```sql
REP 1
REP 1
CBF 2
```
If we evaluate it
```sh
> eval REP 1 REP 1 CBF 2
1: [06 05]
2: [06 05]
```
we find that each replica gets two nodes, in accordance with the CBF. However, these nodes are exactly the same and this might not be desirable. In order to force the policy engine to select different nodes for each replica, we can use the `UNIQUE` option, which is specified right before the `REP` operations.
In our example, if we change it to
```sql
UNIQUE
REP 1
REP 1
CBF 2
```
and evaluate it
```sh
> eval UNIQUE REP 1 REP 1 CBF 2
1: [06 05]
2: [02 03]
```
we now find that the nodes selected for each replica are now distinct from each other.
### More examples
This section presents some more examples of placement policies and their result when applied to the sample netmap. Try to figure out the result before looking at it or evaluating the policy.
#### Example #1
```sql
REP 1 IN TwoRedNodes
SELECT 2 FROM RedNodes AS TwoRedNodes
FILTER Color EQ 'Red' AS RedNodes
```
<details>
<summary>Result</summary>
```sh
> eval REP 1 IN TwoRedNodes SELECT 2 FROM RedNodes AS TwoRedNodes FILTER Color EQ 'Red' AS RedNodes
1: [06 09 03]
```
</details>
#### Example #2
```sql
REP 1 IN TwoRedNodes
REP 1 IN TwoRedNodes
SELECT 2 FROM RedNodes AS TwoRedNodes
FILTER Color EQ 'Red' AS RedNodes
```
<details>
<summary>Result</summary>
```sh
> eval REP 1 REP 1 IN TwoRedNodes SELECT 2 FROM RedNodes AS TwoRedNodes FILTER Color EQ 'Red' AS RedNodes
1: [06 09 03]
2: [06 09 03]
```
</details>
#### Example #3
```sql
REP 2 IN MyNodes
REP 2 IN MyNodes
SELECT 2 FROM RedOrBlueNodes AS MyNodes
FILTER Color EQ 'Red' AS RedNodes
FILTER Color EQ 'Blue' AS BlueNodes
FILTER @RedNodes OR @BlueNodes AS RedOrBlueNodes
```
<details>
<summary>Result</summary>
```sh
> eval REP 2 IN MyNodes REP 2 IN MyNodes SELECT 2 FROM RedOrBlueNodes AS MyNodes FILTER Color EQ 'Red' AS RedNodes FILTER Color EQ 'Blue' AS BlueNodes FILTER @RedNodes OR @BlueNodes AS RedOrBlueNodes
1: [06 01 04 03 09 07]
2: [06 01 04 03 09 07]
```
</details>
#### Example #4
```sql
REP 2 IN MyRedNodes
REP 2 IN MyBlueNodes
CBF 1
SELECT 2 FROM RedNodes AS MyRedNodes
SELECT 2 FROM BlueNodes AS MyBlueNodes
FILTER Color EQ 'Red' AS RedNodes
FILTER Color EQ 'Blue' AS BlueNodes
```
<details>
<summary>Result</summary>
```sh
> eval REP 2 IN MyRedNodes REP 2 IN MyBlueNodes CBF 1 SELECT 2 FROM RedNodes AS MyRedNodes SELECT 2 FROM BlueNodes AS MyBlueNodes FILTER Color EQ 'Red' AS RedNodes FILTER Color EQ 'Blue' AS BlueNodes
1: [06 03]
2: [01 04]
```
</details>
#### Example #5
```sql
UNIQUE
REP 1 IN MyGreenNodes
REP 1 IN MyGreenNodes
REP 1 IN MyGreenNodes
CBF 1
SELECT 1 FROM GreenNodes AS MyGreenNodes
FILTER Color EQ 'Green' AS GreenNodes
```
<details>
<summary>Result</summary>
```sh
> eval UNIQUE REP 1 IN MyGreenNodes REP 1 IN MyGreenNodes REP 1 IN MyGreenNodes CBF 1 SELECT 1 FROM GreenNodes AS MyGreenNodes FILTER Color EQ 'Green' AS GreenNodes
1: [05]
2: [02]
3: [08]
```
</details>
#### Example #6
```sql
REP 1 IN MyNodes
REP 2
CBF 2
SELECT 1 FROM CuteNodes AS MyNodes
FILTER (Color EQ 'Blue') AND NOT (Shape EQ 'Circle' OR Shape EQ 'Square') AS CuteNodes
```
<details>
<summary>Result</summary>
```sh
eval REP 1 IN MyNodes REP 2 CBF 2 SELECT 1 FROM CuteNodes AS MyNodes FILTER (Color EQ 'Blue') AND NOT (Shape EQ 'Circle' OR Shape EQ 'Square') AS CuteNodes
1: [07]
2: [06 05 02 03]
```
</details>
## Appendix 1: Operators
Comparison operators (all binary):
- `EQ`: equals
- `NE`: not equal
- `GE`: greater or equal
- `GT`: greater than
- `LE`: less or equal
- `LT`: less than
Logical operators:
- `NOT`: negation (unary)
- `AND`: conjunction (binary)
- `OR`: disjunction (binary)
Others:
- `@`: filter reference
- `(`: left parenthesis
- `)`: right parenthesis
## Appendix 2: Policy playground commands
- `ls`: list nodes in the current netmap and their attributes
- `add`: add a node to the current netmap. If it already exists, it will be overwritten.
- `remove`: remove a node from the current netmap.
- `eval`: evaluate a placement policy on the current netmap.

62
go.mod
View file

@ -1,56 +1,46 @@
module git.frostfs.info/TrueCloudLab/frostfs-sdk-go module git.frostfs.info/TrueCloudLab/frostfs-sdk-go
go 1.18 go 1.19
require ( require (
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230418080822-bd44a3f47b85 git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb
git.frostfs.info/TrueCloudLab/hrw v1.2.0 git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0
git.frostfs.info/TrueCloudLab/hrw v1.2.1
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 git.frostfs.info/TrueCloudLab/tzhash v1.8.0
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 github.com/antlr4-go/antlr/v4 v4.13.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hashicorp/golang-lru/v2 v2.0.1 github.com/hashicorp/golang-lru/v2 v2.0.2
github.com/mr-tron/base58 v1.2.0 github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/neo-go v0.100.1 github.com/nspcc-dev/neo-go v0.101.2-0.20230601131642-a0117042e8fc
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.3
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.24.0 go.uber.org/zap v1.24.0
google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.30.0
) )
require ( require (
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/benbjohnson/clock v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb // indirect github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230615193820-9185820289ce // indirect
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/twmb/murmur3 v1.1.8 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect go.uber.org/atomic v1.10.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect go.uber.org/goleak v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect golang.org/x/crypto v0.9.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
go.opentelemetry.io/otel/sdk v1.14.0 // indirect golang.org/x/net v0.10.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect golang.org/x/sync v0.2.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect golang.org/x/sys v0.8.0 // indirect
go.uber.org/multierr v1.9.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/crypto v0.4.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

133
go.sum
View file

@ -31,18 +31,14 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.0 h1:oZ0/KiaFeveXRLi5VVEpuLSHczeFyWx4HDl9wTJUtsE= git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44 h1:v6JqBD/VzZx3QSxbaXnUwnnJ1KEYheU4LzLGr3IhsAE=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.0/go.mod h1:sPyITTmQT662ZI38ud2aoE1SUCAr1mO5xV8P4nzLkKI= git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44/go.mod h1:pKJJRLOChW4zDQsAt1e8k/snWKljJtpkiPfxV53ngjI=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230413090614-b3ccd0166f50 h1:wt7ywk0w2y2scTt7LtlObV2tWUDbme5Gm3a3Llf0C14=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230413090614-b3ccd0166f50/go.mod h1:sPyITTmQT662ZI38ud2aoE1SUCAr1mO5xV8P4nzLkKI=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230418080822-bd44a3f47b85 h1:77lvdk0kMhnUgtnmqEcAPXPQaGlt24goMPu2+E5WRTk=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230418080822-bd44a3f47b85/go.mod h1:sPyITTmQT662ZI38ud2aoE1SUCAr1mO5xV8P4nzLkKI=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb h1:S/TrbOOu9qEXZRZ9/Ddw7crnxbBUQLo68PSzQWYrc9M= git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb h1:S/TrbOOu9qEXZRZ9/Ddw7crnxbBUQLo68PSzQWYrc9M=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb/go.mod h1:nkR5gaGeez3Zv2SE7aceP0YwxG2FzIB5cGKpQO2vV2o= git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb/go.mod h1:nkR5gaGeez3Zv2SE7aceP0YwxG2FzIB5cGKpQO2vV2o=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
git.frostfs.info/TrueCloudLab/hrw v1.2.0 h1:KvAES7xIqmQBGd2q8KanNosD9+4BhU/zqD5Kt5KSflk= git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
git.frostfs.info/TrueCloudLab/hrw v1.2.0/go.mod h1:mq2sbvYfO+BB6iFZwYBkgC0yc6mJNx+qZi4jW918m+Y= git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA= git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=
@ -53,7 +49,6 @@ github.com/CityOfZion/neo-go v0.62.1-pre.0.20191114145240-e740fbe708f8/go.mod h1
github.com/CityOfZion/neo-go v0.70.1-pre.0.20191209120015-fccb0085941e/go.mod h1:0enZl0az8xA6PVkwzEOwPWVJGqlt/GO4hA4kmQ5Xzig= github.com/CityOfZion/neo-go v0.70.1-pre.0.20191209120015-fccb0085941e/go.mod h1:0enZl0az8xA6PVkwzEOwPWVJGqlt/GO4hA4kmQ5Xzig=
github.com/CityOfZion/neo-go v0.70.1-pre.0.20191212173117-32ac01130d4c/go.mod h1:JtlHfeqLywZLswKIKFnAp+yzezY4Dji9qlfQKB2OD/I= github.com/CityOfZion/neo-go v0.70.1-pre.0.20191212173117-32ac01130d4c/go.mod h1:JtlHfeqLywZLswKIKFnAp+yzezY4Dji9qlfQKB2OD/I=
github.com/CityOfZion/neo-go v0.71.1-pre.0.20200129171427-f773ec69fb84/go.mod h1:FLI526IrRWHmcsO+mHsCbj64pJZhwQFTLJZu+A4PGOA= github.com/CityOfZion/neo-go v0.71.1-pre.0.20200129171427-f773ec69fb84/go.mod h1:FLI526IrRWHmcsO+mHsCbj64pJZhwQFTLJZu+A4PGOA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs= github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs=
@ -68,8 +63,8 @@ github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGn
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521073959-f0d4d129b7f1/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521073959-f0d4d129b7f1/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -87,10 +82,7 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -100,11 +92,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -112,15 +100,13 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
@ -142,18 +128,11 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-redis/redis v6.10.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.10.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -180,8 +159,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -217,18 +197,17 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -289,11 +268,11 @@ github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkP
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg= github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM= github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM=
github.com/nspcc-dev/neo-go v0.99.4/go.mod h1:mKTolfRUfKjFso5HPvGSQtUZc70n0VKBMs16eGuC5gA= github.com/nspcc-dev/neo-go v0.99.4/go.mod h1:mKTolfRUfKjFso5HPvGSQtUZc70n0VKBMs16eGuC5gA=
github.com/nspcc-dev/neo-go v0.100.1 h1:yugxbQRdzM+ObVa5mtr9/n4rYjxSIrryne8MVr9NBwU= github.com/nspcc-dev/neo-go v0.101.2-0.20230601131642-a0117042e8fc h1:fySIWvUQsitK5e5qYIHnTDCXuPpwzz89SEUEIyY11sg=
github.com/nspcc-dev/neo-go v0.100.1/go.mod h1:Nnp7F4e9IBccsgtCeLtUWV+0T6gk1PtP5HRtA13hUfc= github.com/nspcc-dev/neo-go v0.101.2-0.20230601131642-a0117042e8fc/go.mod h1:s9QhjMC784MWqTURovMbyYduIJc86mnCruxcMiAebpc=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230615193820-9185820289ce h1:vLGuUNDkmQrWMa4rr4vTd1u8ULqejWxVmNz1L7ocTEI=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20230615193820-9185820289ce/go.mod h1:ZUuXOkdtHZgaC13za/zMgXfQFncZ0jLzfQTe+OsDOtg=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs= github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs= github.com/nspcc-dev/neofs-api-go/v2 v2.11.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA=
@ -356,26 +335,22 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=
@ -395,23 +370,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 h1:/fXHZHGvro6MVqV34fJzDhi7sHGpX3Ej/Qjmfn003ho=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0/go.mod h1:UFG7EBMRdXyFstOwH028U0sVf+AvukSGhF0g8+dmNG8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 h1:TKf2uAs2ueguzLaxOCBXNpHxfO/aC7PAdDsSH0IbeRQ=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0/go.mod h1:HrbCVv40OOLTABmOn1ZWty6CHXkU8DK/Urc43tHug70=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8RXX3Mu9apKVtOkM6WSFESLM8K3wNQyOU8sWHcc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 h1:sEL90JjOO/4yhquXl5zTAkLLsZ5+MycAgX99SDsxGc8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0/go.mod h1:oCslUcizYdpKYyS9e8srZEqM6BB8fq41VJBjLAE6z1w=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -419,10 +378,11 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
@ -438,8 +398,8 @@ golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -450,8 +410,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c h1:Govq2W3bnHJimHT2ium65kXcI7ZzTniZHcFATnLJM0Q= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -507,20 +467,18 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -534,8 +492,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -583,17 +541,15 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -602,11 +558,10 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -710,9 +665,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -727,11 +681,9 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -745,8 +697,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/abiosoft/ishell.v2 v2.0.0/go.mod h1:sFp+cGtH6o4s1FtpVPTMcHq2yue+c4DGOVohJCPUzwY= gopkg.in/abiosoft/ishell.v2 v2.0.0/go.mod h1:sFp+cGtH6o4s1FtpVPTMcHq2yue+c4DGOVohJCPUzwY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

59
netmap/bench_test.go Normal file
View file

@ -0,0 +1,59 @@
package netmap
import (
"testing"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
"github.com/stretchr/testify/require"
)
func BenchmarkNetmap_ContainerNodes(b *testing.B) {
nodes := []NodeInfo{
nodeInfoFromAttributes("Country", "Russia", "Order", "1"),
nodeInfoFromAttributes("Country", "Germany", "Order", "2"),
nodeInfoFromAttributes("Country", "Russia", "Order", "3"),
nodeInfoFromAttributes("Country", "France", "Order", "4"),
nodeInfoFromAttributes("Country", "France", "Order", "5"),
nodeInfoFromAttributes("Country", "Russia", "Order", "6"),
nodeInfoFromAttributes("Country", "Russia", "Order", "7"),
nodeInfoFromAttributes("Country", "Germany", "Order", "8"),
nodeInfoFromAttributes("Country", "Germany", "Order", "9"),
nodeInfoFromAttributes("Country", "Russia", "Order", "10"),
nodeInfoFromAttributes("Country", "China", "Order", "11"),
nodeInfoFromAttributes("Country", "China", "Order", "12"),
nodeInfoFromAttributes("Country", "Finland", "Order", "13"),
nodeInfoFromAttributes("Country", "Finland", "Order", "14"),
nodeInfoFromAttributes("Country", "España", "Order", "15"),
nodeInfoFromAttributes("Country", "España", "Order", "16"),
}
var nm NetMap
nm.SetNodes(nodes)
policies := []string{
`REP 2`,
`REP 2 IN X CBF 2 SELECT 2 FROM * AS X`,
}
cnr := cidtest.ID()
pivot := make([]byte, 32)
cnr.Encode(pivot)
for i := range policies {
b.Run(policies[i], func(b *testing.B) {
var p PlacementPolicy
require.NoError(b, p.DecodeString(policies[i]))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := nm.ContainerNodes(p, pivot)
if err != nil {
b.Fatal(err)
}
}
})
}
}

View file

@ -35,6 +35,11 @@ type context struct {
// container backup factor // container backup factor
cbf uint32 cbf uint32
// nodes already used in previous selections, which is needed when the placement
// policy uses the UNIQUE flag. Nodes marked as used are not used in subsequent
// base selections.
usedNodes map[uint64]bool
} }
// Various validation errors. // Various validation errors.
@ -58,6 +63,7 @@ func newContext(nm NetMap) *context {
numCache: make(map[string]uint64), numCache: make(map[string]uint64),
weightFunc: defaultWeightFunc(nm.nodes), weightFunc: defaultWeightFunc(nm.nodes),
usedNodes: make(map[uint64]bool),
} }
} }
@ -76,6 +82,12 @@ func (c *context) setCBF(cbf uint32) {
} }
} }
func (c *context) addUsedNodes(ns ...NodeInfo) {
for _, n := range ns {
c.usedNodes[n.hash] = true
}
}
func defaultWeightFunc(ns nodes) weightFunc { func defaultWeightFunc(ns nodes) weightFunc {
mean := newMeanAgg() mean := newMeanAgg()
min := newMinAgg() min := newMinAgg()

View file

@ -39,7 +39,7 @@ func (c *context) processFilter(f netmap.Filter, top bool) error {
inner := f.GetFilters() inner := f.GetFilters()
switch op := f.GetOp(); op { switch op := f.GetOp(); op {
case netmap.AND, netmap.OR: case netmap.AND, netmap.OR, netmap.NOT:
for i := range inner { for i := range inner {
if err := c.processFilter(inner[i], false); err != nil { if err := c.processFilter(inner[i], false); err != nil {
return fmt.Errorf("process inner filter #%d: %w", i, err) return fmt.Errorf("process inner filter #%d: %w", i, err)
@ -79,6 +79,13 @@ func (c *context) processFilter(f netmap.Filter, top bool) error {
// and missing node properties are considered as a regular fail. // and missing node properties are considered as a regular fail.
func (c *context) match(f *netmap.Filter, b NodeInfo) bool { func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
switch f.GetOp() { switch f.GetOp() {
case netmap.NOT:
inner := f.GetFilters()
fSub := &inner[0]
if name := inner[0].GetName(); name != "" {
fSub = c.processedFilters[name]
}
return !c.match(fSub, b)
case netmap.AND, netmap.OR: case netmap.AND, netmap.OR:
inner := f.GetFilters() inner := f.GetFilters()
for i := range inner { for i := range inner {

View file

@ -158,6 +158,57 @@ func (m NetMap) PlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeIn
return result, nil return result, nil
} }
// SelectFilterNodes returns a two-dimensional list of nodes as a result of applying the
// given SelectFilterExpr to the NetMap.
// If the SelectFilterExpr contains only filters, the result contains a single row with the
// result of the last filter application.
// If the SelectFilterExpr contains only selectors, the result contains the selection rows
// of the last select application.
func (m NetMap) SelectFilterNodes(expr *SelectFilterExpr) ([][]NodeInfo, error) {
p := PlacementPolicy{
filters: expr.filters,
}
if expr.selector != nil {
p.selectors = append(p.selectors, *expr.selector)
}
c := newContext(m)
c.setCBF(expr.cbf)
if err := c.processFilters(p); err != nil {
return nil, err
}
if err := c.processSelectors(p); err != nil {
return nil, err
}
if expr.selector == nil {
var ret []NodeInfo
lastFilter := expr.filters[len(expr.filters)-1]
for _, ni := range m.nodes {
if c.match(c.processedFilters[lastFilter.GetName()], ni) {
ret = append(ret, ni)
}
}
return [][]NodeInfo{ret}, nil
}
sel, err := c.getSelection(*c.processedSelectors[expr.selector.GetName()])
if err != nil {
return nil, err
}
var ret [][]NodeInfo
for i, ns := range sel {
ret = append(ret, []NodeInfo{})
for _, n := range ns {
ret[i] = append(ret[i], n)
}
}
return ret, nil
}
// ContainerNodes returns two-dimensional list of nodes as a result of applying // ContainerNodes returns two-dimensional list of nodes as a result of applying
// given PlacementPolicy to the NetMap. Each line of the list corresponds to a // given PlacementPolicy to the NetMap. Each line of the list corresponds to a
// replica descriptor. Line order corresponds to order of ReplicaDescriptor list // replica descriptor. Line order corresponds to order of ReplicaDescriptor list
@ -181,10 +232,13 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, e
result := make([][]NodeInfo, len(p.replicas)) result := make([][]NodeInfo, len(p.replicas))
// Note that the cached selectors are not used when the policy contains the UNIQUE flag.
// This is necessary because each selection vector affects potentially the subsequent vectors
// and thus we call getSelection in such case, in order to take into account nodes previously
// marked as used by earlier replicas.
for i := range p.replicas { for i := range p.replicas {
sName := p.replicas[i].GetSelector() sName := p.replicas[i].GetSelector()
if sName == "" { if sName == "" {
if len(p.selectors) == 0 {
var s netmap.Selector var s netmap.Selector
s.SetCount(p.replicas[i].GetCount()) s.SetCount(p.replicas[i].GetCount())
s.SetFilter(mainFilterName) s.SetFilter(mainFilterName)
@ -194,23 +248,30 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, e
return nil, err return nil, err
} }
result[i] = flattenNodes(nodes) result[i] = append(result[i], flattenNodes(nodes)...)
}
for i := range p.selectors { if p.unique {
result[i] = append(result[i], flattenNodes(c.selections[p.selectors[i].GetName()])...) c.addUsedNodes(result[i]...)
} }
continue continue
} }
if p.unique {
nodes, err := c.getSelection(*c.processedSelectors[sName])
if err != nil {
return nil, err
}
result[i] = append(result[i], flattenNodes(nodes)...)
c.addUsedNodes(result[i]...)
} else {
nodes, ok := c.selections[sName] nodes, ok := c.selections[sName]
if !ok { if !ok {
return nil, fmt.Errorf("selector not found: REPLICA '%s'", sName) return nil, fmt.Errorf("selector not found: REPLICA '%s'", sName)
} }
result[i] = append(result[i], flattenNodes(nodes)...) result[i] = append(result[i], flattenNodes(nodes)...)
} }
}
return result, nil return result, nil
} }

View file

@ -61,16 +61,16 @@ func TestNetworkInfo_MsPerBlock(t *testing.T) {
} }
func testConfigValue(t *testing.T, func testConfigValue(t *testing.T,
getter func(x NetworkInfo) interface{}, getter func(x NetworkInfo) any,
setter func(x *NetworkInfo, val interface{}), setter func(x *NetworkInfo, val any),
val1, val2 interface{}, val1, val2 any,
v2Key string, v2Val func(val interface{}) []byte, v2Key string, v2Val func(val any) []byte,
) { ) {
var x NetworkInfo var x NetworkInfo
require.Zero(t, getter(x)) require.Zero(t, getter(x))
checkVal := func(exp interface{}) { checkVal := func(exp any) {
require.EqualValues(t, exp, getter(x)) require.EqualValues(t, exp, getter(x))
var m netmap.NetworkInfo var m netmap.NetworkInfo
@ -97,10 +97,10 @@ func testConfigValue(t *testing.T,
func TestNetworkInfo_AuditFee(t *testing.T) { func TestNetworkInfo_AuditFee(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.AuditFee() }, func(x NetworkInfo) any { return x.AuditFee() },
func(info *NetworkInfo, val interface{}) { info.SetAuditFee(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetAuditFee(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"AuditFee", func(val interface{}) []byte { "AuditFee", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -110,10 +110,10 @@ func TestNetworkInfo_AuditFee(t *testing.T) {
func TestNetworkInfo_StoragePrice(t *testing.T) { func TestNetworkInfo_StoragePrice(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.StoragePrice() }, func(x NetworkInfo) any { return x.StoragePrice() },
func(info *NetworkInfo, val interface{}) { info.SetStoragePrice(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetStoragePrice(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"BasicIncomeRate", func(val interface{}) []byte { "BasicIncomeRate", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -123,10 +123,10 @@ func TestNetworkInfo_StoragePrice(t *testing.T) {
func TestNetworkInfo_ContainerFee(t *testing.T) { func TestNetworkInfo_ContainerFee(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.ContainerFee() }, func(x NetworkInfo) any { return x.ContainerFee() },
func(info *NetworkInfo, val interface{}) { info.SetContainerFee(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetContainerFee(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"ContainerFee", func(val interface{}) []byte { "ContainerFee", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -136,10 +136,10 @@ func TestNetworkInfo_ContainerFee(t *testing.T) {
func TestNetworkInfo_NamedContainerFee(t *testing.T) { func TestNetworkInfo_NamedContainerFee(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.NamedContainerFee() }, func(x NetworkInfo) any { return x.NamedContainerFee() },
func(info *NetworkInfo, val interface{}) { info.SetNamedContainerFee(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetNamedContainerFee(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"ContainerAliasFee", func(val interface{}) []byte { "ContainerAliasFee", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -149,10 +149,10 @@ func TestNetworkInfo_NamedContainerFee(t *testing.T) {
func TestNetworkInfo_IRCandidateFee(t *testing.T) { func TestNetworkInfo_IRCandidateFee(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.IRCandidateFee() }, func(x NetworkInfo) any { return x.IRCandidateFee() },
func(info *NetworkInfo, val interface{}) { info.SetIRCandidateFee(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetIRCandidateFee(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"InnerRingCandidateFee", func(val interface{}) []byte { "InnerRingCandidateFee", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -162,10 +162,10 @@ func TestNetworkInfo_IRCandidateFee(t *testing.T) {
func TestNetworkInfo_MaxObjectSize(t *testing.T) { func TestNetworkInfo_MaxObjectSize(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.MaxObjectSize() }, func(x NetworkInfo) any { return x.MaxObjectSize() },
func(info *NetworkInfo, val interface{}) { info.SetMaxObjectSize(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetMaxObjectSize(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"MaxObjectSize", func(val interface{}) []byte { "MaxObjectSize", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -175,10 +175,10 @@ func TestNetworkInfo_MaxObjectSize(t *testing.T) {
func TestNetworkInfo_WithdrawalFee(t *testing.T) { func TestNetworkInfo_WithdrawalFee(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.WithdrawalFee() }, func(x NetworkInfo) any { return x.WithdrawalFee() },
func(info *NetworkInfo, val interface{}) { info.SetWithdrawalFee(val.(uint64)) }, func(info *NetworkInfo, val any) { info.SetWithdrawalFee(val.(uint64)) },
uint64(1), uint64(2), uint64(1), uint64(2),
"WithdrawFee", func(val interface{}) []byte { "WithdrawFee", func(val any) []byte {
data := make([]byte, 8) data := make([]byte, 8)
binary.LittleEndian.PutUint64(data, val.(uint64)) binary.LittleEndian.PutUint64(data, val.(uint64))
return data return data
@ -188,14 +188,14 @@ func TestNetworkInfo_WithdrawalFee(t *testing.T) {
func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) { func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.HomomorphicHashingDisabled() }, func(x NetworkInfo) any { return x.HomomorphicHashingDisabled() },
func(info *NetworkInfo, val interface{}) { func(info *NetworkInfo, val any) {
if val.(bool) { if val.(bool) {
info.DisableHomomorphicHashing() info.DisableHomomorphicHashing()
} }
}, },
true, true, // it is impossible to enable hashing true, true, // it is impossible to enable hashing
"HomomorphicHashingDisabled", func(val interface{}) []byte { "HomomorphicHashingDisabled", func(val any) []byte {
data := make([]byte, 1) data := make([]byte, 1)
if val.(bool) { if val.(bool) {
@ -209,14 +209,14 @@ func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) {
func TestNetworkInfo_MaintenanceModeAllowed(t *testing.T) { func TestNetworkInfo_MaintenanceModeAllowed(t *testing.T) {
testConfigValue(t, testConfigValue(t,
func(x NetworkInfo) interface{} { return x.MaintenanceModeAllowed() }, func(x NetworkInfo) any { return x.MaintenanceModeAllowed() },
func(info *NetworkInfo, val interface{}) { func(info *NetworkInfo, val any) {
if val.(bool) { if val.(bool) {
info.AllowMaintenanceMode() info.AllowMaintenanceMode()
} }
}, },
true, true, true, true,
"MaintenanceModeAllowed", func(val interface{}) []byte { "MaintenanceModeAllowed", func(val any) []byte {
if val.(bool) { if val.(bool) {
return []byte{1} return []byte{1}
} }

View file

@ -4,7 +4,9 @@ options {
tokenVocab = QueryLexer; tokenVocab = QueryLexer;
} }
policy: repStmt+ cbfStmt? selectStmt* filterStmt* EOF; policy: UNIQUE? repStmt+ cbfStmt? selectStmt* filterStmt* EOF;
selectFilterExpr: cbfStmt? selectStmt? filterStmt* EOF;
repStmt: repStmt:
REP Count = NUMBER1 // number of object replicas REP Count = NUMBER1 // number of object replicas
@ -22,7 +24,8 @@ selectStmt:
clause: CLAUSE_SAME | CLAUSE_DISTINCT; // nodes from distinct buckets clause: CLAUSE_SAME | CLAUSE_DISTINCT; // nodes from distinct buckets
filterExpr: filterExpr:
F1 = filterExpr Op = AND_OP F2 = filterExpr Op = NOT_OP '(' F1 = filterExpr ')'
| F1 = filterExpr Op = AND_OP F2 = filterExpr
| F1 = filterExpr Op = OR_OP F2 = filterExpr | F1 = filterExpr Op = OR_OP F2 = filterExpr
| '(' Inner = filterExpr ')' | '(' Inner = filterExpr ')'
| expr | expr

Binary file not shown.

Binary file not shown.

View file

@ -1,9 +1,11 @@
lexer grammar QueryLexer; lexer grammar QueryLexer;
NOT_OP : 'NOT';
AND_OP : 'AND'; AND_OP : 'AND';
OR_OP : 'OR'; OR_OP : 'OR';
SIMPLE_OP : 'EQ' | 'NE' | 'GE' | 'GT' | 'LT' | 'LE'; SIMPLE_OP : 'EQ' | 'NE' | 'GE' | 'GT' | 'LT' | 'LE';
UNIQUE : 'UNIQUE';
REP : 'REP'; REP : 'REP';
IN : 'IN'; IN : 'IN';
AS : 'AS'; AS : 'AS';

Binary file not shown.

Binary file not shown.

View file

@ -1,4 +1,4 @@
package parser package parser
// ANTLR can be downloaded from https://www.antlr.org/download/antlr-4.11.1-complete.jar // ANTLR can be downloaded from https://www.antlr.org/download/antlr-4.13.0-complete.jar
//go:generate java -Xmx500M -cp "./antlr-4.11.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool -Dlanguage=Go -visitor QueryLexer.g4 Query.g4 //go:generate java -Xmx500M -cp "./antlr-4.13.0-complete.jar:$CLASSPATH" org.antlr.v4.Tool -Dlanguage=Go -no-listener -visitor QueryLexer.g4 Query.g4

View file

@ -1,106 +0,0 @@
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
package parser // Query
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
// BaseQueryListener is a complete listener for a parse tree produced by Query.
type BaseQueryListener struct{}
var _ QueryListener = &BaseQueryListener{}
// VisitTerminal is called when a terminal node is visited.
func (s *BaseQueryListener) VisitTerminal(node antlr.TerminalNode) {}
// VisitErrorNode is called when an error node is visited.
func (s *BaseQueryListener) VisitErrorNode(node antlr.ErrorNode) {}
// EnterEveryRule is called when any rule is entered.
func (s *BaseQueryListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
// ExitEveryRule is called when any rule is exited.
func (s *BaseQueryListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
// EnterPolicy is called when production policy is entered.
func (s *BaseQueryListener) EnterPolicy(ctx *PolicyContext) {}
// ExitPolicy is called when production policy is exited.
func (s *BaseQueryListener) ExitPolicy(ctx *PolicyContext) {}
// EnterRepStmt is called when production repStmt is entered.
func (s *BaseQueryListener) EnterRepStmt(ctx *RepStmtContext) {}
// ExitRepStmt is called when production repStmt is exited.
func (s *BaseQueryListener) ExitRepStmt(ctx *RepStmtContext) {}
// EnterCbfStmt is called when production cbfStmt is entered.
func (s *BaseQueryListener) EnterCbfStmt(ctx *CbfStmtContext) {}
// ExitCbfStmt is called when production cbfStmt is exited.
func (s *BaseQueryListener) ExitCbfStmt(ctx *CbfStmtContext) {}
// EnterSelectStmt is called when production selectStmt is entered.
func (s *BaseQueryListener) EnterSelectStmt(ctx *SelectStmtContext) {}
// ExitSelectStmt is called when production selectStmt is exited.
func (s *BaseQueryListener) ExitSelectStmt(ctx *SelectStmtContext) {}
// EnterClause is called when production clause is entered.
func (s *BaseQueryListener) EnterClause(ctx *ClauseContext) {}
// ExitClause is called when production clause is exited.
func (s *BaseQueryListener) ExitClause(ctx *ClauseContext) {}
// EnterFilterExpr is called when production filterExpr is entered.
func (s *BaseQueryListener) EnterFilterExpr(ctx *FilterExprContext) {}
// ExitFilterExpr is called when production filterExpr is exited.
func (s *BaseQueryListener) ExitFilterExpr(ctx *FilterExprContext) {}
// EnterFilterStmt is called when production filterStmt is entered.
func (s *BaseQueryListener) EnterFilterStmt(ctx *FilterStmtContext) {}
// ExitFilterStmt is called when production filterStmt is exited.
func (s *BaseQueryListener) ExitFilterStmt(ctx *FilterStmtContext) {}
// EnterExpr is called when production expr is entered.
func (s *BaseQueryListener) EnterExpr(ctx *ExprContext) {}
// ExitExpr is called when production expr is exited.
func (s *BaseQueryListener) ExitExpr(ctx *ExprContext) {}
// EnterFilterKey is called when production filterKey is entered.
func (s *BaseQueryListener) EnterFilterKey(ctx *FilterKeyContext) {}
// ExitFilterKey is called when production filterKey is exited.
func (s *BaseQueryListener) ExitFilterKey(ctx *FilterKeyContext) {}
// EnterFilterValue is called when production filterValue is entered.
func (s *BaseQueryListener) EnterFilterValue(ctx *FilterValueContext) {}
// ExitFilterValue is called when production filterValue is exited.
func (s *BaseQueryListener) ExitFilterValue(ctx *FilterValueContext) {}
// EnterNumber is called when production number is entered.
func (s *BaseQueryListener) EnterNumber(ctx *NumberContext) {}
// ExitNumber is called when production number is exited.
func (s *BaseQueryListener) ExitNumber(ctx *NumberContext) {}
// EnterKeyword is called when production keyword is entered.
func (s *BaseQueryListener) EnterKeyword(ctx *KeywordContext) {}
// ExitKeyword is called when production keyword is exited.
func (s *BaseQueryListener) ExitKeyword(ctx *KeywordContext) {}
// EnterIdent is called when production ident is entered.
func (s *BaseQueryListener) EnterIdent(ctx *IdentContext) {}
// ExitIdent is called when production ident is exited.
func (s *BaseQueryListener) ExitIdent(ctx *IdentContext) {}
// EnterIdentWC is called when production identWC is entered.
func (s *BaseQueryListener) EnterIdentWC(ctx *IdentWCContext) {}
// ExitIdentWC is called when production identWC is exited.
func (s *BaseQueryListener) ExitIdentWC(ctx *IdentWCContext) {}

View file

@ -1,8 +1,8 @@
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT. // Code generated from Query.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser // Query package parser // Query
import "github.com/antlr/antlr4/runtime/Go/antlr/v4" import "github.com/antlr4-go/antlr/v4"
type BaseQueryVisitor struct { type BaseQueryVisitor struct {
*antlr.BaseParseTreeVisitor *antlr.BaseParseTreeVisitor
@ -12,6 +12,10 @@ func (v *BaseQueryVisitor) VisitPolicy(ctx *PolicyContext) interface{} {
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }
func (v *BaseQueryVisitor) VisitSelectFilterExpr(ctx *SelectFilterExprContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseQueryVisitor) VisitRepStmt(ctx *RepStmtContext) interface{} { func (v *BaseQueryVisitor) VisitRepStmt(ctx *RepStmtContext) interface{} {
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }

View file

@ -1,13 +1,12 @@
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT. // Code generated from QueryLexer.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser package parser
import ( import (
"fmt" "fmt"
"github.com/antlr4-go/antlr/v4"
"sync" "sync"
"unicode" "unicode"
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
) )
// Suppress unused import error // Suppress unused import error
@ -22,134 +21,141 @@ type QueryLexer struct {
// TODO: EOF string // TODO: EOF string
} }
var querylexerLexerStaticData struct { var QueryLexerLexerStaticData struct {
once sync.Once once sync.Once
serializedATN []int32 serializedATN []int32
channelNames []string ChannelNames []string
modeNames []string ModeNames []string
literalNames []string LiteralNames []string
symbolicNames []string SymbolicNames []string
ruleNames []string RuleNames []string
predictionContextCache *antlr.PredictionContextCache PredictionContextCache *antlr.PredictionContextCache
atn *antlr.ATN atn *antlr.ATN
decisionToDFA []*antlr.DFA decisionToDFA []*antlr.DFA
} }
func querylexerLexerInit() { func querylexerLexerInit() {
staticData := &querylexerLexerStaticData staticData := &QueryLexerLexerStaticData
staticData.channelNames = []string{ staticData.ChannelNames = []string{
"DEFAULT_TOKEN_CHANNEL", "HIDDEN", "DEFAULT_TOKEN_CHANNEL", "HIDDEN",
} }
staticData.modeNames = []string{ staticData.ModeNames = []string{
"DEFAULT_MODE", "DEFAULT_MODE",
} }
staticData.literalNames = []string{ staticData.LiteralNames = []string{
"", "'AND'", "'OR'", "", "'REP'", "'IN'", "'AS'", "'CBF'", "'SELECT'", "", "'NOT'", "'AND'", "'OR'", "", "'UNIQUE'", "'REP'", "'IN'", "'AS'",
"'FROM'", "'FILTER'", "'*'", "'SAME'", "'DISTINCT'", "'('", "')'", "'@'", "'CBF'", "'SELECT'", "'FROM'", "'FILTER'", "'*'", "'SAME'", "'DISTINCT'",
"", "", "'0'", "'('", "')'", "'@'", "", "", "'0'",
} }
staticData.symbolicNames = []string{ staticData.SymbolicNames = []string{
"", "AND_OP", "OR_OP", "SIMPLE_OP", "REP", "IN", "AS", "CBF", "SELECT", "", "NOT_OP", "AND_OP", "OR_OP", "SIMPLE_OP", "UNIQUE", "REP", "IN",
"FROM", "FILTER", "WILDCARD", "CLAUSE_SAME", "CLAUSE_DISTINCT", "L_PAREN", "AS", "CBF", "SELECT", "FROM", "FILTER", "WILDCARD", "CLAUSE_SAME",
"R_PAREN", "AT", "IDENT", "NUMBER1", "ZERO", "STRING", "WS", "CLAUSE_DISTINCT", "L_PAREN", "R_PAREN", "AT", "IDENT", "NUMBER1", "ZERO",
"STRING", "WS",
} }
staticData.ruleNames = []string{ staticData.RuleNames = []string{
"AND_OP", "OR_OP", "SIMPLE_OP", "REP", "IN", "AS", "CBF", "SELECT", "NOT_OP", "AND_OP", "OR_OP", "SIMPLE_OP", "UNIQUE", "REP", "IN", "AS",
"FROM", "FILTER", "WILDCARD", "CLAUSE_SAME", "CLAUSE_DISTINCT", "L_PAREN", "CBF", "SELECT", "FROM", "FILTER", "WILDCARD", "CLAUSE_SAME", "CLAUSE_DISTINCT",
"R_PAREN", "AT", "IDENT", "Digit", "Nondigit", "NUMBER1", "ZERO", "STRING", "L_PAREN", "R_PAREN", "AT", "IDENT", "Digit", "Nondigit", "NUMBER1",
"ESC", "UNICODE", "HEX", "SAFECODEPOINTSINGLE", "SAFECODEPOINTDOUBLE", "ZERO", "STRING", "ESC", "UNICODE", "HEX", "SAFECODEPOINTSINGLE", "SAFECODEPOINTDOUBLE",
"WS", "WS",
} }
staticData.predictionContextCache = antlr.NewPredictionContextCache() staticData.PredictionContextCache = antlr.NewPredictionContextCache()
staticData.serializedATN = []int32{ staticData.serializedATN = []int32{
4, 0, 21, 198, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 0, 23, 213, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15,
7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7,
20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25,
2, 26, 7, 26, 2, 27, 7, 27, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 1, 0, 1, 0, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1,
2, 77, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 85, 8, 3, 1, 4,
1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6,
1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9,
1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11,
12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1,
1, 16, 1, 16, 1, 16, 5, 16, 137, 8, 16, 10, 16, 12, 16, 140, 9, 16, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15,
17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 5, 19, 148, 8, 19, 10, 19, 12, 19, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 5, 18, 152, 8,
151, 9, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 5, 21, 158, 8, 21, 10, 21, 18, 10, 18, 12, 18, 155, 9, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21,
12, 21, 161, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 167, 8, 21, 10, 5, 21, 163, 8, 21, 10, 21, 12, 21, 166, 9, 21, 1, 22, 1, 22, 1, 23, 1,
21, 12, 21, 170, 9, 21, 1, 21, 3, 21, 173, 8, 21, 1, 22, 1, 22, 1, 22, 23, 1, 23, 5, 23, 173, 8, 23, 10, 23, 12, 23, 176, 9, 23, 1, 23, 1, 23,
3, 22, 178, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 1, 23, 1, 23, 5, 23, 182, 8, 23, 10, 23, 12, 23, 185, 9, 23, 1, 23, 3,
24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 4, 27, 193, 8, 27, 11, 27, 12, 27, 23, 188, 8, 23, 1, 24, 1, 24, 1, 24, 3, 24, 193, 8, 24, 1, 25, 1, 25, 1,
194, 1, 27, 1, 27, 0, 0, 28, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29,
15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 4, 29, 208, 8, 29, 11, 29, 12, 29, 209, 1, 29, 1, 29, 0, 0, 30, 1, 1, 3,
17, 35, 0, 37, 0, 39, 18, 41, 19, 43, 20, 45, 0, 47, 0, 49, 0, 51, 0, 53, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12,
0, 55, 21, 1, 0, 8, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 1, 0, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 0, 41, 0, 43,
49, 57, 9, 0, 34, 34, 39, 39, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 20, 45, 21, 47, 22, 49, 0, 51, 0, 53, 0, 55, 0, 57, 0, 59, 23, 1, 0, 8,
114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 3, 0, 0, 31, 39, 39, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 1, 0, 49, 57, 9, 0, 34, 34,
92, 92, 3, 0, 0, 31, 34, 34, 92, 92, 3, 0, 9, 10, 13, 13, 32, 32, 205, 39, 39, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116,
0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 3, 0, 48, 57, 65, 70, 97, 102, 3, 0, 0, 31, 39, 39, 92, 92, 3, 0, 0, 31,
0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 34, 34, 92, 92, 3, 0, 9, 10, 13, 13, 32, 32, 220, 0, 1, 1, 0, 0, 0, 0,
0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0,
0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0,
0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0,
1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 1, 57, 1, 0, 0, 0, 3, 61, 1, 0, 0, 0, 5, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0,
76, 1, 0, 0, 0, 7, 78, 1, 0, 0, 0, 9, 82, 1, 0, 0, 0, 11, 85, 1, 0, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1,
0, 13, 88, 1, 0, 0, 0, 15, 92, 1, 0, 0, 0, 17, 99, 1, 0, 0, 0, 19, 104, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 1, 61, 1, 0, 0, 0, 3, 65,
1, 0, 0, 0, 21, 111, 1, 0, 0, 0, 23, 113, 1, 0, 0, 0, 25, 118, 1, 0, 0, 1, 0, 0, 0, 5, 69, 1, 0, 0, 0, 7, 84, 1, 0, 0, 0, 9, 86, 1, 0, 0, 0, 11,
0, 27, 127, 1, 0, 0, 0, 29, 129, 1, 0, 0, 0, 31, 131, 1, 0, 0, 0, 33, 133, 93, 1, 0, 0, 0, 13, 97, 1, 0, 0, 0, 15, 100, 1, 0, 0, 0, 17, 103, 1, 0,
1, 0, 0, 0, 35, 141, 1, 0, 0, 0, 37, 143, 1, 0, 0, 0, 39, 145, 1, 0, 0, 0, 0, 19, 107, 1, 0, 0, 0, 21, 114, 1, 0, 0, 0, 23, 119, 1, 0, 0, 0, 25,
0, 41, 152, 1, 0, 0, 0, 43, 172, 1, 0, 0, 0, 45, 174, 1, 0, 0, 0, 47, 179, 126, 1, 0, 0, 0, 27, 128, 1, 0, 0, 0, 29, 133, 1, 0, 0, 0, 31, 142, 1,
1, 0, 0, 0, 49, 185, 1, 0, 0, 0, 51, 187, 1, 0, 0, 0, 53, 189, 1, 0, 0, 0, 0, 0, 33, 144, 1, 0, 0, 0, 35, 146, 1, 0, 0, 0, 37, 148, 1, 0, 0, 0,
0, 55, 192, 1, 0, 0, 0, 57, 58, 5, 65, 0, 0, 58, 59, 5, 78, 0, 0, 59, 60, 39, 156, 1, 0, 0, 0, 41, 158, 1, 0, 0, 0, 43, 160, 1, 0, 0, 0, 45, 167,
5, 68, 0, 0, 60, 2, 1, 0, 0, 0, 61, 62, 5, 79, 0, 0, 62, 63, 5, 82, 0, 1, 0, 0, 0, 47, 187, 1, 0, 0, 0, 49, 189, 1, 0, 0, 0, 51, 194, 1, 0, 0,
0, 63, 4, 1, 0, 0, 0, 64, 65, 5, 69, 0, 0, 65, 77, 5, 81, 0, 0, 66, 67, 0, 53, 200, 1, 0, 0, 0, 55, 202, 1, 0, 0, 0, 57, 204, 1, 0, 0, 0, 59, 207,
5, 78, 0, 0, 67, 77, 5, 69, 0, 0, 68, 69, 5, 71, 0, 0, 69, 77, 5, 69, 0, 1, 0, 0, 0, 61, 62, 5, 78, 0, 0, 62, 63, 5, 79, 0, 0, 63, 64, 5, 84, 0,
0, 70, 71, 5, 71, 0, 0, 71, 77, 5, 84, 0, 0, 72, 73, 5, 76, 0, 0, 73, 77, 0, 64, 2, 1, 0, 0, 0, 65, 66, 5, 65, 0, 0, 66, 67, 5, 78, 0, 0, 67, 68,
5, 84, 0, 0, 74, 75, 5, 76, 0, 0, 75, 77, 5, 69, 0, 0, 76, 64, 1, 0, 0, 5, 68, 0, 0, 68, 4, 1, 0, 0, 0, 69, 70, 5, 79, 0, 0, 70, 71, 5, 82, 0,
0, 76, 66, 1, 0, 0, 0, 76, 68, 1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 76, 72, 0, 71, 6, 1, 0, 0, 0, 72, 73, 5, 69, 0, 0, 73, 85, 5, 81, 0, 0, 74, 75,
1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 77, 6, 1, 0, 0, 0, 78, 79, 5, 82, 0, 0, 5, 78, 0, 0, 75, 85, 5, 69, 0, 0, 76, 77, 5, 71, 0, 0, 77, 85, 5, 69, 0,
79, 80, 5, 69, 0, 0, 80, 81, 5, 80, 0, 0, 81, 8, 1, 0, 0, 0, 82, 83, 5, 0, 78, 79, 5, 71, 0, 0, 79, 85, 5, 84, 0, 0, 80, 81, 5, 76, 0, 0, 81, 85,
73, 0, 0, 83, 84, 5, 78, 0, 0, 84, 10, 1, 0, 0, 0, 85, 86, 5, 65, 0, 0, 5, 84, 0, 0, 82, 83, 5, 76, 0, 0, 83, 85, 5, 69, 0, 0, 84, 72, 1, 0, 0,
86, 87, 5, 83, 0, 0, 87, 12, 1, 0, 0, 0, 88, 89, 5, 67, 0, 0, 89, 90, 5, 0, 84, 74, 1, 0, 0, 0, 84, 76, 1, 0, 0, 0, 84, 78, 1, 0, 0, 0, 84, 80,
66, 0, 0, 90, 91, 5, 70, 0, 0, 91, 14, 1, 0, 0, 0, 92, 93, 5, 83, 0, 0, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 85, 8, 1, 0, 0, 0, 86, 87, 5, 85, 0, 0,
93, 94, 5, 69, 0, 0, 94, 95, 5, 76, 0, 0, 95, 96, 5, 69, 0, 0, 96, 97, 87, 88, 5, 78, 0, 0, 88, 89, 5, 73, 0, 0, 89, 90, 5, 81, 0, 0, 90, 91,
5, 67, 0, 0, 97, 98, 5, 84, 0, 0, 98, 16, 1, 0, 0, 0, 99, 100, 5, 70, 0, 5, 85, 0, 0, 91, 92, 5, 69, 0, 0, 92, 10, 1, 0, 0, 0, 93, 94, 5, 82, 0,
0, 100, 101, 5, 82, 0, 0, 101, 102, 5, 79, 0, 0, 102, 103, 5, 77, 0, 0, 0, 94, 95, 5, 69, 0, 0, 95, 96, 5, 80, 0, 0, 96, 12, 1, 0, 0, 0, 97, 98,
103, 18, 1, 0, 0, 0, 104, 105, 5, 70, 0, 0, 105, 106, 5, 73, 0, 0, 106, 5, 73, 0, 0, 98, 99, 5, 78, 0, 0, 99, 14, 1, 0, 0, 0, 100, 101, 5, 65,
107, 5, 76, 0, 0, 107, 108, 5, 84, 0, 0, 108, 109, 5, 69, 0, 0, 109, 110, 0, 0, 101, 102, 5, 83, 0, 0, 102, 16, 1, 0, 0, 0, 103, 104, 5, 67, 0, 0,
5, 82, 0, 0, 110, 20, 1, 0, 0, 0, 111, 112, 5, 42, 0, 0, 112, 22, 1, 0, 104, 105, 5, 66, 0, 0, 105, 106, 5, 70, 0, 0, 106, 18, 1, 0, 0, 0, 107,
0, 0, 113, 114, 5, 83, 0, 0, 114, 115, 5, 65, 0, 0, 115, 116, 5, 77, 0, 108, 5, 83, 0, 0, 108, 109, 5, 69, 0, 0, 109, 110, 5, 76, 0, 0, 110, 111,
0, 116, 117, 5, 69, 0, 0, 117, 24, 1, 0, 0, 0, 118, 119, 5, 68, 0, 0, 119, 5, 69, 0, 0, 111, 112, 5, 67, 0, 0, 112, 113, 5, 84, 0, 0, 113, 20, 1,
120, 5, 73, 0, 0, 120, 121, 5, 83, 0, 0, 121, 122, 5, 84, 0, 0, 122, 123, 0, 0, 0, 114, 115, 5, 70, 0, 0, 115, 116, 5, 82, 0, 0, 116, 117, 5, 79,
5, 73, 0, 0, 123, 124, 5, 78, 0, 0, 124, 125, 5, 67, 0, 0, 125, 126, 5, 0, 0, 117, 118, 5, 77, 0, 0, 118, 22, 1, 0, 0, 0, 119, 120, 5, 70, 0, 0,
84, 0, 0, 126, 26, 1, 0, 0, 0, 127, 128, 5, 40, 0, 0, 128, 28, 1, 0, 0, 120, 121, 5, 73, 0, 0, 121, 122, 5, 76, 0, 0, 122, 123, 5, 84, 0, 0, 123,
0, 129, 130, 5, 41, 0, 0, 130, 30, 1, 0, 0, 0, 131, 132, 5, 64, 0, 0, 132, 124, 5, 69, 0, 0, 124, 125, 5, 82, 0, 0, 125, 24, 1, 0, 0, 0, 126, 127,
32, 1, 0, 0, 0, 133, 138, 3, 37, 18, 0, 134, 137, 3, 35, 17, 0, 135, 137, 5, 42, 0, 0, 127, 26, 1, 0, 0, 0, 128, 129, 5, 83, 0, 0, 129, 130, 5, 65,
3, 37, 18, 0, 136, 134, 1, 0, 0, 0, 136, 135, 1, 0, 0, 0, 137, 140, 1, 0, 0, 130, 131, 5, 77, 0, 0, 131, 132, 5, 69, 0, 0, 132, 28, 1, 0, 0, 0,
0, 0, 0, 138, 136, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, 34, 1, 0, 0, 133, 134, 5, 68, 0, 0, 134, 135, 5, 73, 0, 0, 135, 136, 5, 83, 0, 0, 136,
0, 140, 138, 1, 0, 0, 0, 141, 142, 7, 0, 0, 0, 142, 36, 1, 0, 0, 0, 143, 137, 5, 84, 0, 0, 137, 138, 5, 73, 0, 0, 138, 139, 5, 78, 0, 0, 139, 140,
144, 7, 1, 0, 0, 144, 38, 1, 0, 0, 0, 145, 149, 7, 2, 0, 0, 146, 148, 3, 5, 67, 0, 0, 140, 141, 5, 84, 0, 0, 141, 30, 1, 0, 0, 0, 142, 143, 5, 40,
35, 17, 0, 147, 146, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0, 0, 0, 143, 32, 1, 0, 0, 0, 144, 145, 5, 41, 0, 0, 145, 34, 1, 0, 0, 0,
0, 0, 149, 150, 1, 0, 0, 0, 150, 40, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 146, 147, 5, 64, 0, 0, 147, 36, 1, 0, 0, 0, 148, 153, 3, 41, 20, 0, 149,
152, 153, 5, 48, 0, 0, 153, 42, 1, 0, 0, 0, 154, 159, 5, 34, 0, 0, 155, 152, 3, 39, 19, 0, 150, 152, 3, 41, 20, 0, 151, 149, 1, 0, 0, 0, 151, 150,
158, 3, 45, 22, 0, 156, 158, 3, 53, 26, 0, 157, 155, 1, 0, 0, 0, 157, 156, 1, 0, 0, 0, 152, 155, 1, 0, 0, 0, 153, 151, 1, 0, 0, 0, 153, 154, 1, 0,
1, 0, 0, 0, 158, 161, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 154, 38, 1, 0, 0, 0, 155, 153, 1, 0, 0, 0, 156, 157, 7, 0, 0, 0,
0, 0, 160, 162, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 162, 173, 5, 34, 0, 0, 157, 40, 1, 0, 0, 0, 158, 159, 7, 1, 0, 0, 159, 42, 1, 0, 0, 0, 160, 164,
163, 168, 5, 39, 0, 0, 164, 167, 3, 45, 22, 0, 165, 167, 3, 51, 25, 0, 7, 2, 0, 0, 161, 163, 3, 39, 19, 0, 162, 161, 1, 0, 0, 0, 163, 166, 1,
166, 164, 1, 0, 0, 0, 166, 165, 1, 0, 0, 0, 167, 170, 1, 0, 0, 0, 168, 0, 0, 0, 164, 162, 1, 0, 0, 0, 164, 165, 1, 0, 0, 0, 165, 44, 1, 0, 0,
166, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169, 171, 1, 0, 0, 0, 170, 168, 0, 166, 164, 1, 0, 0, 0, 167, 168, 5, 48, 0, 0, 168, 46, 1, 0, 0, 0, 169,
1, 0, 0, 0, 171, 173, 5, 39, 0, 0, 172, 154, 1, 0, 0, 0, 172, 163, 1, 0, 174, 5, 34, 0, 0, 170, 173, 3, 49, 24, 0, 171, 173, 3, 57, 28, 0, 172,
0, 0, 173, 44, 1, 0, 0, 0, 174, 177, 5, 92, 0, 0, 175, 178, 7, 3, 0, 0, 170, 1, 0, 0, 0, 172, 171, 1, 0, 0, 0, 173, 176, 1, 0, 0, 0, 174, 172,
176, 178, 3, 47, 23, 0, 177, 175, 1, 0, 0, 0, 177, 176, 1, 0, 0, 0, 178, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 177, 1, 0, 0, 0, 176, 174, 1, 0,
46, 1, 0, 0, 0, 179, 180, 5, 117, 0, 0, 180, 181, 3, 49, 24, 0, 181, 182, 0, 0, 177, 188, 5, 34, 0, 0, 178, 183, 5, 39, 0, 0, 179, 182, 3, 49, 24,
3, 49, 24, 0, 182, 183, 3, 49, 24, 0, 183, 184, 3, 49, 24, 0, 184, 48, 0, 180, 182, 3, 55, 27, 0, 181, 179, 1, 0, 0, 0, 181, 180, 1, 0, 0, 0,
1, 0, 0, 0, 185, 186, 7, 4, 0, 0, 186, 50, 1, 0, 0, 0, 187, 188, 8, 5, 182, 185, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184,
0, 0, 188, 52, 1, 0, 0, 0, 189, 190, 8, 6, 0, 0, 190, 54, 1, 0, 0, 0, 191, 186, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 186, 188, 5, 39, 0, 0, 187, 169,
193, 7, 7, 0, 0, 192, 191, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 192, 1, 0, 0, 0, 187, 178, 1, 0, 0, 0, 188, 48, 1, 0, 0, 0, 189, 192, 5, 92,
1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 197, 6, 27, 0, 0, 190, 193, 7, 3, 0, 0, 191, 193, 3, 51, 25, 0, 192, 190, 1, 0, 0,
0, 0, 197, 56, 1, 0, 0, 0, 12, 0, 76, 136, 138, 149, 157, 159, 166, 168, 0, 192, 191, 1, 0, 0, 0, 193, 50, 1, 0, 0, 0, 194, 195, 5, 117, 0, 0, 195,
172, 177, 194, 1, 6, 0, 0, 196, 3, 53, 26, 0, 196, 197, 3, 53, 26, 0, 197, 198, 3, 53, 26, 0, 198,
199, 3, 53, 26, 0, 199, 52, 1, 0, 0, 0, 200, 201, 7, 4, 0, 0, 201, 54,
1, 0, 0, 0, 202, 203, 8, 5, 0, 0, 203, 56, 1, 0, 0, 0, 204, 205, 8, 6,
0, 0, 205, 58, 1, 0, 0, 0, 206, 208, 7, 7, 0, 0, 207, 206, 1, 0, 0, 0,
208, 209, 1, 0, 0, 0, 209, 207, 1, 0, 0, 0, 209, 210, 1, 0, 0, 0, 210,
211, 1, 0, 0, 0, 211, 212, 6, 29, 0, 0, 212, 60, 1, 0, 0, 0, 12, 0, 84,
151, 153, 164, 172, 174, 181, 183, 187, 192, 209, 1, 6, 0, 0,
} }
deserializer := antlr.NewATNDeserializer(nil) deserializer := antlr.NewATNDeserializer(nil)
staticData.atn = deserializer.Deserialize(staticData.serializedATN) staticData.atn = deserializer.Deserialize(staticData.serializedATN)
@ -166,7 +172,7 @@ func querylexerLexerInit() {
// NewQueryLexer(). You can call this function if you wish to initialize the static state ahead // NewQueryLexer(). You can call this function if you wish to initialize the static state ahead
// of time. // of time.
func QueryLexerInit() { func QueryLexerInit() {
staticData := &querylexerLexerStaticData staticData := &QueryLexerLexerStaticData
staticData.once.Do(querylexerLexerInit) staticData.once.Do(querylexerLexerInit)
} }
@ -175,13 +181,13 @@ func NewQueryLexer(input antlr.CharStream) *QueryLexer {
QueryLexerInit() QueryLexerInit()
l := new(QueryLexer) l := new(QueryLexer)
l.BaseLexer = antlr.NewBaseLexer(input) l.BaseLexer = antlr.NewBaseLexer(input)
staticData := &querylexerLexerStaticData staticData := &QueryLexerLexerStaticData
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.predictionContextCache) l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache)
l.channelNames = staticData.channelNames l.channelNames = staticData.ChannelNames
l.modeNames = staticData.modeNames l.modeNames = staticData.ModeNames
l.RuleNames = staticData.ruleNames l.RuleNames = staticData.RuleNames
l.LiteralNames = staticData.literalNames l.LiteralNames = staticData.LiteralNames
l.SymbolicNames = staticData.symbolicNames l.SymbolicNames = staticData.SymbolicNames
l.GrammarFileName = "QueryLexer.g4" l.GrammarFileName = "QueryLexer.g4"
// TODO: l.EOF = antlr.TokenEOF // TODO: l.EOF = antlr.TokenEOF
@ -190,25 +196,27 @@ func NewQueryLexer(input antlr.CharStream) *QueryLexer {
// QueryLexer tokens. // QueryLexer tokens.
const ( const (
QueryLexerAND_OP = 1 QueryLexerNOT_OP = 1
QueryLexerOR_OP = 2 QueryLexerAND_OP = 2
QueryLexerSIMPLE_OP = 3 QueryLexerOR_OP = 3
QueryLexerREP = 4 QueryLexerSIMPLE_OP = 4
QueryLexerIN = 5 QueryLexerUNIQUE = 5
QueryLexerAS = 6 QueryLexerREP = 6
QueryLexerCBF = 7 QueryLexerIN = 7
QueryLexerSELECT = 8 QueryLexerAS = 8
QueryLexerFROM = 9 QueryLexerCBF = 9
QueryLexerFILTER = 10 QueryLexerSELECT = 10
QueryLexerWILDCARD = 11 QueryLexerFROM = 11
QueryLexerCLAUSE_SAME = 12 QueryLexerFILTER = 12
QueryLexerCLAUSE_DISTINCT = 13 QueryLexerWILDCARD = 13
QueryLexerL_PAREN = 14 QueryLexerCLAUSE_SAME = 14
QueryLexerR_PAREN = 15 QueryLexerCLAUSE_DISTINCT = 15
QueryLexerAT = 16 QueryLexerL_PAREN = 16
QueryLexerIDENT = 17 QueryLexerR_PAREN = 17
QueryLexerNUMBER1 = 18 QueryLexerAT = 18
QueryLexerZERO = 19 QueryLexerIDENT = 19
QueryLexerSTRING = 20 QueryLexerNUMBER1 = 20
QueryLexerWS = 21 QueryLexerZERO = 21
QueryLexerSTRING = 22
QueryLexerWS = 23
) )

View file

@ -1,94 +0,0 @@
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
package parser // Query
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
// QueryListener is a complete listener for a parse tree produced by Query.
type QueryListener interface {
antlr.ParseTreeListener
// EnterPolicy is called when entering the policy production.
EnterPolicy(c *PolicyContext)
// EnterRepStmt is called when entering the repStmt production.
EnterRepStmt(c *RepStmtContext)
// EnterCbfStmt is called when entering the cbfStmt production.
EnterCbfStmt(c *CbfStmtContext)
// EnterSelectStmt is called when entering the selectStmt production.
EnterSelectStmt(c *SelectStmtContext)
// EnterClause is called when entering the clause production.
EnterClause(c *ClauseContext)
// EnterFilterExpr is called when entering the filterExpr production.
EnterFilterExpr(c *FilterExprContext)
// EnterFilterStmt is called when entering the filterStmt production.
EnterFilterStmt(c *FilterStmtContext)
// EnterExpr is called when entering the expr production.
EnterExpr(c *ExprContext)
// EnterFilterKey is called when entering the filterKey production.
EnterFilterKey(c *FilterKeyContext)
// EnterFilterValue is called when entering the filterValue production.
EnterFilterValue(c *FilterValueContext)
// EnterNumber is called when entering the number production.
EnterNumber(c *NumberContext)
// EnterKeyword is called when entering the keyword production.
EnterKeyword(c *KeywordContext)
// EnterIdent is called when entering the ident production.
EnterIdent(c *IdentContext)
// EnterIdentWC is called when entering the identWC production.
EnterIdentWC(c *IdentWCContext)
// ExitPolicy is called when exiting the policy production.
ExitPolicy(c *PolicyContext)
// ExitRepStmt is called when exiting the repStmt production.
ExitRepStmt(c *RepStmtContext)
// ExitCbfStmt is called when exiting the cbfStmt production.
ExitCbfStmt(c *CbfStmtContext)
// ExitSelectStmt is called when exiting the selectStmt production.
ExitSelectStmt(c *SelectStmtContext)
// ExitClause is called when exiting the clause production.
ExitClause(c *ClauseContext)
// ExitFilterExpr is called when exiting the filterExpr production.
ExitFilterExpr(c *FilterExprContext)
// ExitFilterStmt is called when exiting the filterStmt production.
ExitFilterStmt(c *FilterStmtContext)
// ExitExpr is called when exiting the expr production.
ExitExpr(c *ExprContext)
// ExitFilterKey is called when exiting the filterKey production.
ExitFilterKey(c *FilterKeyContext)
// ExitFilterValue is called when exiting the filterValue production.
ExitFilterValue(c *FilterValueContext)
// ExitNumber is called when exiting the number production.
ExitNumber(c *NumberContext)
// ExitKeyword is called when exiting the keyword production.
ExitKeyword(c *KeywordContext)
// ExitIdent is called when exiting the ident production.
ExitIdent(c *IdentContext)
// ExitIdentWC is called when exiting the identWC production.
ExitIdentWC(c *IdentWCContext)
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT. // Code generated from Query.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser // Query package parser // Query
import "github.com/antlr/antlr4/runtime/Go/antlr/v4" import "github.com/antlr4-go/antlr/v4"
// A complete Visitor for a parse tree produced by Query. // A complete Visitor for a parse tree produced by Query.
type QueryVisitor interface { type QueryVisitor interface {
@ -11,6 +11,9 @@ type QueryVisitor interface {
// Visit a parse tree produced by Query#policy. // Visit a parse tree produced by Query#policy.
VisitPolicy(ctx *PolicyContext) interface{} VisitPolicy(ctx *PolicyContext) interface{}
// Visit a parse tree produced by Query#selectFilterExpr.
VisitSelectFilterExpr(ctx *SelectFilterExprContext) interface{}
// Visit a parse tree produced by Query#repStmt. // Visit a parse tree produced by Query#repStmt.
VisitRepStmt(ctx *RepStmtContext) interface{} VisitRepStmt(ctx *RepStmtContext) interface{}

View file

@ -9,7 +9,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap/parser" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap/parser"
"github.com/antlr/antlr4/runtime/Go/antlr/v4" "github.com/antlr4-go/antlr/v4"
) )
// PlacementPolicy declares policy to store objects in the FrostFS container. // PlacementPolicy declares policy to store objects in the FrostFS container.
@ -28,6 +28,8 @@ type PlacementPolicy struct {
selectors []netmap.Selector selectors []netmap.Selector
replicas []netmap.Replica replicas []netmap.Replica
unique bool
} }
func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresence bool) error { func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresence bool) error {
@ -39,6 +41,7 @@ func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresenc
p.backupFactor = m.GetContainerBackupFactor() p.backupFactor = m.GetContainerBackupFactor()
p.selectors = m.GetSelectors() p.selectors = m.GetSelectors()
p.filters = m.GetFilters() p.filters = m.GetFilters()
p.unique = m.GetUnique()
return nil return nil
} }
@ -113,6 +116,7 @@ func (p PlacementPolicy) WriteToV2(m *netmap.PlacementPolicy) {
m.SetFilters(p.filters) m.SetFilters(p.filters)
m.SetSelectors(p.selectors) m.SetSelectors(p.selectors)
m.SetReplicas(p.replicas) m.SetReplicas(p.replicas)
m.SetUnique(p.unique)
} }
// ReplicaDescriptor replica descriptor characterizes replicas of objects from // ReplicaDescriptor replica descriptor characterizes replicas of objects from
@ -178,6 +182,14 @@ func (p *PlacementPolicy) SetContainerBackupFactor(f uint32) {
p.backupFactor = f p.backupFactor = f
} }
// SetUnique sets the unique flag: it controls whether the selected replica buckets
// are disjoint or not.
//
// Zero PlacementPolicy has false unique flag.
func (p *PlacementPolicy) SetUnique(b bool) {
p.unique = b
}
// Selector describes the bucket selection operator: choose a number of nodes // Selector describes the bucket selection operator: choose a number of nodes
// from the bucket taking the nearest nodes to the related container by hash distance. // from the bucket taking the nearest nodes to the related container by hash distance.
type Selector struct { type Selector struct {
@ -362,7 +374,14 @@ func (p *PlacementPolicy) AddFilters(fs ...Filter) {
// the result into w. Returns w's errors directly. // the result into w. Returns w's errors directly.
// //
// See also DecodeString. // See also DecodeString.
// nolint: funlen
func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) { func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) {
if p.unique {
if _, err := w.WriteString("UNIQUE\n"); err != nil {
return err
}
}
delim := "" delim := ""
for i := range p.replicas { for i := range p.replicas {
@ -452,7 +471,7 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
unspecified := op == 0 unspecified := op == 0
if s = f.GetKey(); s != "" { if s = f.GetKey(); s != "" {
_, err = w.WriteString(fmt.Sprintf("%s %s %s", s, op, f.GetValue())) _, err = w.WriteString(fmt.Sprintf("%s %s %s", escapeString(s), op, escapeString(f.GetValue())))
if err != nil { if err != nil {
return err return err
} }
@ -464,6 +483,21 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
} }
inner := f.GetFilters() inner := f.GetFilters()
if op == netmap.NOT {
_, err = w.WriteString(op.String() + " (")
if err != nil {
return err
}
err = writeFilterStringTo(w, inner[0])
if err != nil {
return err
}
_, err = w.WriteString(")")
if err != nil {
return err
}
} else {
for i := range inner { for i := range inner {
if i != 0 { if i != 0 {
_, err = w.WriteString(" " + op.String() + " ") _, err = w.WriteString(" " + op.String() + " ")
@ -471,12 +505,12 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
return err return err
} }
} }
err = writeFilterStringTo(w, inner[i]) err = writeFilterStringTo(w, inner[i])
if err != nil { if err != nil {
return err return err
} }
} }
}
if s = f.GetName(); s != "" && !unspecified { if s = f.GetName(); s != "" && !unspecified {
_, err = w.WriteString(" AS " + s) _, err = w.WriteString(" AS " + s)
@ -526,6 +560,44 @@ func (p *PlacementPolicy) DecodeString(s string) error {
return nil return nil
} }
// SelectFilterExpr is an expression containing only selectors and filters.
// It's useful to evaluate their effect before being used in a policy.
type SelectFilterExpr struct {
cbf uint32
selector *netmap.Selector
filters []netmap.Filter
}
// DecodeString decodes a string into a SelectFilterExpr.
// Returns an error if s is malformed.
func DecodeSelectFilterString(s string) (*SelectFilterExpr, error) {
var v policyVisitor
input := antlr.NewInputStream(s)
lexer := parser.NewQueryLexer(input)
lexer.RemoveErrorListeners()
lexer.AddErrorListener(&v)
stream := antlr.NewCommonTokenStream(lexer, 0)
pp := parser.NewQuery(stream)
pp.BuildParseTrees = true
pp.RemoveErrorListeners()
pp.AddErrorListener(&v)
sfExpr := pp.SelectFilterExpr().Accept(&v)
if len(v.errors) != 0 {
return nil, v.errors[0]
}
parsed, ok := sfExpr.(*SelectFilterExpr)
if !ok {
return nil, fmt.Errorf("unexpected parsed instance type %T", sfExpr)
}
return parsed, nil
}
var ( var (
// errUnknownFilter is returned when a value of FROM in a query is unknown. // errUnknownFilter is returned when a value of FROM in a query is unknown.
errUnknownFilter = errors.New("filter not found") errUnknownFilter = errors.New("filter not found")
@ -541,22 +613,25 @@ type policyVisitor struct {
antlr.DefaultErrorListener antlr.DefaultErrorListener
} }
func (p *policyVisitor) SyntaxError(_ antlr.Recognizer, _ interface{}, line, column int, msg string, _ antlr.RecognitionException) { func (p *policyVisitor) SyntaxError(_ antlr.Recognizer, _ any, line, column int, msg string, _ antlr.RecognitionException) {
p.reportError(fmt.Errorf("%w: line %d:%d %s", errSyntaxError, line, column, msg)) p.reportError(fmt.Errorf("%w: line %d:%d %s", errSyntaxError, line, column, msg))
} }
func (p *policyVisitor) reportError(err error) interface{} { func (p *policyVisitor) reportError(err error) any {
p.errors = append(p.errors, err) p.errors = append(p.errors, err)
return nil return nil
} }
// VisitPolicy implements parser.QueryVisitor interface. // VisitPolicy implements parser.QueryVisitor interface.
func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} { func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) any {
if len(p.errors) != 0 { if len(p.errors) != 0 {
return nil return nil
} }
pl := new(PlacementPolicy) pl := new(PlacementPolicy)
pl.unique = ctx.UNIQUE() != nil
repStmts := ctx.AllRepStmt() repStmts := ctx.AllRepStmt()
pl.replicas = make([]netmap.Replica, 0, len(repStmts)) pl.replicas = make([]netmap.Replica, 0, len(repStmts))
@ -599,7 +674,40 @@ func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} {
return pl return pl
} }
func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) interface{} { func (p *policyVisitor) VisitSelectFilterExpr(ctx *parser.SelectFilterExprContext) any {
if len(p.errors) != 0 {
return nil
}
sfExpr := new(SelectFilterExpr)
if cbfStmt := ctx.CbfStmt(); cbfStmt != nil {
cbf, ok := cbfStmt.(*parser.CbfStmtContext).Accept(p).(uint32)
if !ok {
return nil
}
sfExpr.cbf = cbf
}
if selStmt := ctx.SelectStmt(); selStmt != nil {
sel, ok := selStmt.Accept(p).(*netmap.Selector)
if !ok {
return nil
}
sfExpr.selector = sel
}
filtStmts := ctx.AllFilterStmt()
sfExpr.filters = make([]netmap.Filter, 0, len(filtStmts))
for _, f := range filtStmts {
sfExpr.filters = append(sfExpr.filters, *f.Accept(p).(*netmap.Filter))
}
return sfExpr
}
func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) any {
cbf, err := strconv.ParseUint(ctx.GetBackupFactor().GetText(), 10, 32) cbf, err := strconv.ParseUint(ctx.GetBackupFactor().GetText(), 10, 32)
if err != nil { if err != nil {
return p.reportError(errInvalidNumber) return p.reportError(errInvalidNumber)
@ -609,7 +717,7 @@ func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) interface{} {
} }
// VisitRepStmt implements parser.QueryVisitor interface. // VisitRepStmt implements parser.QueryVisitor interface.
func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) interface{} { func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) any {
num, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32) num, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
if err != nil { if err != nil {
return p.reportError(errInvalidNumber) return p.reportError(errInvalidNumber)
@ -626,7 +734,7 @@ func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) interface{} {
} }
// VisitSelectStmt implements parser.QueryVisitor interface. // VisitSelectStmt implements parser.QueryVisitor interface.
func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) interface{} { func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) any {
res, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32) res, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
if err != nil { if err != nil {
return p.reportError(errInvalidNumber) return p.reportError(errInvalidNumber)
@ -652,13 +760,13 @@ func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) interface
} }
// VisitFilterStmt implements parser.QueryVisitor interface. // VisitFilterStmt implements parser.QueryVisitor interface.
func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) interface{} { func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) any {
f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter) f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter)
f.SetName(ctx.GetName().GetText()) f.SetName(ctx.GetName().GetText())
return f return f
} }
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface{} { func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) any {
if eCtx := ctx.Expr(); eCtx != nil { if eCtx := ctx.Expr(); eCtx != nil {
return eCtx.Accept(p) return eCtx.Accept(p)
} }
@ -671,6 +779,12 @@ func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface
op := operationFromString(ctx.GetOp().GetText()) op := operationFromString(ctx.GetOp().GetText())
f.SetOp(op) f.SetOp(op)
if op == netmap.NOT {
f1 := *ctx.GetF1().Accept(p).(*netmap.Filter)
f.SetFilters([]netmap.Filter{f1})
return f
}
f1 := *ctx.GetF1().Accept(p).(*netmap.Filter) f1 := *ctx.GetF1().Accept(p).(*netmap.Filter)
f2 := *ctx.GetF2().Accept(p).(*netmap.Filter) f2 := *ctx.GetF2().Accept(p).(*netmap.Filter)
@ -687,7 +801,7 @@ func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface
} }
// VisitFilterKey implements parser.QueryVisitor interface. // VisitFilterKey implements parser.QueryVisitor interface.
func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{} { func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) any {
if id := ctx.Ident(); id != nil { if id := ctx.Ident(); id != nil {
return id.GetText() return id.GetText()
} }
@ -696,7 +810,7 @@ func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{}
return str[1 : len(str)-1] return str[1 : len(str)-1]
} }
func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interface{} { func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) any {
if id := ctx.Ident(); id != nil { if id := ctx.Ident(); id != nil {
return id.GetText() return id.GetText()
} }
@ -710,7 +824,7 @@ func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interfa
} }
// VisitExpr implements parser.QueryVisitor interface. // VisitExpr implements parser.QueryVisitor interface.
func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) interface{} { func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) any {
f := new(netmap.Filter) f := new(netmap.Filter)
if flt := ctx.GetFilter(); flt != nil { if flt := ctx.GetFilter(); flt != nil {
f.SetName(flt.GetText()) f.SetName(flt.GetText())
@ -773,3 +887,12 @@ func operationFromString(s string) (op netmap.Operation) {
return return
} }
// escapeString returns single quote wrapped string if it contains special
// characters '-' and whitespace.
func escapeString(s string) string {
if strings.ContainsAny(s, " -\t") {
return "'" + s + "'"
}
return s
}

View file

@ -23,6 +23,18 @@ FILTER @FromRU AND Rating GT 7 AS Good`,
`REP 7 IN SPB `REP 7 IN SPB
SELECT 1 IN City FROM SPBSSD AS SPB SELECT 1 IN City FROM SPBSSD AS SPB
FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`, FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`,
`REP 7 IN SPB
SELECT 1 IN City FROM SPBSSD AS SPB
FILTER NOT (NOT (City EQ SPB) AND SSD EQ true OR City EQ SPB AND Rating GE 5) AS SPBSSD`,
`UNIQUE
REP 1
REP 1`,
`REP 1 IN X
SELECT 1 FROM F AS X
FILTER 'UN-LOCODE' EQ 'RU LED' AS F`,
} }
var p PlacementPolicy var p PlacementPolicy
@ -66,3 +78,28 @@ func TestPlacementPolicyEncoding(t *testing.T) {
require.Equal(t, v, v2) require.Equal(t, v, v2)
}) })
} }
func TestDecodeSelectFilterExpr(t *testing.T) {
for _, s := range []string{
"SELECT 1 FROM *",
"FILTER Color EQ 'Red' AS RedNode",
`
FILTER Color EQ 'Red' AS RedNode
FILTER @RedNode AND Shape EQ 'Cirle' AS RedCircleNode
`,
`
SELECT 1 FROM RedCircleNode
FILTER Color EQ 'Red' AS RedNode
FILTER @RedNode AND Shape EQ 'Cirle' AS RedCircleNode
`,
`
CBF 1
SELECT 1 FROM RedCircleNode
FILTER Color EQ 'Red' AS RedNode
FILTER @RedNode AND Shape EQ 'Cirle' AS RedCircleNode
`,
} {
_, err := DecodeSelectFilterString(s)
require.NoError(t, err)
}
}

View file

@ -140,6 +140,9 @@ func (c *context) getSelectionBase(s netmap.Selector) []nodeAttrPair {
attr := s.GetAttribute() attr := s.GetAttribute()
for i := range c.netMap.nodes { for i := range c.netMap.nodes {
if c.usedNodes[c.netMap.nodes[i].hash] {
continue
}
if isMain || c.match(f, c.netMap.nodes[i]) { if isMain || c.match(f, c.netMap.nodes[i]) {
if attr == "" { if attr == "" {
// Default attribute is transparent identifier which is different for every node. // Default attribute is transparent identifier which is different for every node.

View file

@ -1,6 +1,7 @@
package netmap package netmap
import ( import (
"encoding/binary"
"fmt" "fmt"
"math/rand" "math/rand"
"sort" "sort"
@ -244,6 +245,161 @@ func TestPlacementPolicy_ProcessSelectors(t *testing.T) {
} }
} }
func TestPlacementPolicy_Unique(t *testing.T) {
p := newPlacementPolicy(2,
[]ReplicaDescriptor{
newReplica(1, "S"),
newReplica(1, "S"),
},
[]Selector{
newSelector("S", "City", 1, "*", (*Selector).SelectSame),
},
[]Filter{})
p.unique = true
var nodes []NodeInfo
for i, city := range []string{"Moscow", "Berlin", "Shenzhen"} {
for j := 0; j < 3; j++ {
node := nodeInfoFromAttributes("City", city)
node.SetPublicKey(binary.BigEndian.AppendUint16(nil, uint16(i*4+j)))
nodes = append(nodes, node)
}
}
var nm NetMap
nm.SetNodes(nodes)
v, err := nm.ContainerNodes(p, nil)
require.NoError(t, err)
for i, vi := range v {
for _, ni := range vi {
for j := 0; j < i; j++ {
for _, nj := range v[j] {
require.NotEqual(t, ni.hash, nj.hash)
}
}
}
}
}
func TestPlacementPolicy_MultiREP(t *testing.T) {
nodes := []NodeInfo{
nodeInfoFromAttributes("ID", "1", "Country", "Russia", "City", "SPB"),
nodeInfoFromAttributes("ID", "2", "Country", "Germany", "City", "Berlin"),
nodeInfoFromAttributes("ID", "3", "Country", "Russia", "City", "Moscow"),
nodeInfoFromAttributes("ID", "4", "Country", "France", "City", "Paris"),
nodeInfoFromAttributes("ID", "5", "Country", "France", "City", "Lyon"),
nodeInfoFromAttributes("ID", "6", "Country", "Russia", "City", "SPB"),
nodeInfoFromAttributes("ID", "7", "Country", "Russia", "City", "Moscow"),
nodeInfoFromAttributes("ID", "8", "Country", "Germany", "City", "Darmstadt"),
nodeInfoFromAttributes("ID", "9", "Country", "Germany", "City", "Frankfurt"),
nodeInfoFromAttributes("ID", "10", "Country", "Russia", "City", "SPB"),
nodeInfoFromAttributes("ID", "11", "Country", "Russia", "City", "Moscow"),
nodeInfoFromAttributes("ID", "12", "Country", "Germany", "City", "London"),
}
for i := range nodes {
pub := make([]byte, 33)
rand.Read(pub)
nodes[i].SetPublicKey(pub)
}
var nm NetMap
nm.SetNodes(nodes)
ss := []Selector{newSelector("SameRU", "City", 2, "FromRU", (*Selector).SelectDistinct)}
fs := []Filter{newFilter("FromRU", "Country", "Russia", netmap.EQ)}
for _, unique := range []bool{false, true} {
for _, additional := range []int{0, 1, 2} {
t.Run(fmt.Sprintf("unique=%t, additional=%d", unique, additional), func(t *testing.T) {
rs := []ReplicaDescriptor{newReplica(1, "SameRU")}
for i := 0; i < additional; i++ {
rs = append(rs, newReplica(1, ""))
}
p := newPlacementPolicy(3, rs, ss, fs)
p.unique = unique
v, err := nm.ContainerNodes(p, []byte{1})
require.NoError(t, err)
require.Equal(t, 1+additional, len(v))
require.Equal(t, 6, len(v[0]))
for i := 1; i < additional; i++ {
require.Equal(t, 3, len(v[i]))
if !unique {
require.Equal(t, v[1], v[i])
}
}
if unique {
seen := make(map[string]bool)
for i := range v {
for j := range v[i] {
attr := v[i][j].Attribute("ID")
require.NotEmpty(t, attr)
require.False(t, seen[attr])
seen[attr] = true
}
}
}
})
}
}
}
func TestPlacementPolicy_ProcessSelectorsExceptForNodes(t *testing.T) {
p := newPlacementPolicy(1, nil,
[]Selector{
newSelector("ExceptRU", "City", 2, "ExceptRU", (*Selector).SelectSame),
},
[]Filter{
newFilter("ExceptRU", "", "", netmap.NOT,
newFilter("", "", "", netmap.AND,
newFilter("", "City", "Lyon", netmap.EQ),
newFilter("", "Rating", "10", netmap.LE),
),
),
})
nodes := []NodeInfo{
nodeInfoFromAttributes("Country", "Germany", "Rating", "1", "City", "Berlin"),
nodeInfoFromAttributes("Country", "Germany", "Rating", "5", "City", "Berlin"),
nodeInfoFromAttributes("Country", "Russia", "Rating", "6", "City", "Moscow"),
nodeInfoFromAttributes("Country", "France", "Rating", "4", "City", "Paris"),
nodeInfoFromAttributes("Country", "France", "Rating", "1", "City", "Lyon"),
nodeInfoFromAttributes("Country", "France", "Rating", "5", "City", "Lyon"),
nodeInfoFromAttributes("Country", "Russia", "Rating", "7", "City", "Moscow"),
nodeInfoFromAttributes("Country", "Germany", "Rating", "3", "City", "Darmstadt"),
nodeInfoFromAttributes("Country", "Germany", "Rating", "7", "City", "Frankfurt"),
nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
nodeInfoFromAttributes("Country", "Russia", "Rating", "9", "City", "SPB"),
}
var nm NetMap
nm.SetNodes(nodes)
c := newContext(nm)
c.setCBF(p.backupFactor)
require.NoError(t, c.processFilters(p))
require.NoError(t, c.processSelectors(p))
for _, s := range p.selectors {
sel := c.selections[s.GetName()]
s := c.processedSelectors[s.GetName()]
bucketCount, nodesInBucket := calcNodesCount(*s)
nodesInBucket *= int(c.cbf)
targ := fmt.Sprintf("selector '%s'", s.GetName())
require.Equal(t, bucketCount, len(sel), targ)
fName := s.GetFilter()
for _, res := range sel {
require.Equal(t, nodesInBucket, len(res), targ)
for j := range res {
require.True(t, fName == mainFilterName || c.match(c.processedFilters[fName], res[j]), targ)
}
}
}
}
func TestSelector_SetName(t *testing.T) { func TestSelector_SetName(t *testing.T) {
const name = "some name" const name = "some name"
var s Selector var s Selector

View file

@ -10,6 +10,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
@ -18,15 +19,28 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
// multiSchemeClient unites invoker.RPCInvoke and common interface of
// rpcclient.Client and rpcclient.WSClient.
type multiSchemeClient interface {
invoker.RPCInvoke
// Init turns client to "ready-to-work" state.
Init() error
// Close closes connections.
Close()
// GetContractStateByID returns state of the NNS contract on 1 input.
GetContractStateByID(int32) (*state.Contract, error)
}
// NNS looks up FrostFS names using Neo Name Service. // NNS looks up FrostFS names using Neo Name Service.
// //
// Instances are created with a variable declaration. Before work, the connection // Instances are created with a variable declaration. Before work, the connection
// to the NNS server MUST be established using Dial method. // to the NNS server MUST be established using Dial method.
type NNS struct { type NNS struct {
nnsContract util.Uint160 nnsContract util.Uint160
client multiSchemeClient
invoker interface { invoker interface {
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
} }
} }
@ -36,47 +50,41 @@ type NNS struct {
// If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used, // If URL address scheme is 'ws' or 'wss', then WebSocket protocol is used,
// otherwise HTTP. // otherwise HTTP.
func (n *NNS) Dial(address string) error { func (n *NNS) Dial(address string) error {
// multiSchemeClient unites invoker.RPCInvoke and common interface of
// rpcclient.Client and rpcclient.WSClient. Interface is anonymous
// according to assumption that common interface of these client types
// is not required by design and may diverge with changes.
var multiSchemeClient interface {
invoker.RPCInvoke
// Init turns client to "ready-to-work" state.
Init() error
// GetContractStateByID returns state of the NNS contract on 1 input.
GetContractStateByID(int32) (*state.Contract, error)
}
var err error var err error
uri, err := url.Parse(address) uri, err := url.Parse(address)
if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") { if err == nil && (uri.Scheme == "ws" || uri.Scheme == "wss") {
multiSchemeClient, err = rpcclient.NewWS(context.Background(), address, rpcclient.Options{}) n.client, err = rpcclient.NewWS(context.Background(), address, rpcclient.WSOptions{})
if err != nil { if err != nil {
return fmt.Errorf("create Neo WebSocket client: %w", err) return fmt.Errorf("create Neo WebSocket client: %w", err)
} }
} else { } else {
multiSchemeClient, err = rpcclient.New(context.Background(), address, rpcclient.Options{}) n.client, err = rpcclient.New(context.Background(), address, rpcclient.Options{})
if err != nil { if err != nil {
return fmt.Errorf("create Neo HTTP client: %w", err) return fmt.Errorf("create Neo HTTP client: %w", err)
} }
} }
if err = multiSchemeClient.Init(); err != nil { if err = n.client.Init(); err != nil {
return fmt.Errorf("initialize Neo client: %w", err) return fmt.Errorf("initialize Neo client: %w", err)
} }
nnsContract, err := multiSchemeClient.GetContractStateByID(1) nnsContract, err := n.client.GetContractStateByID(1)
if err != nil { if err != nil {
return fmt.Errorf("get NNS contract state: %w", err) return fmt.Errorf("get NNS contract state: %w", err)
} }
n.invoker = invoker.New(multiSchemeClient, nil) n.invoker = invoker.New(n.client, nil)
n.nnsContract = nnsContract.Hash n.nnsContract = nnsContract.Hash
return nil return nil
} }
// Close closes connections of multiSchemeClient.
func (n *NNS) Close() {
n.client.Close()
}
// ResolveContainerDomain looks up for NNS TXT records for the given container domain // ResolveContainerDomain looks up for NNS TXT records for the given container domain
// by calling `resolve` method of NNS contract. Returns the first record which represents // by calling `resolve` method of NNS contract. Returns the first record which represents
// valid container ID in a string format. Otherwise, returns an error. // valid container ID in a string format. Otherwise, returns an error.
@ -116,3 +124,46 @@ func (n *NNS) ResolveContainerDomain(domain container.Domain) (cid.ID, error) {
return cid.ID{}, errNotFound return cid.ID{}, errNotFound
} }
// ResolveContractHash looks up for NNS TXT records for the given container domain
// by calling `resolve` method of NNS contract. Returns the first record which represents
// valid contract hash 20 bytes long unsigned integer. Otherwise, returns an error.
//
// ResolveContractHash MUST NOT be called before successful Dial.
//
// See also https://docs.neo.org/docs/en-us/reference/nns.html.
func (n *NNS) ResolveContractHash(domain container.Domain) (util.Uint160, error) {
item, err := unwrap.Item(n.invoker.Call(n.nnsContract, "resolve",
domain.Name()+"."+domain.Zone(), int64(nns.TXT),
))
if err != nil {
return util.Uint160{}, fmt.Errorf("contract invocation: %w", err)
}
if _, ok := item.(stackitem.Null); !ok {
arr, ok := item.Value().([]stackitem.Item)
if !ok {
// unexpected for types from stackitem package
return util.Uint160{}, errors.New("invalid cast to stack item slice")
}
for i := range arr {
recordValue, err := arr[i].TryBytes()
if err != nil {
return util.Uint160{}, fmt.Errorf("convert array item to byte slice: %w", err)
}
strRecordValue := string(recordValue)
scriptHash, err := address.StringToUint160(strRecordValue)
if err == nil {
return scriptHash, nil
}
scriptHash, err = util.Uint160DecodeStringLE(strRecordValue)
if err == nil {
return scriptHash, nil
}
}
}
return util.Uint160{}, errNotFound
}

View file

@ -29,7 +29,7 @@ type testNeoClient struct {
err error err error
} }
func (x *testNeoClient) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) { func (x *testNeoClient) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) {
var domain string var domain string
require.Equal(x.t, x.expectedContract, contract) require.Equal(x.t, x.expectedContract, contract)
@ -49,7 +49,7 @@ type brokenArrayStackItem struct {
stackitem.Item stackitem.Item
} }
func (x brokenArrayStackItem) Value() interface{} { func (x brokenArrayStackItem) Value() any {
return 1 return 1
} }
@ -154,3 +154,86 @@ func TestNNS_ResolveContainerDomain(t *testing.T) {
require.Equal(t, id, res) require.Equal(t, id, res)
}) })
} }
func TestNNS_ResolveContractHash(t *testing.T) {
var testContainerDomain container.Domain
testContainerDomain.SetName("some_container")
var nnsContract util.Uint160
rand.Read(nnsContract[:])
testC := &testNeoClient{
t: t,
expectedContract: nnsContract,
}
n := NNS{
nnsContract: nnsContract,
invoker: testC,
}
t.Run("invocation failure", func(t *testing.T) {
err1 := errors.New("invoke err")
testC.err = err1
_, err2 := n.ResolveContractHash(testContainerDomain)
require.ErrorIs(t, err2, err1)
})
testC.err = nil
t.Run("fault exception", func(t *testing.T) {
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
testC.res.State = vmstate.Halt.String()
t.Run("empty stack", func(t *testing.T) {
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
testC.res.Stack = make([]stackitem.Item, 1)
t.Run("non-array last stack item", func(t *testing.T) {
testC.res.Stack[0] = stackitem.NewBigInteger(big.NewInt(11))
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
t.Run("null array", func(t *testing.T) {
testC.res.Stack[0] = stackitem.Null{}
_, err := n.ResolveContractHash(testContainerDomain)
require.ErrorIs(t, err, errNotFound)
})
t.Run("array stack item with non-slice value", func(t *testing.T) {
testC.res.Stack[0] = brokenArrayStackItem{}
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
arr := make([]stackitem.Item, 2)
testC.res.Stack[0] = stackitem.NewArray(arr)
t.Run("non-bytes array element", func(t *testing.T) {
arr[0] = stackitem.NewArray(nil)
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
arr[0] = stackitem.NewByteArray([]byte("some byte array 1"))
t.Run("non-container array elements", func(t *testing.T) {
arr[1] = stackitem.NewByteArray([]byte("some byte array 2"))
_, err := n.ResolveContractHash(testContainerDomain)
require.Error(t, err)
})
}

View file

@ -169,3 +169,11 @@ func (x *Address) DecodeString(s string) error {
func (x Address) String() string { func (x Address) String() string {
return x.EncodeToString() return x.EncodeToString()
} }
// Equals defines a comparison relation between two Address's instances.
//
// Note that comparison using '==' operator is not recommended since it MAY result
// in loss of compatibility.
func (x Address) Equals(other Address) bool {
return x.obj.Equals(other.obj) && x.cnr.Equals(other.cnr)
}

View file

@ -220,16 +220,6 @@ func TestObject_SetSessionToken(t *testing.T) {
require.Equal(t, tok, obj.SessionToken()) require.Equal(t, tok, obj.SessionToken())
} }
func TestObject_SetType(t *testing.T) {
obj := New()
typ := TypeStorageGroup
obj.SetType(typ)
require.Equal(t, typ, obj.Type())
}
func TestObject_CutPayload(t *testing.T) { func TestObject_CutPayload(t *testing.T) {
o1 := New() o1 := New()

View file

@ -1,44 +1,28 @@
package transformer package transformer
import ( import (
"context"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"github.com/nspcc-dev/neo-go/pkg/util/slice"
) )
type chanTarget struct { type chanTarget struct {
header *objectSDK.Object
payload []byte
ch chan<- *objectSDK.Object ch chan<- *objectSDK.Object
} }
// NewChannelTarget returns ObjectTarget which writes // NewChannelTarget returns ObjectTarget which writes
// object parts to a provided channel. // object parts to a provided channel.
func NewChannelTarget(ch chan<- *objectSDK.Object) ObjectTarget { func NewChannelTarget(ch chan<- *objectSDK.Object) ObjectWriter {
return &chanTarget{ return &chanTarget{
ch: ch, ch: ch,
} }
} }
// WriteHeader implements the ObjectTarget interface. func (c *chanTarget) WriteObject(ctx context.Context, o *objectSDK.Object) error {
func (c *chanTarget) WriteHeader(object *objectSDK.Object) error { select {
c.header = object case c.ch <- o:
case <-ctx.Done():
return ctx.Err()
}
return nil return nil
} }
// Write implements the ObjectTarget interface.
func (c *chanTarget) Write(p []byte) (n int, err error) {
c.payload = append(c.payload, p...)
return len(p), nil
}
// Close implements the ObjectTarget interface.
func (c *chanTarget) Close() (*AccessIdentifiers, error) {
if len(c.payload) != 0 {
c.header.SetPayload(slice.Copy(c.payload))
}
c.ch <- c.header
c.header = nil
c.payload = nil
return new(AccessIdentifiers), nil
}

View file

@ -1,6 +1,7 @@
package transformer package transformer
import ( import (
"context"
"crypto/rand" "crypto/rand"
"testing" "testing"
@ -15,9 +16,10 @@ func TestChannelTarget(t *testing.T) {
ch := make(chan *objectSDK.Object, 10) ch := make(chan *objectSDK.Object, 10)
tt := new(testTarget) tt := new(testTarget)
ct := NewChannelTarget(ch)
chTarget, _ := newPayloadSizeLimiter(maxSize, NewChannelTarget(ch)) chTarget, _ := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return ct })
testTarget, _ := newPayloadSizeLimiter(maxSize, tt) testTarget, _ := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return tt })
ver := version.Current() ver := version.Current()
cnr := cidtest.ID() cnr := cidtest.ID()
@ -29,8 +31,9 @@ func TestChannelTarget(t *testing.T) {
payload := make([]byte, maxSize*2+maxSize/2) payload := make([]byte, maxSize*2+maxSize/2)
_, _ = rand.Read(payload) _, _ = rand.Read(payload)
expectedIDs := writeObject(t, testTarget, hdr, payload) ctx := context.Background()
actualIDs := writeObject(t, chTarget, hdr, payload) expectedIDs := writeObject(t, ctx, testTarget, hdr, payload)
actualIDs := writeObject(t, ctx, chTarget, hdr, payload)
_ = expectedIDs _ = expectedIDs
_ = actualIDs _ = actualIDs
//require.Equal(t, expectedIDs, actualIDs) //require.Equal(t, expectedIDs, actualIDs)

View file

@ -1,6 +1,7 @@
package transformer package transformer
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
@ -19,6 +20,7 @@ type payloadSizeLimiter struct {
written, writtenCurrent uint64 written, writtenCurrent uint64
current, parent *object.Object current, parent *object.Object
payload []byte
currentHashers, parentHashers []payloadChecksumHasher currentHashers, parentHashers []payloadChecksumHasher
@ -27,11 +29,13 @@ type payloadSizeLimiter struct {
splitID *object.SplitID splitID *object.SplitID
parAttrs []object.Attribute parAttrs []object.Attribute
nextTarget ObjectWriter
} }
type Params struct { type Params struct {
Key *ecdsa.PrivateKey Key *ecdsa.PrivateKey
NextTarget ObjectTarget NextTargetInit TargetInitializer
SessionToken *session.Object SessionToken *session.Object
NetworkState EpochSource NetworkState EpochSource
MaxSize uint64 MaxSize uint64
@ -45,14 +49,14 @@ type Params struct {
// is false. // is false.
// //
// Objects w/ payload size less or equal than max size remain untouched. // Objects w/ payload size less or equal than max size remain untouched.
func NewPayloadSizeLimiter(p Params) ObjectTarget { func NewPayloadSizeLimiter(p Params) ChunkedObjectWriter {
return &payloadSizeLimiter{ return &payloadSizeLimiter{
Params: p, Params: p,
splitID: object.NewSplitID(), splitID: object.NewSplitID(),
} }
} }
func (s *payloadSizeLimiter) WriteHeader(hdr *object.Object) error { func (s *payloadSizeLimiter) WriteHeader(_ context.Context, hdr *object.Object) error {
s.current = fromObject(hdr) s.current = fromObject(hdr)
s.initialize() s.initialize()
@ -60,16 +64,16 @@ func (s *payloadSizeLimiter) WriteHeader(hdr *object.Object) error {
return nil return nil
} }
func (s *payloadSizeLimiter) Write(p []byte) (int, error) { func (s *payloadSizeLimiter) Write(ctx context.Context, p []byte) (int, error) {
if err := s.writeChunk(p); err != nil { if err := s.writeChunk(ctx, p); err != nil {
return 0, err return 0, err
} }
return len(p), nil return len(p), nil
} }
func (s *payloadSizeLimiter) Close() (*AccessIdentifiers, error) { func (s *payloadSizeLimiter) Close(ctx context.Context) (*AccessIdentifiers, error) {
return s.release(true) return s.release(ctx, true)
} }
func (s *payloadSizeLimiter) initialize() { func (s *payloadSizeLimiter) initialize() {
@ -79,11 +83,13 @@ func (s *payloadSizeLimiter) initialize() {
if ln := len(s.previous); ln > 0 { if ln := len(s.previous); ln > 0 {
// initialize parent object once (after 1st object) // initialize parent object once (after 1st object)
if ln == 1 { if ln == 1 {
ver := version.Current()
s.parent = fromObject(s.current) s.parent = fromObject(s.current)
s.parentHashers = append(s.parentHashers[:0], s.currentHashers...) s.parent.ResetRelations()
s.parent.SetSignature(nil)
// return source attributes
s.parent.SetAttributes(s.parAttrs...) s.parent.SetAttributes(s.parAttrs...)
s.parent.SetVersion(&ver)
s.parentHashers = append(s.parentHashers[:0], s.currentHashers...)
} }
// set previous object to the last previous identifier // set previous object to the last previous identifier
@ -112,9 +118,10 @@ func fromObject(obj *object.Object) *object.Object {
} }
func (s *payloadSizeLimiter) initializeCurrent() { func (s *payloadSizeLimiter) initializeCurrent() {
// create payload hashers s.nextTarget = s.NextTargetInit()
s.writtenCurrent = 0 s.writtenCurrent = 0
s.initPayloadHashers() s.initPayloadHashers()
s.payload = make([]byte, 0)
} }
func (s *payloadSizeLimiter) initPayloadHashers() { func (s *payloadSizeLimiter) initPayloadHashers() {
@ -131,8 +138,7 @@ func (s *payloadSizeLimiter) initPayloadHashers() {
} }
} }
// nolint: funlen func (s *payloadSizeLimiter) release(ctx context.Context, finalize bool) (*AccessIdentifiers, error) {
func (s *payloadSizeLimiter) release(finalize bool) (*AccessIdentifiers, error) {
// Arg finalize is true only when called from Close method. // Arg finalize is true only when called from Close method.
// We finalize parent and generate linking objects only if it is more // We finalize parent and generate linking objects only if it is more
// than 1 object in split-chain. // than 1 object in split-chain.
@ -151,6 +157,33 @@ func (s *payloadSizeLimiter) release(finalize bool) (*AccessIdentifiers, error)
s.currentHashers[i].writeChecksum(s.current) s.currentHashers[i].writeChecksum(s.current)
} }
ids, err := s.fillHeader()
if err != nil {
return nil, fmt.Errorf("fillHeader: %w", err)
}
s.current.SetPayload(s.payload)
if err := s.nextTarget.WriteObject(ctx, s.current); err != nil {
return nil, fmt.Errorf("could not write to next target: %w", err)
}
// save identifier of the released object
s.previous = append(s.previous, ids.SelfID)
if withParent {
// generate and release linking object
s.initializeLinking(ids.ParentHeader)
s.initializeCurrent()
if _, err := s.release(ctx, false); err != nil {
return nil, fmt.Errorf("could not release linking object: %w", err)
}
}
return ids, nil
}
func (s *payloadSizeLimiter) fillHeader() (*AccessIdentifiers, error) {
curEpoch := s.NetworkState.CurrentEpoch() curEpoch := s.NetworkState.CurrentEpoch()
ver := version.Current() ver := version.Current()
@ -185,36 +218,12 @@ func (s *payloadSizeLimiter) release(finalize bool) (*AccessIdentifiers, error)
return nil, fmt.Errorf("could not finalize object: %w", err) return nil, fmt.Errorf("could not finalize object: %w", err)
} }
if err := s.NextTarget.WriteHeader(s.current); err != nil {
return nil, fmt.Errorf("could not write header to next target: %w", err)
}
if _, err := s.NextTarget.Close(); err != nil {
return nil, fmt.Errorf("could not close next target: %w", err)
}
id, _ := s.current.ID() id, _ := s.current.ID()
return &AccessIdentifiers{
ids := &AccessIdentifiers{
ParentID: parID, ParentID: parID,
SelfID: id, SelfID: id,
ParentHeader: parHdr, ParentHeader: parHdr,
} }, nil
// save identifier of the released object
s.previous = append(s.previous, ids.SelfID)
if withParent {
// generate and release linking object
s.initializeLinking(ids.ParentHeader)
s.initializeCurrent()
if _, err := s.release(false); err != nil {
return nil, fmt.Errorf("could not release linking object: %w", err)
}
}
return ids, nil
} }
func (s *payloadSizeLimiter) initializeLinking(parHdr *object.Object) { func (s *payloadSizeLimiter) initializeLinking(parHdr *object.Object) {
@ -224,7 +233,7 @@ func (s *payloadSizeLimiter) initializeLinking(parHdr *object.Object) {
s.current.SetSplitID(s.splitID) s.current.SetSplitID(s.splitID)
} }
func (s *payloadSizeLimiter) writeChunk(chunk []byte) error { func (s *payloadSizeLimiter) writeChunk(ctx context.Context, chunk []byte) error {
for { for {
// statement is true if the previous write of bytes reached exactly the boundary. // statement is true if the previous write of bytes reached exactly the boundary.
if s.written > 0 && s.written%s.MaxSize == 0 { if s.written > 0 && s.written%s.MaxSize == 0 {
@ -233,7 +242,7 @@ func (s *payloadSizeLimiter) writeChunk(chunk []byte) error {
} }
// we need to release current object // we need to release current object
if _, err := s.release(false); err != nil { if _, err := s.release(ctx, false); err != nil {
return fmt.Errorf("could not release object: %w", err) return fmt.Errorf("could not release object: %w", err)
} }
@ -269,10 +278,7 @@ func (s *payloadSizeLimiter) writeChunk(chunk []byte) error {
} }
func (s *payloadSizeLimiter) writeHashes(chunk []byte) error { func (s *payloadSizeLimiter) writeHashes(chunk []byte) error {
_, err := s.NextTarget.Write(chunk) s.payload = append(s.payload, chunk...)
if err != nil {
return err
}
// The `Write` method of `hash.Hash` never returns an error. // The `Write` method of `hash.Hash` never returns an error.
for i := range s.currentHashers { for i := range s.currentHashers {

View file

@ -1,6 +1,7 @@
package transformer package transformer
import ( import (
"context"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"testing" "testing"
@ -19,7 +20,7 @@ func TestTransformer(t *testing.T) {
tt := new(testTarget) tt := new(testTarget)
target, pk := newPayloadSizeLimiter(maxSize, tt) target, pk := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return tt })
cnr := cidtest.ID() cnr := cidtest.ID()
hdr := newObject(cnr) hdr := newObject(cnr)
@ -31,7 +32,7 @@ func TestTransformer(t *testing.T) {
expectedPayload := make([]byte, maxSize*2+maxSize/2) expectedPayload := make([]byte, maxSize*2+maxSize/2)
_, _ = rand.Read(expectedPayload) _, _ = rand.Read(expectedPayload)
ids := writeObject(t, target, hdr, expectedPayload) ids := writeObject(t, context.Background(), target, hdr, expectedPayload)
require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object
var actualPayload []byte var actualPayload []byte
@ -54,15 +55,28 @@ func TestTransformer(t *testing.T) {
require.Equal(t, h[:], cs.Value()) require.Equal(t, h[:], cs.Value())
} }
require.True(t, tt.objects[i].VerifyIDSignature())
switch i { switch i {
case 0, 1: case 0, 1:
require.EqualValues(t, maxSize, len(payload)) require.EqualValues(t, maxSize, len(payload))
require.Nil(t, tt.objects[i].Parent())
case 2: case 2:
require.EqualValues(t, maxSize/2, len(payload)) require.EqualValues(t, maxSize/2, len(payload))
parent := tt.objects[i].Parent()
require.NotNil(t, parent)
require.Nil(t, parent.SplitID())
require.True(t, parent.VerifyIDSignature())
case 3: case 3:
parID, ok := tt.objects[i].ParentID() parID, ok := tt.objects[i].ParentID()
require.True(t, ok) require.True(t, ok)
require.Equal(t, ids.ParentID, &parID) require.Equal(t, ids.ParentID, &parID)
children := tt.objects[i].Children()
for j := 0; j < i; j++ {
id, ok := tt.objects[j].ID()
require.True(t, ok)
require.Equal(t, id, children[j])
}
} }
} }
require.Equal(t, expectedPayload, actualPayload) require.Equal(t, expectedPayload, actualPayload)
@ -85,13 +99,13 @@ func newObject(cnr cid.ID) *objectSDK.Object {
return hdr return hdr
} }
func writeObject(t *testing.T, target ObjectTarget, header *objectSDK.Object, payload []byte) *AccessIdentifiers { func writeObject(t *testing.T, ctx context.Context, target ChunkedObjectWriter, header *objectSDK.Object, payload []byte) *AccessIdentifiers {
require.NoError(t, target.WriteHeader(header)) require.NoError(t, target.WriteHeader(ctx, header))
_, err := target.Write(payload) _, err := target.Write(ctx, payload)
require.NoError(t, err) require.NoError(t, err)
ids, err := target.Close() ids, err := target.Close(ctx)
require.NoError(t, err) require.NoError(t, err)
return ids return ids
@ -112,24 +126,25 @@ func benchmarkTransformer(b *testing.B, header *objectSDK.Object, payloadSize in
const maxSize = 64 * 1024 * 1024 const maxSize = 64 * 1024 * 1024
payload := make([]byte, payloadSize) payload := make([]byte, payloadSize)
ctx := context.Background()
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
f, _ := newPayloadSizeLimiter(maxSize, benchTarget{}) f, _ := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return benchTarget{} })
if err := f.WriteHeader(header); err != nil { if err := f.WriteHeader(ctx, header); err != nil {
b.Fatalf("write header: %v", err) b.Fatalf("write header: %v", err)
} }
if _, err := f.Write(payload); err != nil { if _, err := f.Write(ctx, payload); err != nil {
b.Fatalf("write: %v", err) b.Fatalf("write: %v", err)
} }
if _, err := f.Close(); err != nil { if _, err := f.Close(ctx); err != nil {
b.Fatalf("close: %v", err) b.Fatalf("close: %v", err)
} }
} }
} }
func newPayloadSizeLimiter(maxSize uint64, nextTarget ObjectTarget) (ObjectTarget, *keys.PrivateKey) { func newPayloadSizeLimiter(maxSize uint64, nextTarget TargetInitializer) (ChunkedObjectWriter, *keys.PrivateKey) {
p, err := keys.NewPrivateKey() p, err := keys.NewPrivateKey()
if err != nil { if err != nil {
panic(err) panic(err)
@ -137,7 +152,7 @@ func newPayloadSizeLimiter(maxSize uint64, nextTarget ObjectTarget) (ObjectTarge
return NewPayloadSizeLimiter(Params{ return NewPayloadSizeLimiter(Params{
Key: &p.PrivateKey, Key: &p.PrivateKey,
NextTarget: nextTarget, NextTargetInit: nextTarget,
NetworkState: dummyEpochSource(123), NetworkState: dummyEpochSource(123),
MaxSize: maxSize, MaxSize: maxSize,
WithoutHomomorphicHash: true, WithoutHomomorphicHash: true,
@ -152,38 +167,15 @@ func (s dummyEpochSource) CurrentEpoch() uint64 {
type benchTarget struct{} type benchTarget struct{}
func (benchTarget) WriteHeader(object *objectSDK.Object) error { func (benchTarget) WriteObject(context.Context, *objectSDK.Object) error {
return nil return nil
} }
func (benchTarget) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (benchTarget) Close() (*AccessIdentifiers, error) {
return nil, nil
}
type testTarget struct { type testTarget struct {
current *objectSDK.Object
payload []byte
objects []*objectSDK.Object objects []*objectSDK.Object
} }
func (tt *testTarget) WriteHeader(object *objectSDK.Object) error { func (tt *testTarget) WriteObject(_ context.Context, o *objectSDK.Object) error {
tt.current = object tt.objects = append(tt.objects, o)
return nil return nil // AccessIdentifiers should not be used.
}
func (tt *testTarget) Write(p []byte) (n int, err error) {
tt.payload = append(tt.payload, p...)
return len(p), nil
}
func (tt *testTarget) Close() (*AccessIdentifiers, error) {
tt.current.SetPayload(tt.payload)
tt.objects = append(tt.objects, tt.current)
tt.current = nil
tt.payload = nil
return nil, nil // AccessIdentifiers should not be used.
} }

View file

@ -1,7 +1,7 @@
package transformer package transformer
import ( import (
"io" "context"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
@ -21,8 +21,9 @@ type EpochSource interface {
CurrentEpoch() uint64 CurrentEpoch() uint64
} }
// ObjectTarget is an interface of the object writer. // ChunkedObjectWriter is an interface of the object writer
type ObjectTarget interface { // that writes object chunked.
type ChunkedObjectWriter interface {
// WriteHeader writes object header w/ payload part. // WriteHeader writes object header w/ payload part.
// The payload of the object may be incomplete. // The payload of the object may be incomplete.
// //
@ -31,14 +32,14 @@ type ObjectTarget interface {
// that depends on the implementation. // that depends on the implementation.
// //
// Must not be called after Close call. // Must not be called after Close call.
WriteHeader(*object.Object) error WriteHeader(context.Context, *object.Object) error
// Write writes object payload chunk. // Write writes object payload chunk.
// //
// Can be called multiple times. // Can be called multiple times.
// //
// Must not be called after Close call. // Must not be called after Close call.
io.Writer Write(context.Context, []byte) (int, error)
// Close is used to finish object writing. // Close is used to finish object writing.
// //
@ -48,8 +49,14 @@ type ObjectTarget interface {
// Must be called no more than once. Control remains with the caller. // Must be called no more than once. Control remains with the caller.
// Re-calling can lead to undefined behavior // Re-calling can lead to undefined behavior
// that depends on the implementation. // that depends on the implementation.
Close() (*AccessIdentifiers, error) Close(context.Context) (*AccessIdentifiers, error)
} }
// TargetInitializer represents ObjectTarget constructor. // TargetInitializer represents ObjectWriter constructor.
type TargetInitializer func() ObjectTarget type TargetInitializer func() ObjectWriter
// ObjectWriter is an interface of the object writer that writes prepared object.
type ObjectWriter interface {
// WriteObject writes prepared object.
WriteObject(context.Context, *object.Object) error
}

View file

@ -9,7 +9,7 @@ type Type object.Type
const ( const (
TypeRegular Type = iota TypeRegular Type = iota
TypeTombstone TypeTombstone
TypeStorageGroup _
TypeLock TypeLock
) )
@ -25,7 +25,6 @@ func TypeFromV2(t object.Type) Type {
// //
// String mapping: // String mapping:
// - TypeTombstone: TOMBSTONE; // - TypeTombstone: TOMBSTONE;
// - TypeStorageGroup: STORAGE_GROUP;
// - TypeLock: LOCK; // - TypeLock: LOCK;
// - TypeRegular, default: REGULAR. // - TypeRegular, default: REGULAR.
func (t Type) String() string { func (t Type) String() string {

View file

@ -21,10 +21,6 @@ func TestType_ToV2(t *testing.T) {
t: object.TypeTombstone, t: object.TypeTombstone,
t2: v2object.TypeTombstone, t2: v2object.TypeTombstone,
}, },
{
t: object.TypeStorageGroup,
t2: v2object.TypeStorageGroup,
},
{ {
t: object.TypeLock, t: object.TypeLock,
t2: v2object.TypeLock, t2: v2object.TypeLock,
@ -47,7 +43,6 @@ func TestType_String(t *testing.T) {
testEnumStrings(t, new(object.Type), []enumStringItem{ testEnumStrings(t, new(object.Type), []enumStringItem{
{val: toPtr(object.TypeTombstone), str: "TOMBSTONE"}, {val: toPtr(object.TypeTombstone), str: "TOMBSTONE"},
{val: toPtr(object.TypeStorageGroup), str: "STORAGE_GROUP"},
{val: toPtr(object.TypeRegular), str: "REGULAR"}, {val: toPtr(object.TypeRegular), str: "REGULAR"},
{val: toPtr(object.TypeLock), str: "LOCK"}, {val: toPtr(object.TypeLock), str: "LOCK"},
}) })

View file

@ -10,7 +10,7 @@ import (
type sessionCache struct { type sessionCache struct {
cache *lru.Cache[string, *cacheValue] cache *lru.Cache[string, *cacheValue]
currentEpoch uint64 currentEpoch atomic.Uint64
} }
type cacheValue struct { type cacheValue struct {
@ -58,14 +58,14 @@ func (c *sessionCache) DeleteByPrefix(prefix string) {
} }
func (c *sessionCache) updateEpoch(newEpoch uint64) { func (c *sessionCache) updateEpoch(newEpoch uint64) {
epoch := atomic.LoadUint64(&c.currentEpoch) epoch := c.currentEpoch.Load()
if newEpoch > epoch { if newEpoch > epoch {
atomic.StoreUint64(&c.currentEpoch, newEpoch) c.currentEpoch.Store(newEpoch)
} }
} }
func (c *sessionCache) expired(val *cacheValue) bool { func (c *sessionCache) expired(val *cacheValue) bool {
epoch := atomic.LoadUint64(&c.currentEpoch) epoch := c.currentEpoch.Load()
// use epoch+1 (clear cache beforehand) to prevent 'expired session token' error right after epoch tick // use epoch+1 (clear cache beforehand) to prevent 'expired session token' error right after epoch tick
return val.token.ExpiredAt(epoch + 1) return val.token.ExpiredAt(epoch + 1)
} }

View file

@ -11,6 +11,7 @@ import (
"math/rand" "math/rand"
"sort" "sort"
"sync" "sync"
"sync/atomic"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
@ -29,9 +30,9 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"go.uber.org/atomic"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"google.golang.org/grpc"
) )
// client represents virtual connection to the single FrostFS network endpoint from which Pool is formed. // client represents virtual connection to the single FrostFS network endpoint from which Pool is formed.
@ -194,10 +195,13 @@ func newClientStatusMonitor(logger *zap.Logger, addr string, errorThreshold uint
methods[i] = &methodStatus{name: i.String()} methods[i] = &methodStatus{name: i.String()}
} }
healthy := new(atomic.Bool)
healthy.Store(true)
return clientStatusMonitor{ return clientStatusMonitor{
logger: logger, logger: logger,
addr: addr, addr: addr,
healthy: atomic.NewBool(true), healthy: healthy,
errorThreshold: errorThreshold, errorThreshold: errorThreshold,
methods: methods, methods: methods,
} }
@ -235,6 +239,7 @@ type wrapperPrm struct {
errorThreshold uint32 errorThreshold uint32
responseInfoCallback func(sdkClient.ResponseMetaInfo) error responseInfoCallback func(sdkClient.ResponseMetaInfo) error
poolRequestInfoCallback func(RequestInfo) poolRequestInfoCallback func(RequestInfo)
dialOptions []grpc.DialOption
} }
// setAddress sets endpoint to connect in FrostFS network. // setAddress sets endpoint to connect in FrostFS network.
@ -273,6 +278,11 @@ func (x *wrapperPrm) setResponseInfoCallback(f func(sdkClient.ResponseMetaInfo)
x.responseInfoCallback = f x.responseInfoCallback = f
} }
// setGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
func (x *wrapperPrm) setGRPCDialOptions(opts []grpc.DialOption) {
x.dialOptions = opts
}
// newWrapper creates a clientWrapper that implements the client interface. // newWrapper creates a clientWrapper that implements the client interface.
func newWrapper(prm wrapperPrm) *clientWrapper { func newWrapper(prm wrapperPrm) *clientWrapper {
var cl sdkClient.Client var cl sdkClient.Client
@ -304,6 +314,7 @@ func (c *clientWrapper) dial(ctx context.Context) error {
prmDial.SetServerURI(c.prm.address) prmDial.SetServerURI(c.prm.address)
prmDial.SetTimeout(c.prm.dialTimeout) prmDial.SetTimeout(c.prm.dialTimeout)
prmDial.SetStreamTimeout(c.prm.streamTimeout) prmDial.SetStreamTimeout(c.prm.streamTimeout)
prmDial.SetGRPCDialOptions(c.prm.dialOptions...)
if err = cl.Dial(ctx, prmDial); err != nil { if err = cl.Dial(ctx, prmDial); err != nil {
c.setUnhealthy() c.setUnhealthy()
@ -334,6 +345,7 @@ func (c *clientWrapper) restartIfUnhealthy(ctx context.Context) (healthy, change
prmDial.SetServerURI(c.prm.address) prmDial.SetServerURI(c.prm.address)
prmDial.SetTimeout(c.prm.dialTimeout) prmDial.SetTimeout(c.prm.dialTimeout)
prmDial.SetStreamTimeout(c.prm.streamTimeout) prmDial.SetStreamTimeout(c.prm.streamTimeout)
prmDial.SetGRPCDialOptions(c.prm.dialOptions...)
if err := cl.Dial(ctx, prmDial); err != nil { if err := cl.Dial(ctx, prmDial); err != nil {
c.setUnhealthy() c.setUnhealthy()
@ -395,7 +407,7 @@ func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) (
} }
start := time.Now() start := time.Now()
res, err := cl.ContainerPut(ctx, prm.prmClient) res, err := cl.ContainerPut(ctx, prm.ClientParams)
c.incRequests(time.Since(start), methodContainerPut) c.incRequests(time.Since(start), methodContainerPut)
var st apistatus.Status var st apistatus.Status
if res != nil { if res != nil {
@ -405,13 +417,16 @@ func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) (
return cid.ID{}, fmt.Errorf("container put on client: %w", err) return cid.ID{}, fmt.Errorf("container put on client: %w", err)
} }
if !prm.waitParamsSet { if prm.WaitParams == nil {
prm.waitParams.setDefaults() prm.WaitParams = defaultWaitParams()
}
if err = prm.WaitParams.CheckValidity(); err != nil {
return cid.ID{}, fmt.Errorf("invalid wait parameters: %w", err)
} }
idCnr := res.ID() idCnr := res.ID()
err = waitForContainerPresence(ctx, c, idCnr, &prm.waitParams) err = waitForContainerPresence(ctx, c, idCnr, prm.WaitParams)
if err = c.handleError(ctx, nil, err); err != nil { if err = c.handleError(ctx, nil, err); err != nil {
return cid.ID{}, fmt.Errorf("wait container presence on client: %w", err) return cid.ID{}, fmt.Errorf("wait container presence on client: %w", err)
} }
@ -426,8 +441,9 @@ func (c *clientWrapper) containerGet(ctx context.Context, prm PrmContainerGet) (
return container.Container{}, err return container.Container{}, err
} }
var cliPrm sdkClient.PrmContainerGet cliPrm := sdkClient.PrmContainerGet{
cliPrm.SetContainer(prm.cnrID) ContainerID: &prm.ContainerID,
}
start := time.Now() start := time.Now()
res, err := cl.ContainerGet(ctx, cliPrm) res, err := cl.ContainerGet(ctx, cliPrm)
@ -474,10 +490,9 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
return err return err
} }
var cliPrm sdkClient.PrmContainerDelete cliPrm := sdkClient.PrmContainerDelete{
cliPrm.SetContainer(prm.cnrID) ContainerID: &prm.ContainerID,
if prm.stokenSet { Session: prm.Session,
cliPrm.WithinSession(prm.stoken)
} }
start := time.Now() start := time.Now()
@ -491,11 +506,14 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
return fmt.Errorf("container delete on client: %w", err) return fmt.Errorf("container delete on client: %w", err)
} }
if !prm.waitParamsSet { if prm.WaitParams == nil {
prm.waitParams.setDefaults() prm.WaitParams = defaultWaitParams()
}
if err := prm.WaitParams.CheckValidity(); err != nil {
return fmt.Errorf("invalid wait parameters: %w", err)
} }
return waitForContainerRemoved(ctx, c, &prm.cnrID, &prm.waitParams) return waitForContainerRemoved(ctx, c, &prm.ContainerID, prm.WaitParams)
} }
// containerEACL invokes sdkClient.ContainerEACL parse response status to error and return result as is. // containerEACL invokes sdkClient.ContainerEACL parse response status to error and return result as is.
@ -615,7 +633,7 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
} }
var cliPrm sdkClient.PrmObjectPutInit var cliPrm sdkClient.PrmObjectPutInit
cliPrm.SetCopiesNumber(prm.copiesNumber) // TODO(@ironbee): adopt multiple copy number https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/44 cliPrm.SetCopiesNumberByVectors(prm.copiesNumber)
if prm.stoken != nil { if prm.stoken != nil {
cliPrm.WithinSession(*prm.stoken) cliPrm.WithinSession(*prm.stoken)
} }
@ -633,7 +651,7 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
return oid.ID{}, fmt.Errorf("init writing on API client: %w", err) return oid.ID{}, fmt.Errorf("init writing on API client: %w", err)
} }
if wObj.WriteHeader(prm.hdr) { if wObj.WriteHeader(ctx, prm.hdr) {
sz := prm.hdr.PayloadSize() sz := prm.hdr.PayloadSize()
if data := prm.hdr.Payload(); len(data) > 0 { if data := prm.hdr.Payload(); len(data) > 0 {
@ -660,7 +678,7 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
n, err = prm.payload.Read(buf) n, err = prm.payload.Read(buf)
if n > 0 { if n > 0 {
start = time.Now() start = time.Now()
successWrite := wObj.WritePayloadChunk(buf[:n]) successWrite := wObj.WritePayloadChunk(ctx, buf[:n])
c.incRequests(time.Since(start), methodObjectPut) c.incRequests(time.Since(start), methodObjectPut)
if !successWrite { if !successWrite {
break break
@ -678,7 +696,7 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
} }
} }
res, err := wObj.Close() res, err := wObj.Close(ctx)
var st apistatus.Status var st apistatus.Status
if res != nil { if res != nil {
st = res.Status() st = res.Status()
@ -1002,10 +1020,10 @@ func (c *clientStatusMonitor) handleError(ctx context.Context, st apistatus.Stat
err = apistatus.ErrFromStatus(st) err = apistatus.ErrFromStatus(st)
switch err.(type) { switch err.(type) {
case apistatus.ServerInternal, *apistatus.ServerInternal, case *apistatus.ServerInternal,
apistatus.WrongMagicNumber, *apistatus.WrongMagicNumber, *apistatus.WrongMagicNumber,
apistatus.SignatureVerification, *apistatus.SignatureVerification, *apistatus.SignatureVerification,
apistatus.NodeUnderMaintenance, *apistatus.NodeUnderMaintenance: *apistatus.NodeUnderMaintenance:
c.incErrorRate() c.incErrorRate()
} }
@ -1051,6 +1069,7 @@ type InitParameters struct {
errorThreshold uint32 errorThreshold uint32
nodeParams []NodeParam nodeParams []NodeParam
requestCallback func(RequestInfo) requestCallback func(RequestInfo)
dialOptions []grpc.DialOption
clientBuilder clientBuilder clientBuilder clientBuilder
} }
@ -1110,6 +1129,11 @@ func (x *InitParameters) AddNode(nodeParam NodeParam) {
x.nodeParams = append(x.nodeParams, nodeParam) x.nodeParams = append(x.nodeParams, nodeParam)
} }
// SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
func (x *InitParameters) SetGRPCDialOptions(opts ...grpc.DialOption) {
x.dialOptions = opts
}
// setClientBuilder sets clientBuilder used for client construction. // setClientBuilder sets clientBuilder used for client construction.
// Wraps setClientBuilderContext without a context. // Wraps setClientBuilderContext without a context.
func (x *InitParameters) setClientBuilder(builder clientBuilder) { func (x *InitParameters) setClientBuilder(builder clientBuilder) {
@ -1157,44 +1181,82 @@ func (x *NodeParam) SetPriority(priority int) {
x.priority = priority x.priority = priority
} }
// Priority returns priority of the node.
func (x *NodeParam) Priority() int {
return x.priority
}
// SetAddress specifies address of the node. // SetAddress specifies address of the node.
func (x *NodeParam) SetAddress(address string) { func (x *NodeParam) SetAddress(address string) {
x.address = address x.address = address
} }
// Address returns address of the node.
func (x *NodeParam) Address() string {
return x.address
}
// SetWeight specifies weight of the node. // SetWeight specifies weight of the node.
func (x *NodeParam) SetWeight(weight float64) { func (x *NodeParam) SetWeight(weight float64) {
x.weight = weight x.weight = weight
} }
// Weight returns weight of the node.
func (x *NodeParam) Weight() float64 {
return x.weight
}
// WaitParams contains parameters used in polling is a something applied on FrostFS network. // WaitParams contains parameters used in polling is a something applied on FrostFS network.
type WaitParams struct { type WaitParams struct {
timeout time.Duration Timeout time.Duration
pollInterval time.Duration PollInterval time.Duration
} }
// SetTimeout specifies the time to wait for the operation to complete. // SetTimeout specifies the time to wait for the operation to complete.
//
// Deprecated: Use WaitParams.Timeout instead.
func (x *WaitParams) SetTimeout(timeout time.Duration) { func (x *WaitParams) SetTimeout(timeout time.Duration) {
x.timeout = timeout x.Timeout = timeout
} }
// SetPollInterval specifies the interval, once it will check the completion of the operation. // SetPollInterval specifies the interval, once it will check the completion of the operation.
//
// Deprecated: Use WaitParams.PollInterval instead.
func (x *WaitParams) SetPollInterval(tick time.Duration) { func (x *WaitParams) SetPollInterval(tick time.Duration) {
x.pollInterval = tick x.PollInterval = tick
} }
// Deprecated: Use defaultWaitParams() instead.
func (x *WaitParams) setDefaults() { func (x *WaitParams) setDefaults() {
x.timeout = 120 * time.Second x.Timeout = 120 * time.Second
x.pollInterval = 5 * time.Second x.PollInterval = 5 * time.Second
}
func defaultWaitParams() *WaitParams {
return &WaitParams{
Timeout: 120 * time.Second,
PollInterval: 5 * time.Second,
}
} }
// checkForPositive panics if any of the wait params isn't positive. // checkForPositive panics if any of the wait params isn't positive.
func (x *WaitParams) checkForPositive() { func (x *WaitParams) checkForPositive() {
if x.timeout <= 0 || x.pollInterval <= 0 { if x.Timeout <= 0 || x.PollInterval <= 0 {
panic("all wait params must be positive") panic("all wait params must be positive")
} }
} }
// CheckForValid checks if all wait params are non-negative.
func (x *WaitParams) CheckValidity() error {
if x.Timeout <= 0 {
return errors.New("timeout cannot be negative")
}
if x.PollInterval <= 0 {
return errors.New("poll interval cannot be negative")
}
return nil
}
type prmContext struct { type prmContext struct {
defaultSession bool defaultSession bool
verb session.ObjectVerb verb session.ObjectVerb
@ -1257,7 +1319,7 @@ type PrmObjectPut struct {
payload io.Reader payload io.Reader
copiesNumber uint32 copiesNumber []uint32
} }
// SetHeader specifies header of the object. // SetHeader specifies header of the object.
@ -1273,6 +1335,12 @@ func (x *PrmObjectPut) SetPayload(payload io.Reader) {
// SetCopiesNumber sets number of object copies that is enough to consider put successful. // SetCopiesNumber sets number of object copies that is enough to consider put successful.
// Zero means using default behavior. // Zero means using default behavior.
func (x *PrmObjectPut) SetCopiesNumber(copiesNumber uint32) { func (x *PrmObjectPut) SetCopiesNumber(copiesNumber uint32) {
x.copiesNumber = []uint32{copiesNumber}
}
// SetCopiesNumberVector sets number of object copies that is enough to consider put successful, provided as array.
// Nil/empty vector means using default behavior.
func (x *PrmObjectPut) SetCopiesNumberVector(copiesNumber []uint32) {
x.copiesNumber = copiesNumber x.copiesNumber = copiesNumber
} }
@ -1361,45 +1429,51 @@ func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) {
// PrmContainerPut groups parameters of PutContainer operation. // PrmContainerPut groups parameters of PutContainer operation.
type PrmContainerPut struct { type PrmContainerPut struct {
prmClient sdkClient.PrmContainerPut ClientParams sdkClient.PrmContainerPut
waitParams WaitParams WaitParams *WaitParams
waitParamsSet bool
} }
// SetContainer container structure to be used as a parameter of the base // SetContainer container structure to be used as a parameter of the base
// client's operation. // client's operation.
// //
// See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.SetContainer. // See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.SetContainer.
//
// Deprecated: Use PrmContainerPut.ClientParams.Container instead.
func (x *PrmContainerPut) SetContainer(cnr container.Container) { func (x *PrmContainerPut) SetContainer(cnr container.Container) {
x.prmClient.SetContainer(cnr) x.ClientParams.SetContainer(cnr)
} }
// WithinSession specifies session to be used as a parameter of the base // WithinSession specifies session to be used as a parameter of the base
// client's operation. // client's operation.
// //
// See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.WithinSession. // See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.WithinSession.
//
// Deprecated: Use PrmContainerPut.ClientParams.Session instead.
func (x *PrmContainerPut) WithinSession(s session.Container) { func (x *PrmContainerPut) WithinSession(s session.Container) {
x.prmClient.WithinSession(s) x.ClientParams.WithinSession(s)
} }
// SetWaitParams specifies timeout params to complete operation. // SetWaitParams specifies timeout params to complete operation.
// If not provided the default one will be used. // If not provided the default one will be used.
// Panics if any of the wait params isn't positive. // Panics if any of the wait params isn't positive.
//
// Deprecated: Use PrmContainerPut.ClientParams.WaitParams instead.
func (x *PrmContainerPut) SetWaitParams(waitParams WaitParams) { func (x *PrmContainerPut) SetWaitParams(waitParams WaitParams) {
waitParams.checkForPositive() waitParams.checkForPositive()
x.waitParams = waitParams x.WaitParams = &waitParams
x.waitParamsSet = true
} }
// PrmContainerGet groups parameters of GetContainer operation. // PrmContainerGet groups parameters of GetContainer operation.
type PrmContainerGet struct { type PrmContainerGet struct {
cnrID cid.ID ContainerID cid.ID
} }
// SetContainerID specifies identifier of the container to be read. // SetContainerID specifies identifier of the container to be read.
func (x *PrmContainerGet) SetContainerID(cnrID cid.ID) { //
x.cnrID = cnrID // Deprecated: Use PrmContainerGet.ContainerID instead.
func (prm *PrmContainerGet) SetContainerID(cnrID cid.ID) {
prm.ContainerID = cnrID
} }
// PrmContainerList groups parameters of ListContainers operation. // PrmContainerList groups parameters of ListContainers operation.
@ -1414,33 +1488,35 @@ func (x *PrmContainerList) SetOwnerID(ownerID user.ID) {
// PrmContainerDelete groups parameters of DeleteContainer operation. // PrmContainerDelete groups parameters of DeleteContainer operation.
type PrmContainerDelete struct { type PrmContainerDelete struct {
cnrID cid.ID ContainerID cid.ID
stoken session.Container Session *session.Container
stokenSet bool
waitParams WaitParams WaitParams *WaitParams
waitParamsSet bool
} }
// SetContainerID specifies identifier of the FrostFS container to be removed. // SetContainerID specifies identifier of the FrostFS container to be removed.
//
// Deprecated: Use PrmContainerDelete.ContainerID instead.
func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) { func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) {
x.cnrID = cnrID x.ContainerID = cnrID
} }
// SetSessionToken specifies session within which operation should be performed. // SetSessionToken specifies session within which operation should be performed.
//
// Deprecated: Use PrmContainerDelete.Session instead.
func (x *PrmContainerDelete) SetSessionToken(token session.Container) { func (x *PrmContainerDelete) SetSessionToken(token session.Container) {
x.stoken = token x.Session = &token
x.stokenSet = true
} }
// SetWaitParams specifies timeout params to complete operation. // SetWaitParams specifies timeout params to complete operation.
// If not provided the default one will be used. // If not provided the default one will be used.
// Panics if any of the wait params isn't positive. // Panics if any of the wait params isn't positive.
//
// Deprecated: Use PrmContainerDelete.WaitParams instead.
func (x *PrmContainerDelete) SetWaitParams(waitParams WaitParams) { func (x *PrmContainerDelete) SetWaitParams(waitParams WaitParams) {
waitParams.checkForPositive() waitParams.checkForPositive()
x.waitParams = waitParams x.WaitParams = &waitParams
x.waitParamsSet = true
} }
// PrmContainerEACL groups parameters of GetEACL operation. // PrmContainerEACL groups parameters of GetEACL operation.
@ -1711,6 +1787,7 @@ func fillDefaultInitParams(params *InitParameters, cache *sessionCache) {
prm.setStreamTimeout(params.nodeStreamTimeout) prm.setStreamTimeout(params.nodeStreamTimeout)
prm.setErrorThreshold(params.errorThreshold) prm.setErrorThreshold(params.errorThreshold)
prm.setPoolRequestCallback(params.requestCallback) prm.setPoolRequestCallback(params.requestCallback)
prm.setGRPCDialOptions(params.dialOptions)
prm.setResponseInfoCallback(func(info sdkClient.ResponseMetaInfo) error { prm.setResponseInfoCallback(func(info sdkClient.ResponseMetaInfo) error {
cache.updateEpoch(info.Epoch()) cache.updateEpoch(info.Epoch())
return nil return nil
@ -1790,7 +1867,7 @@ func (p *Pool) updateInnerNodesHealth(ctx context.Context, i int, bufferWeights
pool := p.innerPools[i] pool := p.innerPools[i]
options := p.rebalanceParams options := p.rebalanceParams
healthyChanged := atomic.NewBool(false) healthyChanged := new(atomic.Bool)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for j, cli := range pool.clients { for j, cli := range pool.clients {
@ -2464,9 +2541,9 @@ func waitForContainerRemoved(ctx context.Context, cli client, cnrID *cid.ID, wai
// waitFor await that given condition will be met in waitParams time. // waitFor await that given condition will be met in waitParams time.
func waitFor(ctx context.Context, params *WaitParams, condition func(context.Context) bool) error { func waitFor(ctx context.Context, params *WaitParams, condition func(context.Context) bool) error {
wctx, cancel := context.WithTimeout(ctx, params.timeout) wctx, cancel := context.WithTimeout(ctx, params.Timeout)
defer cancel() defer cancel()
ticker := time.NewTimer(params.pollInterval) ticker := time.NewTimer(params.PollInterval)
defer ticker.Stop() defer ticker.Stop()
wdone := wctx.Done() wdone := wctx.Done()
done := ctx.Done() done := ctx.Done()
@ -2480,7 +2557,7 @@ func waitFor(ctx context.Context, params *WaitParams, condition func(context.Con
if condition(ctx) { if condition(ctx) {
return nil return nil
} }
ticker.Reset(params.pollInterval) ticker.Reset(params.PollInterval)
} }
} }
} }

View file

@ -271,7 +271,7 @@ func TestSessionCache(t *testing.T) {
mockClientBuilder := func(addr string) client { mockClientBuilder := func(addr string) client {
mockCli := newMockClient(addr, *key) mockCli := newMockClient(addr, *key)
mockCli.statusOnGetObject(apistatus.SessionTokenNotFound{}) mockCli.statusOnGetObject(new(apistatus.SessionTokenNotFound))
return mockCli return mockCli
} }
@ -483,8 +483,8 @@ func TestWaitPresence(t *testing.T) {
var idCnr cid.ID var idCnr cid.ID
err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{ err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{
timeout: 120 * time.Second, Timeout: 120 * time.Second,
pollInterval: 5 * time.Second, PollInterval: 5 * time.Second,
}) })
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "context canceled") require.Contains(t, err.Error(), "context canceled")
@ -494,8 +494,8 @@ func TestWaitPresence(t *testing.T) {
ctx := context.Background() ctx := context.Background()
var idCnr cid.ID var idCnr cid.ID
err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{ err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{
timeout: 500 * time.Millisecond, Timeout: 500 * time.Millisecond,
pollInterval: 5 * time.Second, PollInterval: 5 * time.Second,
}) })
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "context deadline exceeded") require.Contains(t, err.Error(), "context deadline exceeded")
@ -505,8 +505,8 @@ func TestWaitPresence(t *testing.T) {
ctx := context.Background() ctx := context.Background()
var idCnr cid.ID var idCnr cid.ID
err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{ err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{
timeout: 10 * time.Second, Timeout: 10 * time.Second,
pollInterval: 500 * time.Millisecond, PollInterval: 500 * time.Millisecond,
}) })
require.NoError(t, err) require.NoError(t, err)
}) })
@ -548,14 +548,14 @@ func TestHandleError(t *testing.T) {
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.SuccessDefaultV2{}, status: new(apistatus.SuccessDefaultV2),
err: nil, err: nil,
expectedError: false, expectedError: false,
countError: false, countError: false,
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.SuccessDefaultV2{}, status: new(apistatus.SuccessDefaultV2),
err: errors.New("error"), err: errors.New("error"),
expectedError: true, expectedError: true,
countError: true, countError: true,
@ -569,42 +569,42 @@ func TestHandleError(t *testing.T) {
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.ObjectNotFound{}, status: new(apistatus.ObjectNotFound),
err: nil, err: nil,
expectedError: true, expectedError: true,
countError: false, countError: false,
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.ServerInternal{}, status: new(apistatus.ServerInternal),
err: nil, err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.WrongMagicNumber{}, status: new(apistatus.WrongMagicNumber),
err: nil, err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.SignatureVerification{}, status: new(apistatus.SignatureVerification),
err: nil, err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
ctx: ctx, ctx: ctx,
status: &apistatus.SignatureVerification{}, status: new(apistatus.SignatureVerification),
err: nil, err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
}, },
{ {
ctx: ctx, ctx: ctx,
status: apistatus.NodeUnderMaintenance{}, status: new(apistatus.NodeUnderMaintenance),
err: nil, err: nil,
expectedError: true, expectedError: true,
countError: true, countError: true,
@ -649,7 +649,7 @@ func TestSwitchAfterErrorThreshold(t *testing.T) {
if addr == nodes[0].address { if addr == nodes[0].address {
mockCli := newMockClient(addr, *key) mockCli := newMockClient(addr, *key)
mockCli.setThreshold(uint32(errorThreshold)) mockCli.setThreshold(uint32(errorThreshold))
mockCli.statusOnGetObject(apistatus.ServerInternal{}) mockCli.statusOnGetObject(new(apistatus.ServerInternal))
return mockCli return mockCli
} }

136
pool/tree/client.go Normal file
View file

@ -0,0 +1,136 @@
package tree
import (
"context"
"crypto/tls"
"fmt"
"sync"
apiClient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
grpcService "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
type treeClient struct {
mu sync.RWMutex
address string
opts []grpc.DialOption
conn *grpc.ClientConn
service grpcService.TreeServiceClient
healthy bool
}
// newTreeClient creates new tree client with auto dial.
func newTreeClient(addr string, opts ...grpc.DialOption) *treeClient {
return &treeClient{
address: addr,
opts: opts,
}
}
func (c *treeClient) dial(ctx context.Context) error {
c.mu.Lock()
defer c.mu.Unlock()
if c.conn != nil {
return fmt.Errorf("couldn't dial '%s': connection already established", c.address)
}
var err error
if c.conn, c.service, err = dialClient(ctx, c.address, c.opts...); err != nil {
return err
}
if _, err = c.service.Healthcheck(ctx, &grpcService.HealthcheckRequest{}); err != nil {
return fmt.Errorf("healthcheck tree service: %w", err)
}
c.healthy = true
return nil
}
func (c *treeClient) redialIfNecessary(ctx context.Context) (healthHasChanged bool, err error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.conn == nil {
if c.conn, c.service, err = dialClient(ctx, c.address, c.opts...); err != nil {
return false, err
}
}
wasHealthy := c.healthy
if _, err = c.service.Healthcheck(ctx, &grpcService.HealthcheckRequest{}); err != nil {
c.healthy = false
return wasHealthy, fmt.Errorf("healthcheck tree service: %w", err)
}
c.healthy = true
return !wasHealthy, nil
}
func dialClient(ctx context.Context, addr string, clientOptions ...grpc.DialOption) (*grpc.ClientConn, grpcService.TreeServiceClient, error) {
host, tlsEnable, err := apiClient.ParseURI(addr)
if err != nil {
return nil, nil, fmt.Errorf("parse address: %w", err)
}
creds := insecure.NewCredentials()
if tlsEnable {
creds = credentials.NewTLS(&tls.Config{})
}
options := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
// the order is matter, we want client to be able to overwrite options.
opts := append(options, clientOptions...)
conn, err := grpc.DialContext(ctx, host, opts...)
if err != nil {
return nil, nil, fmt.Errorf("grpc dial node tree service: %w", err)
}
return conn, grpcService.NewTreeServiceClient(conn), nil
}
func (c *treeClient) serviceClient() (grpcService.TreeServiceClient, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.conn == nil || !c.healthy {
return nil, fmt.Errorf("unhealthy endpoint: '%s'", c.address)
}
return c.service, nil
}
func (c *treeClient) endpoint() string {
return c.address
}
func (c *treeClient) isHealthy() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.healthy
}
func (c *treeClient) setHealthy(val bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.healthy = val
}
func (c *treeClient) close() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.conn == nil {
return nil
}
return c.conn.Close()
}

755
pool/tree/pool.go Normal file
View file

@ -0,0 +1,755 @@
package tree
import (
"context"
"errors"
"fmt"
"io"
"sort"
"strings"
"sync"
"time"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
grpcService "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/service"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"google.golang.org/grpc"
)
const (
defaultRebalanceInterval = 15 * time.Second
defaultHealthcheckTimeout = 4 * time.Second
defaultDialTimeout = 5 * time.Second
defaultStreamTimeout = 10 * time.Second
)
var (
// ErrNodeNotFound is returned from Tree service in case of not found error.
ErrNodeNotFound = errors.New("not found")
// ErrNodeAccessDenied is returned from Tree service in case of access denied error.
ErrNodeAccessDenied = errors.New("access denied")
)
// client represents virtual connection to the single FrostFS tree service from which Pool is formed.
// This interface is expected to have exactly one production implementation - treeClient.
// Others are expected to be for test purposes only.
type client interface {
serviceClient() (grpcService.TreeServiceClient, error)
endpoint() string
isHealthy() bool
setHealthy(bool)
dial(ctx context.Context) error
redialIfNecessary(context.Context) (bool, error)
close() error
}
// InitParameters contains values used to initialize connection Pool.
type InitParameters struct {
key *keys.PrivateKey
logger *zap.Logger
nodeDialTimeout time.Duration
nodeStreamTimeout time.Duration
healthcheckTimeout time.Duration
clientRebalanceInterval time.Duration
nodeParams []pool.NodeParam
dialOptions []grpc.DialOption
}
// Pool represents virtual connection to the FrostFS tree services network to communicate
// with multiple FrostFS tree services without thinking about switching between servers
// due to their unavailability.
//
// Pool can be created and initialized using NewPool function.
// Before executing the FrostFS tree operations using the Pool, connection to the
// servers MUST BE correctly established (see Dial method).
type Pool struct {
innerPools []*innerPool
key *keys.PrivateKey
cancel context.CancelFunc
closedCh chan struct{}
rebalanceParams rebalanceParameters
dialOptions []grpc.DialOption
logger *zap.Logger
startIndicesMtx sync.RWMutex
// startIndices points to the client from which the next request will be executed.
// Since clients are stored in innerPool field we have to use two indices.
// These indices being changed during:
// * rebalance procedure (see Pool.startRebalance)
// * retry in case of request failure (see Pool.requestWithRetry)
startIndices [2]int
}
type innerPool struct {
clients []client
}
type rebalanceParameters struct {
nodesGroup [][]pool.NodeParam
nodeRequestTimeout time.Duration
clientRebalanceInterval time.Duration
}
// GetNodesParams groups parameters of Pool.GetNodes operation.
type GetNodesParams struct {
CID cid.ID
TreeID string
Path []string
Meta []string
PathAttribute string
LatestOnly bool
AllAttrs bool
BearerToken []byte
}
// GetSubTreeParams groups parameters of Pool.GetSubTree operation.
type GetSubTreeParams struct {
CID cid.ID
TreeID string
RootID uint64
Depth uint32
BearerToken []byte
}
// AddNodeParams groups parameters of Pool.AddNode operation.
type AddNodeParams struct {
CID cid.ID
TreeID string
Parent uint64
Meta map[string]string
BearerToken []byte
}
// AddNodeByPathParams groups parameters of Pool.AddNodeByPath operation.
type AddNodeByPathParams struct {
CID cid.ID
TreeID string
Path []string
Meta map[string]string
PathAttribute string
BearerToken []byte
}
// MoveNodeParams groups parameters of Pool.MoveNode operation.
type MoveNodeParams struct {
CID cid.ID
TreeID string
NodeID uint64
ParentID uint64
Meta map[string]string
BearerToken []byte
}
// RemoveNodeParams groups parameters of Pool.RemoveNode operation.
type RemoveNodeParams struct {
CID cid.ID
TreeID string
NodeID uint64
BearerToken []byte
}
// NewPool creates connection pool using parameters.
func NewPool(options InitParameters) (*Pool, error) {
if options.key == nil {
return nil, fmt.Errorf("missed required parameter 'Key'")
}
nodesParams, err := adjustNodeParams(options.nodeParams)
if err != nil {
return nil, err
}
fillDefaultInitParams(&options)
p := &Pool{
key: options.key,
logger: options.logger,
dialOptions: options.dialOptions,
rebalanceParams: rebalanceParameters{
nodesGroup: nodesParams,
nodeRequestTimeout: options.healthcheckTimeout,
clientRebalanceInterval: options.clientRebalanceInterval,
},
}
return p, nil
}
// Dial establishes a connection to the tree servers from the FrostFS network.
// It also starts a routine that checks the health of the nodes and
// updates the weights of the nodes for balancing.
// Returns an error describing failure reason.
//
// If failed, the Pool SHOULD NOT be used.
//
// See also InitParameters.SetClientRebalanceInterval.
func (p *Pool) Dial(ctx context.Context) error {
inner := make([]*innerPool, len(p.rebalanceParams.nodesGroup))
var atLeastOneHealthy bool
for i, nodes := range p.rebalanceParams.nodesGroup {
clients := make([]client, len(nodes))
for j, node := range nodes {
clients[j] = newTreeClient(node.Address(), p.dialOptions...)
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))
continue
}
atLeastOneHealthy = true
}
inner[i] = &innerPool{
clients: clients,
}
}
if !atLeastOneHealthy {
return fmt.Errorf("at least one node must be healthy")
}
ctx, cancel := context.WithCancel(ctx)
p.cancel = cancel
p.closedCh = make(chan struct{})
p.innerPools = inner
go p.startRebalance(ctx)
return nil
}
// SetKey specifies default key to be used for the protocol communication by default.
func (x *InitParameters) SetKey(key *keys.PrivateKey) {
x.key = key
}
// SetLogger specifies logger.
func (x *InitParameters) SetLogger(logger *zap.Logger) {
x.logger = logger
}
// SetNodeDialTimeout specifies the timeout for connection to be established.
func (x *InitParameters) SetNodeDialTimeout(timeout time.Duration) {
x.nodeDialTimeout = timeout
}
// SetNodeStreamTimeout specifies the timeout for individual operations in streaming RPC.
func (x *InitParameters) SetNodeStreamTimeout(timeout time.Duration) {
x.nodeStreamTimeout = timeout
}
// SetHealthcheckTimeout specifies the timeout for request to node to decide if it is alive.
//
// See also Pool.Dial.
func (x *InitParameters) SetHealthcheckTimeout(timeout time.Duration) {
x.healthcheckTimeout = timeout
}
// SetClientRebalanceInterval specifies the interval for updating nodes health status.
//
// See also Pool.Dial.
func (x *InitParameters) SetClientRebalanceInterval(interval time.Duration) {
x.clientRebalanceInterval = interval
}
// AddNode append information about the node to which you want to connect.
func (x *InitParameters) AddNode(nodeParam pool.NodeParam) {
x.nodeParams = append(x.nodeParams, nodeParam)
}
// SetGRPCDialOptions sets the gRPC dial options for new gRPC tree client connection.
func (x *InitParameters) SetGRPCDialOptions(opts ...grpc.DialOption) {
x.dialOptions = opts
}
// GetNodes invokes eponymous method from TreeServiceClient.
//
// Can return predefined errors:
// * ErrNodeNotFound
// * ErrNodeAccessDenied.
func (p *Pool) GetNodes(ctx context.Context, prm GetNodesParams) ([]*grpcService.GetNodeByPathResponse_Info, error) {
request := &grpcService.GetNodeByPathRequest{
Body: &grpcService.GetNodeByPathRequest_Body{
ContainerId: prm.CID[:],
TreeId: prm.TreeID,
Path: prm.Path,
Attributes: prm.Meta,
PathAttribute: prm.PathAttribute,
LatestOnly: prm.LatestOnly,
AllAttributes: prm.AllAttrs,
BearerToken: prm.BearerToken,
},
}
if err := p.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &grpcService.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return nil, err
}
var resp *grpcService.GetNodeByPathResponse
if err := p.requestWithRetry(func(client grpcService.TreeServiceClient) (inErr error) {
resp, inErr = client.GetNodeByPath(ctx, request)
return handleError("failed to get node by path", inErr)
}); err != nil {
return nil, err
}
return resp.GetBody().GetNodes(), nil
}
// SubTreeReader is designed to read list of subtree nodes FrostFS tree service.
//
// Must be initialized using Pool.GetSubTree, any other usage is unsafe.
type SubTreeReader struct {
cli grpcService.TreeService_GetSubTreeClient
}
// Read reads another list of the subtree nodes.
func (x *SubTreeReader) Read(buf []*grpcService.GetSubTreeResponse_Body) (int, error) {
for i := 0; i < len(buf); i++ {
resp, err := x.cli.Recv()
if err == io.EOF {
return i, io.EOF
} else if err != nil {
return i, handleError("failed to get sub tree", err)
}
buf[i] = resp.Body
}
return len(buf), nil
}
// ReadAll reads all nodes subtree nodes.
func (x *SubTreeReader) ReadAll() ([]*grpcService.GetSubTreeResponse_Body, error) {
var res []*grpcService.GetSubTreeResponse_Body
for {
resp, err := x.cli.Recv()
if err == io.EOF {
break
} else if err != nil {
return nil, handleError("failed to get sub tree", err)
}
res = append(res, resp.Body)
}
return res, nil
}
// Next gets the next node from subtree.
func (x *SubTreeReader) Next() (*grpcService.GetSubTreeResponse_Body, error) {
resp, err := x.cli.Recv()
if err == io.EOF {
return nil, io.EOF
}
if err != nil {
return nil, handleError("failed to get sub tree", err)
}
return resp.Body, nil
}
// GetSubTree invokes eponymous method from TreeServiceClient.
//
// Can return predefined errors:
// * ErrNodeNotFound
// * ErrNodeAccessDenied.
func (p *Pool) GetSubTree(ctx context.Context, prm GetSubTreeParams) (*SubTreeReader, error) {
request := &grpcService.GetSubTreeRequest{
Body: &grpcService.GetSubTreeRequest_Body{
ContainerId: prm.CID[:],
TreeId: prm.TreeID,
RootId: prm.RootID,
Depth: prm.Depth,
BearerToken: prm.BearerToken,
OrderBy: &grpcService.GetSubTreeRequest_Body_Order{
Direction: grpcService.GetSubTreeRequest_Body_Order_Asc,
},
},
}
if err := p.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &grpcService.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return nil, err
}
var cli grpcService.TreeService_GetSubTreeClient
if err := p.requestWithRetry(func(client grpcService.TreeServiceClient) (inErr error) {
cli, inErr = client.GetSubTree(ctx, request)
return handleError("failed to get sub tree client", inErr)
}); err != nil {
return nil, err
}
return &SubTreeReader{cli: cli}, nil
}
// AddNode invokes eponymous method from TreeServiceClient.
//
// Can return predefined errors:
// * ErrNodeNotFound
// * ErrNodeAccessDenied.
func (p *Pool) AddNode(ctx context.Context, prm AddNodeParams) (uint64, error) {
request := &grpcService.AddRequest{
Body: &grpcService.AddRequest_Body{
ContainerId: prm.CID[:],
TreeId: prm.TreeID,
ParentId: prm.Parent,
Meta: metaToKV(prm.Meta),
BearerToken: prm.BearerToken,
},
}
if err := p.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &grpcService.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return 0, err
}
var resp *grpcService.AddResponse
if err := p.requestWithRetry(func(client grpcService.TreeServiceClient) (inErr error) {
resp, inErr = client.Add(ctx, request)
return handleError("failed to add node", inErr)
}); err != nil {
return 0, err
}
return resp.GetBody().GetNodeId(), nil
}
// AddNodeByPath invokes eponymous method from TreeServiceClient.
//
// Can return predefined errors:
// * ErrNodeNotFound
// * ErrNodeAccessDenied.
func (p *Pool) AddNodeByPath(ctx context.Context, prm AddNodeByPathParams) (uint64, error) {
request := &grpcService.AddByPathRequest{
Body: &grpcService.AddByPathRequest_Body{
ContainerId: prm.CID[:],
TreeId: prm.TreeID,
Path: prm.Path,
Meta: metaToKV(prm.Meta),
PathAttribute: prm.PathAttribute,
BearerToken: prm.BearerToken,
},
}
if err := p.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &grpcService.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return 0, err
}
var resp *grpcService.AddByPathResponse
if err := p.requestWithRetry(func(client grpcService.TreeServiceClient) (inErr error) {
resp, inErr = client.AddByPath(ctx, request)
return handleError("failed to add node by path", inErr)
}); err != nil {
return 0, err
}
body := resp.GetBody()
if body == nil {
return 0, errors.New("nil body in tree service response")
} else if len(body.Nodes) == 0 {
return 0, errors.New("empty list of added nodes in tree service response")
}
// The first node is the leaf that we add, according to tree service docs.
return body.Nodes[0], nil
}
// MoveNode invokes eponymous method from TreeServiceClient.
//
// Can return predefined errors:
// * ErrNodeNotFound
// * ErrNodeAccessDenied.
func (p *Pool) MoveNode(ctx context.Context, prm MoveNodeParams) error {
request := &grpcService.MoveRequest{
Body: &grpcService.MoveRequest_Body{
ContainerId: prm.CID[:],
TreeId: prm.TreeID,
NodeId: prm.NodeID,
ParentId: prm.ParentID,
Meta: metaToKV(prm.Meta),
BearerToken: prm.BearerToken,
},
}
if err := p.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &grpcService.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return err
}
return p.requestWithRetry(func(client grpcService.TreeServiceClient) error {
if _, err := client.Move(ctx, request); err != nil {
return handleError("failed to move node", err)
}
return nil
})
}
// RemoveNode invokes eponymous method from TreeServiceClient.
//
// Can return predefined errors:
// * ErrNodeNotFound
// * ErrNodeAccessDenied.
func (p *Pool) RemoveNode(ctx context.Context, prm RemoveNodeParams) error {
request := &grpcService.RemoveRequest{
Body: &grpcService.RemoveRequest_Body{
ContainerId: prm.CID[:],
TreeId: prm.TreeID,
NodeId: prm.NodeID,
BearerToken: prm.BearerToken,
},
}
if err := p.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &grpcService.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return err
}
return p.requestWithRetry(func(client grpcService.TreeServiceClient) error {
if _, err := client.Remove(ctx, request); err != nil {
return handleError("failed to remove node", err)
}
return nil
})
}
// Close closes the Pool and releases all the associated resources.
func (p *Pool) Close() error {
p.cancel()
<-p.closedCh
var err error
for _, group := range p.innerPools {
for _, cl := range group.clients {
if closeErr := cl.close(); closeErr != nil {
p.log(zapcore.ErrorLevel, "close client connection", zap.Error(closeErr))
err = closeErr
}
}
}
return err
}
func handleError(msg string, err error) error {
if err == nil {
return nil
}
if strings.Contains(err.Error(), "not found") {
return fmt.Errorf("%w: %s", ErrNodeNotFound, err.Error())
} else if strings.Contains(err.Error(), "is denied by") {
return fmt.Errorf("%w: %s", ErrNodeAccessDenied, err.Error())
}
return fmt.Errorf("%s: %w", msg, err)
}
func metaToKV(meta map[string]string) []*grpcService.KeyValue {
result := make([]*grpcService.KeyValue, 0, len(meta))
for key, value := range meta {
result = append(result, &grpcService.KeyValue{Key: key, Value: []byte(value)})
}
return result
}
func adjustNodeParams(nodeParams []pool.NodeParam) ([][]pool.NodeParam, error) {
if len(nodeParams) == 0 {
return nil, errors.New("no FrostFS peers configured")
}
nodeParamsMap := make(map[int][]pool.NodeParam)
for _, param := range nodeParams {
nodes := nodeParamsMap[param.Priority()]
nodeParamsMap[param.Priority()] = append(nodes, param)
}
res := make([][]pool.NodeParam, 0, len(nodeParamsMap))
for _, nodes := range nodeParamsMap {
res = append(res, nodes)
}
sort.Slice(res, func(i, j int) bool {
return res[i][0].Priority() < res[j][0].Priority()
})
return res, nil
}
func fillDefaultInitParams(params *InitParameters) {
if params.clientRebalanceInterval <= 0 {
params.clientRebalanceInterval = defaultRebalanceInterval
}
if params.healthcheckTimeout <= 0 {
params.healthcheckTimeout = defaultHealthcheckTimeout
}
if params.nodeDialTimeout <= 0 {
params.nodeDialTimeout = defaultDialTimeout
}
if params.nodeStreamTimeout <= 0 {
params.nodeStreamTimeout = defaultStreamTimeout
}
}
func (p *Pool) log(level zapcore.Level, msg string, fields ...zap.Field) {
if p.logger == nil {
return
}
p.logger.Log(level, msg, fields...)
}
// startRebalance runs loop to monitor tree client healthy status.
func (p *Pool) startRebalance(ctx context.Context) {
ticker := time.NewTimer(p.rebalanceParams.clientRebalanceInterval)
buffers := make([][]bool, len(p.rebalanceParams.nodesGroup))
for i, nodes := range p.rebalanceParams.nodesGroup {
buffers[i] = make([]bool, len(nodes))
}
for {
select {
case <-ctx.Done():
close(p.closedCh)
return
case <-ticker.C:
p.updateNodesHealth(ctx, buffers)
ticker.Reset(p.rebalanceParams.clientRebalanceInterval)
}
}
}
func (p *Pool) updateNodesHealth(ctx context.Context, buffers [][]bool) {
wg := sync.WaitGroup{}
for i, inner := range p.innerPools {
wg.Add(1)
go func(i int, innerPool *innerPool) {
defer wg.Done()
p.updateInnerNodesHealth(ctx, i, buffers[i])
}(i, inner)
}
wg.Wait()
LOOP:
for i, buffer := range buffers {
for j, healthy := range buffer {
if healthy {
p.setStartIndices(i, j)
break LOOP
}
}
}
}
func (p *Pool) updateInnerNodesHealth(ctx context.Context, i int, buffer []bool) {
if i > len(p.innerPools)-1 {
return
}
nodesByPriority := p.innerPools[i]
options := p.rebalanceParams
var wg sync.WaitGroup
for j, cli := range nodesByPriority.clients {
wg.Add(1)
go func(j int, cli client) {
defer wg.Done()
tctx, c := context.WithTimeout(ctx, options.nodeRequestTimeout)
defer c()
changed, err := cli.redialIfNecessary(tctx)
healthy := err == nil
if changed {
fields := []zap.Field{zap.String("address", cli.endpoint()), zap.Bool("healthy", healthy)}
if err != nil {
fields = append(fields, zap.Error(err))
}
p.log(zap.DebugLevel, "tree health has changed", fields...)
} else if err != nil {
p.log(zap.DebugLevel, "tree redial error", zap.String("address", cli.endpoint()), zap.Error(err))
}
buffer[j] = healthy
}(j, cli)
}
wg.Wait()
}
func (p *Pool) getStartIndices() (int, int) {
p.startIndicesMtx.RLock()
defer p.startIndicesMtx.RUnlock()
return p.startIndices[0], p.startIndices[1]
}
func (p *Pool) setStartIndices(i, j int) {
p.startIndicesMtx.Lock()
p.startIndices[0] = i
p.startIndices[1] = j
p.startIndicesMtx.Unlock()
}
func (p *Pool) requestWithRetry(fn func(client grpcService.TreeServiceClient) error) error {
var (
err error
cl grpcService.TreeServiceClient
)
startI, startJ := p.getStartIndices()
groupsLen := len(p.innerPools)
for i := startI; i < startI+groupsLen; i++ {
indexI := i % groupsLen
clientsLen := len(p.innerPools[indexI].clients)
for j := startJ; j < startJ+clientsLen; j++ {
indexJ := j % clientsLen
if cl, err = p.innerPools[indexI].clients[indexJ].serviceClient(); err == nil {
err = fn(cl)
}
if !shouldTryAgain(err) {
if startI != indexI || startJ != indexJ {
p.setStartIndices(indexI, indexJ)
}
return err
}
p.log(zap.DebugLevel, "tree request error", zap.String("address", p.innerPools[indexI].clients[indexJ].endpoint()), zap.Error(err))
}
startJ = 0
}
return err
}
func shouldTryAgain(err error) bool {
return !(err == nil ||
errors.Is(err, ErrNodeNotFound) ||
errors.Is(err, ErrNodeAccessDenied))
}

View file

@ -0,0 +1,25 @@
package tree
import (
crypto "git.frostfs.info/TrueCloudLab/frostfs-crypto"
"google.golang.org/protobuf/proto"
)
func (p *Pool) signData(buf []byte, f func(key, sign []byte)) error {
sign, err := crypto.Sign(&p.key.PrivateKey, buf)
if err != nil {
return err
}
f(p.key.PublicKey().Bytes(), sign)
return nil
}
func (p *Pool) signRequest(requestBody proto.Message, f func(key, sign []byte)) error {
buf, err := proto.Marshal(requestBody)
if err != nil {
return err
}
return p.signData(buf, f)
}

301
pool/tree/pool_test.go Normal file
View file

@ -0,0 +1,301 @@
package tree
import (
"context"
"errors"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
grpcService "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/service"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
type treeClientMock struct {
address string
err bool
}
func (t *treeClientMock) serviceClient() (grpcService.TreeServiceClient, error) {
if t.err {
return nil, errors.New("serviceClient() mock error")
}
return nil, nil
}
func (t *treeClientMock) endpoint() string {
return t.address
}
func (t *treeClientMock) isHealthy() bool {
return true
}
func (t *treeClientMock) setHealthy(bool) {
return
}
func (t *treeClientMock) dial(context.Context) error {
return nil
}
func (t *treeClientMock) redialIfNecessary(context.Context) (bool, error) {
if t.err {
return false, errors.New("redialIfNecessary() mock error")
}
return false, nil
}
func (t *treeClientMock) close() error {
return nil
}
func TestHandleError(t *testing.T) {
defaultError := errors.New("default error")
for _, tc := range []struct {
err error
expectedError error
}{
{
err: defaultError,
expectedError: defaultError,
},
{
err: errors.New("something not found"),
expectedError: ErrNodeNotFound,
},
{
err: errors.New("something is denied by some acl rule"),
expectedError: ErrNodeAccessDenied,
},
} {
t.Run("", func(t *testing.T) {
err := handleError("err message", tc.err)
require.True(t, errors.Is(err, tc.expectedError))
})
}
}
func TestRetry(t *testing.T) {
nodes := [][]string{
{"node00", "node01", "node02", "node03"},
{"node10", "node11", "node12", "node13"},
}
p := &Pool{
logger: zaptest.NewLogger(t),
innerPools: makeInnerPool(nodes),
}
makeFn := func(client grpcService.TreeServiceClient) error {
return nil
}
t.Run("first ok", func(t *testing.T) {
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 0, 0)
})
t.Run("first failed", func(t *testing.T) {
setErrors(p, "node00")
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 0, 1)
})
t.Run("all failed", func(t *testing.T) {
setErrors(p, nodes[0]...)
setErrors(p, nodes[1]...)
err := p.requestWithRetry(makeFn)
require.Error(t, err)
checkIndicesAndReset(t, p, 0, 0)
})
t.Run("round", func(t *testing.T) {
setErrors(p, nodes[0][0], nodes[0][1])
setErrors(p, nodes[1]...)
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndices(t, p, 0, 2)
resetClientsErrors(p)
setErrors(p, nodes[0][2], nodes[0][3])
err = p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 0, 0)
})
t.Run("group switch", func(t *testing.T) {
setErrors(p, nodes[0]...)
setErrors(p, nodes[1][0])
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 1, 1)
})
t.Run("group round", func(t *testing.T) {
setErrors(p, nodes[0][1:]...)
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 0, 0)
})
t.Run("group round switch", func(t *testing.T) {
setErrors(p, nodes[0]...)
p.setStartIndices(0, 1)
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 1, 0)
})
t.Run("no panic group switch", func(t *testing.T) {
setErrors(p, nodes[1]...)
p.setStartIndices(1, 0)
err := p.requestWithRetry(makeFn)
require.NoError(t, err)
checkIndicesAndReset(t, p, 0, 0)
})
}
func TestRebalance(t *testing.T) {
nodes := [][]string{
{"node00", "node01"},
{"node10", "node11"},
}
p := &Pool{
logger: zaptest.NewLogger(t),
innerPools: makeInnerPool(nodes),
rebalanceParams: rebalanceParameters{
nodesGroup: makeNodesGroup(nodes),
},
}
ctx := context.Background()
buffers := makeBuffer(p)
t.Run("check dirty buffers", func(t *testing.T) {
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 0, 0)
setErrors(p, nodes[0][0])
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 0, 1)
resetClients(p)
})
t.Run("don't change healthy status", func(t *testing.T) {
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 0, 0)
resetClients(p)
})
t.Run("switch to second group", func(t *testing.T) {
setErrors(p, nodes[0][0], nodes[0][1])
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 1, 0)
resetClients(p)
})
t.Run("switch back and forth", func(t *testing.T) {
setErrors(p, nodes[0][0], nodes[0][1])
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 1, 0)
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 1, 0)
setNoErrors(p, nodes[0][0])
p.updateNodesHealth(ctx, buffers)
checkIndices(t, p, 0, 0)
resetClients(p)
})
}
func makeInnerPool(nodes [][]string) []*innerPool {
res := make([]*innerPool, len(nodes))
for i, group := range nodes {
res[i] = &innerPool{clients: make([]client, len(group))}
for j, node := range group {
res[i].clients[j] = &treeClientMock{address: node}
}
}
return res
}
func makeNodesGroup(nodes [][]string) [][]pool.NodeParam {
res := make([][]pool.NodeParam, len(nodes))
for i, group := range nodes {
res[i] = make([]pool.NodeParam, len(group))
for j, node := range group {
res[i][j] = pool.NewNodeParam(1, node, 1)
}
}
return res
}
func makeBuffer(p *Pool) [][]bool {
buffers := make([][]bool, len(p.rebalanceParams.nodesGroup))
for i, nodes := range p.rebalanceParams.nodesGroup {
buffers[i] = make([]bool, len(nodes))
}
return buffers
}
func checkIndicesAndReset(t *testing.T, p *Pool, iExp, jExp int) {
checkIndices(t, p, iExp, jExp)
resetClients(p)
}
func checkIndices(t *testing.T, p *Pool, iExp, jExp int) {
i, j := p.getStartIndices()
require.Equal(t, [2]int{iExp, jExp}, [2]int{i, j})
}
func resetClients(p *Pool) {
resetClientsErrors(p)
p.setStartIndices(0, 0)
}
func resetClientsErrors(p *Pool) {
for _, group := range p.innerPools {
for _, cl := range group.clients {
node := cl.(*treeClientMock)
node.err = false
}
}
}
func setErrors(p *Pool, nodes ...string) {
setErrorsBase(p, true, nodes...)
}
func setNoErrors(p *Pool, nodes ...string) {
setErrorsBase(p, false, nodes...)
}
func setErrorsBase(p *Pool, err bool, nodes ...string) {
for _, group := range p.innerPools {
for _, cl := range group.clients {
node := cl.(*treeClientMock)
if containsStr(nodes, node.address) {
node.err = err
}
}
}
}
func containsStr(list []string, item string) bool {
for i := range list {
if list[i] == item {
return true
}
}
return false
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,520 @@
//*
// Service for working with CRDT tree.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v3.21.9
// source: pkg/services/tree/service.proto
package tree
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
TreeService_Add_FullMethodName = "/tree.TreeService/Add"
TreeService_AddByPath_FullMethodName = "/tree.TreeService/AddByPath"
TreeService_Remove_FullMethodName = "/tree.TreeService/Remove"
TreeService_Move_FullMethodName = "/tree.TreeService/Move"
TreeService_GetNodeByPath_FullMethodName = "/tree.TreeService/GetNodeByPath"
TreeService_GetSubTree_FullMethodName = "/tree.TreeService/GetSubTree"
TreeService_TreeList_FullMethodName = "/tree.TreeService/TreeList"
TreeService_Apply_FullMethodName = "/tree.TreeService/Apply"
TreeService_GetOpLog_FullMethodName = "/tree.TreeService/GetOpLog"
TreeService_Healthcheck_FullMethodName = "/tree.TreeService/Healthcheck"
)
// TreeServiceClient is the client API for TreeService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TreeServiceClient interface {
// Add adds new node to the tree. Invoked by a client.
Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*AddResponse, error)
// AddByPath adds new node to the tree by path. Invoked by a client.
AddByPath(ctx context.Context, in *AddByPathRequest, opts ...grpc.CallOption) (*AddByPathResponse, error)
// Remove removes node from the tree. Invoked by a client.
Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error)
// Move moves node from one parent to another. Invoked by a client.
Move(ctx context.Context, in *MoveRequest, opts ...grpc.CallOption) (*MoveResponse, error)
// GetNodeByPath returns list of IDs corresponding to a specific filepath.
GetNodeByPath(ctx context.Context, in *GetNodeByPathRequest, opts ...grpc.CallOption) (*GetNodeByPathResponse, error)
// GetSubTree returns tree corresponding to a specific node.
GetSubTree(ctx context.Context, in *GetSubTreeRequest, opts ...grpc.CallOption) (TreeService_GetSubTreeClient, error)
// TreeList return list of the existing trees in the container.
TreeList(ctx context.Context, in *TreeListRequest, opts ...grpc.CallOption) (*TreeListResponse, error)
// Apply pushes log operation from another node to the current.
// The request must be signed by a container node.
Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error)
// GetOpLog returns a stream of logged operations starting from some height.
GetOpLog(ctx context.Context, in *GetOpLogRequest, opts ...grpc.CallOption) (TreeService_GetOpLogClient, error)
// Healthcheck is a dummy rpc to check service availability
Healthcheck(ctx context.Context, in *HealthcheckRequest, opts ...grpc.CallOption) (*HealthcheckResponse, error)
}
type treeServiceClient struct {
cc grpc.ClientConnInterface
}
func NewTreeServiceClient(cc grpc.ClientConnInterface) TreeServiceClient {
return &treeServiceClient{cc}
}
func (c *treeServiceClient) Add(ctx context.Context, in *AddRequest, opts ...grpc.CallOption) (*AddResponse, error) {
out := new(AddResponse)
err := c.cc.Invoke(ctx, TreeService_Add_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) AddByPath(ctx context.Context, in *AddByPathRequest, opts ...grpc.CallOption) (*AddByPathResponse, error) {
out := new(AddByPathResponse)
err := c.cc.Invoke(ctx, TreeService_AddByPath_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error) {
out := new(RemoveResponse)
err := c.cc.Invoke(ctx, TreeService_Remove_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) Move(ctx context.Context, in *MoveRequest, opts ...grpc.CallOption) (*MoveResponse, error) {
out := new(MoveResponse)
err := c.cc.Invoke(ctx, TreeService_Move_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) GetNodeByPath(ctx context.Context, in *GetNodeByPathRequest, opts ...grpc.CallOption) (*GetNodeByPathResponse, error) {
out := new(GetNodeByPathResponse)
err := c.cc.Invoke(ctx, TreeService_GetNodeByPath_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) GetSubTree(ctx context.Context, in *GetSubTreeRequest, opts ...grpc.CallOption) (TreeService_GetSubTreeClient, error) {
stream, err := c.cc.NewStream(ctx, &TreeService_ServiceDesc.Streams[0], TreeService_GetSubTree_FullMethodName, opts...)
if err != nil {
return nil, err
}
x := &treeServiceGetSubTreeClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type TreeService_GetSubTreeClient interface {
Recv() (*GetSubTreeResponse, error)
grpc.ClientStream
}
type treeServiceGetSubTreeClient struct {
grpc.ClientStream
}
func (x *treeServiceGetSubTreeClient) Recv() (*GetSubTreeResponse, error) {
m := new(GetSubTreeResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *treeServiceClient) TreeList(ctx context.Context, in *TreeListRequest, opts ...grpc.CallOption) (*TreeListResponse, error) {
out := new(TreeListResponse)
err := c.cc.Invoke(ctx, TreeService_TreeList_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error) {
out := new(ApplyResponse)
err := c.cc.Invoke(ctx, TreeService_Apply_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *treeServiceClient) GetOpLog(ctx context.Context, in *GetOpLogRequest, opts ...grpc.CallOption) (TreeService_GetOpLogClient, error) {
stream, err := c.cc.NewStream(ctx, &TreeService_ServiceDesc.Streams[1], TreeService_GetOpLog_FullMethodName, opts...)
if err != nil {
return nil, err
}
x := &treeServiceGetOpLogClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type TreeService_GetOpLogClient interface {
Recv() (*GetOpLogResponse, error)
grpc.ClientStream
}
type treeServiceGetOpLogClient struct {
grpc.ClientStream
}
func (x *treeServiceGetOpLogClient) Recv() (*GetOpLogResponse, error) {
m := new(GetOpLogResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *treeServiceClient) Healthcheck(ctx context.Context, in *HealthcheckRequest, opts ...grpc.CallOption) (*HealthcheckResponse, error) {
out := new(HealthcheckResponse)
err := c.cc.Invoke(ctx, TreeService_Healthcheck_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// TreeServiceServer is the server API for TreeService service.
// All implementations should embed UnimplementedTreeServiceServer
// for forward compatibility
type TreeServiceServer interface {
// Add adds new node to the tree. Invoked by a client.
Add(context.Context, *AddRequest) (*AddResponse, error)
// AddByPath adds new node to the tree by path. Invoked by a client.
AddByPath(context.Context, *AddByPathRequest) (*AddByPathResponse, error)
// Remove removes node from the tree. Invoked by a client.
Remove(context.Context, *RemoveRequest) (*RemoveResponse, error)
// Move moves node from one parent to another. Invoked by a client.
Move(context.Context, *MoveRequest) (*MoveResponse, error)
// GetNodeByPath returns list of IDs corresponding to a specific filepath.
GetNodeByPath(context.Context, *GetNodeByPathRequest) (*GetNodeByPathResponse, error)
// GetSubTree returns tree corresponding to a specific node.
GetSubTree(*GetSubTreeRequest, TreeService_GetSubTreeServer) error
// TreeList return list of the existing trees in the container.
TreeList(context.Context, *TreeListRequest) (*TreeListResponse, error)
// Apply pushes log operation from another node to the current.
// The request must be signed by a container node.
Apply(context.Context, *ApplyRequest) (*ApplyResponse, error)
// GetOpLog returns a stream of logged operations starting from some height.
GetOpLog(*GetOpLogRequest, TreeService_GetOpLogServer) error
// Healthcheck is a dummy rpc to check service availability
Healthcheck(context.Context, *HealthcheckRequest) (*HealthcheckResponse, error)
}
// UnimplementedTreeServiceServer should be embedded to have forward compatible implementations.
type UnimplementedTreeServiceServer struct {
}
func (UnimplementedTreeServiceServer) Add(context.Context, *AddRequest) (*AddResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Add not implemented")
}
func (UnimplementedTreeServiceServer) AddByPath(context.Context, *AddByPathRequest) (*AddByPathResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddByPath not implemented")
}
func (UnimplementedTreeServiceServer) Remove(context.Context, *RemoveRequest) (*RemoveResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Remove not implemented")
}
func (UnimplementedTreeServiceServer) Move(context.Context, *MoveRequest) (*MoveResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Move not implemented")
}
func (UnimplementedTreeServiceServer) GetNodeByPath(context.Context, *GetNodeByPathRequest) (*GetNodeByPathResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetNodeByPath not implemented")
}
func (UnimplementedTreeServiceServer) GetSubTree(*GetSubTreeRequest, TreeService_GetSubTreeServer) error {
return status.Errorf(codes.Unimplemented, "method GetSubTree not implemented")
}
func (UnimplementedTreeServiceServer) TreeList(context.Context, *TreeListRequest) (*TreeListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TreeList not implemented")
}
func (UnimplementedTreeServiceServer) Apply(context.Context, *ApplyRequest) (*ApplyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Apply not implemented")
}
func (UnimplementedTreeServiceServer) GetOpLog(*GetOpLogRequest, TreeService_GetOpLogServer) error {
return status.Errorf(codes.Unimplemented, "method GetOpLog not implemented")
}
func (UnimplementedTreeServiceServer) Healthcheck(context.Context, *HealthcheckRequest) (*HealthcheckResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Healthcheck not implemented")
}
// UnsafeTreeServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TreeServiceServer will
// result in compilation errors.
type UnsafeTreeServiceServer interface {
mustEmbedUnimplementedTreeServiceServer()
}
func RegisterTreeServiceServer(s grpc.ServiceRegistrar, srv TreeServiceServer) {
s.RegisterService(&TreeService_ServiceDesc, srv)
}
func _TreeService_Add_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Add(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_Add_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Add(ctx, req.(*AddRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_AddByPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddByPathRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).AddByPath(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_AddByPath_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).AddByPath(ctx, req.(*AddByPathRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_Remove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Remove(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_Remove_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Remove(ctx, req.(*RemoveRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_Move_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MoveRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Move(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_Move_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Move(ctx, req.(*MoveRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_GetNodeByPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetNodeByPathRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).GetNodeByPath(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_GetNodeByPath_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).GetNodeByPath(ctx, req.(*GetNodeByPathRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_GetSubTree_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetSubTreeRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(TreeServiceServer).GetSubTree(m, &treeServiceGetSubTreeServer{stream})
}
type TreeService_GetSubTreeServer interface {
Send(*GetSubTreeResponse) error
grpc.ServerStream
}
type treeServiceGetSubTreeServer struct {
grpc.ServerStream
}
func (x *treeServiceGetSubTreeServer) Send(m *GetSubTreeResponse) error {
return x.ServerStream.SendMsg(m)
}
func _TreeService_TreeList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TreeListRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).TreeList(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_TreeList_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).TreeList(ctx, req.(*TreeListRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_Apply_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ApplyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Apply(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_Apply_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Apply(ctx, req.(*ApplyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TreeService_GetOpLog_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetOpLogRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(TreeServiceServer).GetOpLog(m, &treeServiceGetOpLogServer{stream})
}
type TreeService_GetOpLogServer interface {
Send(*GetOpLogResponse) error
grpc.ServerStream
}
type treeServiceGetOpLogServer struct {
grpc.ServerStream
}
func (x *treeServiceGetOpLogServer) Send(m *GetOpLogResponse) error {
return x.ServerStream.SendMsg(m)
}
func _TreeService_Healthcheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HealthcheckRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TreeServiceServer).Healthcheck(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TreeService_Healthcheck_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TreeServiceServer).Healthcheck(ctx, req.(*HealthcheckRequest))
}
return interceptor(ctx, in, info, handler)
}
// TreeService_ServiceDesc is the grpc.ServiceDesc for TreeService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TreeService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "tree.TreeService",
HandlerType: (*TreeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Add",
Handler: _TreeService_Add_Handler,
},
{
MethodName: "AddByPath",
Handler: _TreeService_AddByPath_Handler,
},
{
MethodName: "Remove",
Handler: _TreeService_Remove_Handler,
},
{
MethodName: "Move",
Handler: _TreeService_Move_Handler,
},
{
MethodName: "GetNodeByPath",
Handler: _TreeService_GetNodeByPath_Handler,
},
{
MethodName: "TreeList",
Handler: _TreeService_TreeList_Handler,
},
{
MethodName: "Apply",
Handler: _TreeService_Apply_Handler,
},
{
MethodName: "Healthcheck",
Handler: _TreeService_Healthcheck_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "GetSubTree",
Handler: _TreeService_GetSubTree_Handler,
ServerStreams: true,
},
{
StreamName: "GetOpLog",
Handler: _TreeService_GetOpLog_Handler,
ServerStreams: true,
},
},
Metadata: "pkg/services/tree/service.proto",
}

View file

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

View file

@ -1,38 +0,0 @@
/*
Package storagegroup provides features to work with information that is
used for proof of storage in FrostFS system.
StorageGroup type groups verification values for Data Audit sessions:
// receive sg info
sg.ExpirationEpoch() // expiration of the storage group
sg.Members() // objects in the group
sg.ValidationDataHash() // hash for objects validation
sg.ValidationDataSize() // total objects' payload size
Instances can be also used to process FrostFS API V2 protocol messages
(see neo.fs.v2.storagegroup package in https://git.frostfs.info/TrueCloudLab/frostfs-api).
On client side:
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/storagegroup"
var msg storagegroup.StorageGroup
sg.WriteToV2(&msg)
// send msg
On server side:
// recv msg
var sg StorageGroupDecimal
sg.ReadFromV2(msg)
// process sg
Using package types in an application is recommended to potentially work with
different protocol versions with which these types are compatible.
*/
package storagegroup

View file

@ -1,329 +0,0 @@
package storagegroup
import (
"errors"
"fmt"
"strconv"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/storagegroup"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
)
// StorageGroup represents storage group of the FrostFS objects.
//
// StorageGroup is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/storagegroup.StorageGroup
// message. See ReadFromMessageV2 / WriteToMessageV2 methods.
//
// Instances can be created using built-in var declaration.
//
// Note that direct typecast is not safe and may result in loss of compatibility:
//
// _ = StorageGroup(storagegroup.StorageGroup) // not recommended
type StorageGroup storagegroup.StorageGroup
// reads StorageGroup from the storagegroup.StorageGroup message. If checkFieldPresence is set,
// returns an error on absence of any protocol-required field.
func (sg *StorageGroup) readFromV2(m storagegroup.StorageGroup, checkFieldPresence bool) error {
var err error
h := m.GetValidationHash()
if h != nil {
err = new(checksum.Checksum).ReadFromV2(*h)
if err != nil {
return fmt.Errorf("invalid hash: %w", err)
}
} else if checkFieldPresence {
return errors.New("missing hash")
}
members := m.GetMembers()
if len(members) > 0 {
var member oid.ID
mMembers := make(map[oid.ID]struct{}, len(members))
var exits bool
for i := range members {
err = member.ReadFromV2(members[i])
if err != nil {
return fmt.Errorf("invalid member: %w", err)
}
_, exits = mMembers[member]
if exits {
return fmt.Errorf("duplicated member %s", member)
}
mMembers[member] = struct{}{}
}
} else if checkFieldPresence {
return errors.New("missing members")
}
*sg = StorageGroup(m)
return nil
}
// ReadFromV2 reads StorageGroup from the storagegroup.StorageGroup message.
// Checks if the message conforms to FrostFS API V2 protocol.
//
// See also WriteToV2.
func (sg *StorageGroup) ReadFromV2(m storagegroup.StorageGroup) error {
return sg.readFromV2(m, true)
}
// WriteToV2 writes StorageGroup to the storagegroup.StorageGroup message.
// The message must not be nil.
//
// See also ReadFromV2.
func (sg StorageGroup) WriteToV2(m *storagegroup.StorageGroup) {
*m = (storagegroup.StorageGroup)(sg)
}
// ValidationDataSize returns total size of the payloads
// of objects in the storage group.
//
// Zero StorageGroup has 0 data size.
//
// See also SetValidationDataSize.
func (sg StorageGroup) ValidationDataSize() uint64 {
v2 := (storagegroup.StorageGroup)(sg)
return v2.GetValidationDataSize()
}
// SetValidationDataSize sets total size of the payloads
// of objects in the storage group.
//
// See also ValidationDataSize.
func (sg *StorageGroup) SetValidationDataSize(epoch uint64) {
(*storagegroup.StorageGroup)(sg).SetValidationDataSize(epoch)
}
// ValidationDataHash returns homomorphic hash from the
// concatenation of the payloads of the storage group members
// and bool that indicates checksum presence in the storage
// group.
//
// Zero StorageGroup does not have validation data checksum.
//
// See also SetValidationDataHash.
func (sg StorageGroup) ValidationDataHash() (v checksum.Checksum, isSet bool) {
v2 := (storagegroup.StorageGroup)(sg)
if checksumV2 := v2.GetValidationHash(); checksumV2 != nil {
_ = v.ReadFromV2(*checksumV2) // FIXME(@cthulhu-rider): #226 handle error
isSet = true
}
return
}
// SetValidationDataHash sets homomorphic hash from the
// concatenation of the payloads of the storage group members.
//
// See also ValidationDataHash.
func (sg *StorageGroup) SetValidationDataHash(hash checksum.Checksum) {
var v2 refs.Checksum
hash.WriteToV2(&v2)
(*storagegroup.StorageGroup)(sg).SetValidationHash(&v2)
}
// ExpirationEpoch returns last FrostFS epoch number
// of the storage group lifetime.
//
// Zero StorageGroup has 0 expiration epoch.
//
// See also SetExpirationEpoch.
func (sg StorageGroup) ExpirationEpoch() uint64 {
v2 := (storagegroup.StorageGroup)(sg)
return v2.GetExpirationEpoch()
}
// SetExpirationEpoch sets last FrostFS epoch number
// of the storage group lifetime.
//
// See also ExpirationEpoch.
func (sg *StorageGroup) SetExpirationEpoch(epoch uint64) {
(*storagegroup.StorageGroup)(sg).SetExpirationEpoch(epoch)
}
// Members returns strictly ordered list of
// storage group member objects.
//
// Zero StorageGroup has nil members value.
//
// See also SetMembers.
func (sg StorageGroup) Members() []oid.ID {
v2 := (storagegroup.StorageGroup)(sg)
mV2 := v2.GetMembers()
if mV2 == nil {
return nil
}
m := make([]oid.ID, len(mV2))
for i := range mV2 {
_ = m[i].ReadFromV2(mV2[i])
}
return m
}
// SetMembers sets strictly ordered list of
// storage group member objects.
//
// See also Members.
func (sg *StorageGroup) SetMembers(members []oid.ID) {
mV2 := (*storagegroup.StorageGroup)(sg).GetMembers()
if members == nil {
mV2 = nil
} else {
ln := len(members)
if cap(mV2) >= ln {
mV2 = mV2[:0]
} else {
mV2 = make([]refs.ObjectID, 0, ln)
}
var oidV2 refs.ObjectID
for i := 0; i < ln; i++ {
members[i].WriteToV2(&oidV2)
mV2 = append(mV2, oidV2)
}
}
(*storagegroup.StorageGroup)(sg).SetMembers(mV2)
}
// Marshal marshals StorageGroup into a protobuf binary form.
//
// See also Unmarshal.
func (sg StorageGroup) Marshal() ([]byte, error) {
return (*storagegroup.StorageGroup)(&sg).StableMarshal(nil), nil
}
// Unmarshal unmarshals protobuf binary representation of StorageGroup.
//
// See also Marshal.
func (sg *StorageGroup) Unmarshal(data []byte) error {
v2 := (*storagegroup.StorageGroup)(sg)
err := v2.Unmarshal(data)
if err != nil {
return err
}
return sg.readFromV2(*v2, false)
}
// MarshalJSON encodes StorageGroup to protobuf JSON format.
//
// See also UnmarshalJSON.
func (sg StorageGroup) MarshalJSON() ([]byte, error) {
v2 := (storagegroup.StorageGroup)(sg)
return v2.MarshalJSON()
}
// UnmarshalJSON decodes StorageGroup from protobuf JSON format.
//
// See also MarshalJSON.
func (sg *StorageGroup) UnmarshalJSON(data []byte) error {
v2 := (*storagegroup.StorageGroup)(sg)
err := v2.UnmarshalJSON(data)
if err != nil {
return err
}
return sg.readFromV2(*v2, false)
}
// ReadFromObject assemble StorageGroup from a regular
// Object structure. Object must contain unambiguous information
// about its expiration epoch, otherwise behaviour is undefined.
//
// Returns any error appeared during storage group parsing; returns
// error if object is not of TypeStorageGroup type.
func ReadFromObject(sg *StorageGroup, o objectSDK.Object) error {
if typ := o.Type(); typ != objectSDK.TypeStorageGroup {
return fmt.Errorf("object is not of StorageGroup type: %s", typ)
}
err := sg.Unmarshal(o.Payload())
if err != nil {
return fmt.Errorf("could not unmarshal object: %w", err)
}
var expObj uint64
for _, attr := range o.Attributes() {
if attr.Key() == objectV2.SysAttributeExpEpoch {
expObj, err = strconv.ParseUint(attr.Value(), 10, 64)
if err != nil {
return fmt.Errorf("could not get expiration from object: %w", err)
}
break
}
}
// Supporting deprecated functionality.
// See https://github.com/nspcc-dev/neofs-api/pull/205.
if expSG := sg.ExpirationEpoch(); expObj != expSG {
return fmt.Errorf(
"expiration does not match: from object: %d, from payload: %d",
expObj, expSG)
}
return nil
}
// WriteToObject writes StorageGroup to a regular
// Object structure. Object must not contain ambiguous
// information about its expiration epoch or must not
// have it at all.
//
// Written information:
// - expiration epoch;
// - object type (TypeStorageGroup);
// - raw payload.
func WriteToObject(sg StorageGroup, o *objectSDK.Object) {
sgRaw, err := sg.Marshal()
if err != nil {
// Marshal() does not return errors
// in the next API release
panic(fmt.Errorf("could not marshal storage group: %w", err))
}
o.SetPayload(sgRaw)
o.SetType(objectSDK.TypeStorageGroup)
attrs := o.Attributes()
var expAttrFound bool
for i := range attrs {
if attrs[i].Key() == objectV2.SysAttributeExpEpoch {
expAttrFound = true
attrs[i].SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10))
break
}
}
if !expAttrFound {
var attr objectSDK.Attribute
attr.SetKey(objectV2.SysAttributeExpEpoch)
attr.SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10))
attrs = append(attrs, attr)
}
o.SetAttributes(attrs...)
}

View file

@ -1,283 +0,0 @@
package storagegroup_test
import (
"crypto/sha256"
"strconv"
"testing"
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
storagegroupV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/storagegroup"
storagegroupV2test "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/storagegroup/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
checksumtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum/test"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/storagegroup"
storagegrouptest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/storagegroup/test"
"github.com/stretchr/testify/require"
)
func TestStorageGroup(t *testing.T) {
var sg storagegroup.StorageGroup
sz := uint64(13)
sg.SetValidationDataSize(sz)
require.Equal(t, sz, sg.ValidationDataSize())
cs := checksumtest.Checksum()
sg.SetValidationDataHash(cs)
cs2, set := sg.ValidationDataHash()
require.True(t, set)
require.Equal(t, cs, cs2)
exp := uint64(33)
sg.SetExpirationEpoch(exp)
require.Equal(t, exp, sg.ExpirationEpoch())
members := []oid.ID{oidtest.ID(), oidtest.ID()}
sg.SetMembers(members)
require.Equal(t, members, sg.Members())
}
func TestStorageGroup_ReadFromV2(t *testing.T) {
t.Run("from zero", func(t *testing.T) {
var (
x storagegroup.StorageGroup
v2 storagegroupV2.StorageGroup
)
require.Error(t, x.ReadFromV2(v2))
})
t.Run("from non-zero", func(t *testing.T) {
var (
x storagegroup.StorageGroup
v2 = storagegroupV2test.GenerateStorageGroup(false)
)
// https://github.com/nspcc-dev/neofs-api-go/issues/394
v2.SetMembers(generateOIDList())
size := v2.GetValidationDataSize()
epoch := v2.GetExpirationEpoch()
mm := v2.GetMembers()
hashV2 := v2.GetValidationHash()
require.NoError(t, x.ReadFromV2(*v2))
require.Equal(t, epoch, x.ExpirationEpoch())
require.Equal(t, size, x.ValidationDataSize())
var hash checksum.Checksum
require.NoError(t, hash.ReadFromV2(*hashV2))
h, set := x.ValidationDataHash()
require.True(t, set)
require.Equal(t, hash, h)
var oidV2 refs.ObjectID
for i, m := range mm {
x.Members()[i].WriteToV2(&oidV2)
require.Equal(t, m, oidV2)
}
})
}
func TestStorageGroupEncoding(t *testing.T) {
sg := storagegrouptest.StorageGroup()
t.Run("binary", func(t *testing.T) {
data, err := sg.Marshal()
require.NoError(t, err)
var sg2 storagegroup.StorageGroup
require.NoError(t, sg2.Unmarshal(data))
require.Equal(t, sg, sg2)
})
t.Run("json", func(t *testing.T) {
data, err := sg.MarshalJSON()
require.NoError(t, err)
var sg2 storagegroup.StorageGroup
require.NoError(t, sg2.UnmarshalJSON(data))
require.Equal(t, sg, sg2)
})
}
func TestStorageGroup_WriteToV2(t *testing.T) {
t.Run("zero to v2", func(t *testing.T) {
var (
x storagegroup.StorageGroup
v2 storagegroupV2.StorageGroup
)
x.WriteToV2(&v2)
require.Nil(t, v2.GetValidationHash())
require.Nil(t, v2.GetMembers())
require.Zero(t, v2.GetValidationDataSize())
require.Zero(t, v2.GetExpirationEpoch())
})
t.Run("non-zero to v2", func(t *testing.T) {
var (
x = storagegrouptest.StorageGroup()
v2 storagegroupV2.StorageGroup
)
x.WriteToV2(&v2)
require.Equal(t, x.ExpirationEpoch(), v2.GetExpirationEpoch())
require.Equal(t, x.ValidationDataSize(), v2.GetValidationDataSize())
var hash checksum.Checksum
require.NoError(t, hash.ReadFromV2(*v2.GetValidationHash()))
h, set := x.ValidationDataHash()
require.True(t, set)
require.Equal(t, h, hash)
var oidV2 refs.ObjectID
for i, m := range x.Members() {
m.WriteToV2(&oidV2)
require.Equal(t, oidV2, v2.GetMembers()[i])
}
})
}
func TestNew(t *testing.T) {
t.Run("default values", func(t *testing.T) {
var sg storagegroup.StorageGroup
// check initial values
require.Nil(t, sg.Members())
_, set := sg.ValidationDataHash()
require.False(t, set)
require.Zero(t, sg.ExpirationEpoch())
require.Zero(t, sg.ValidationDataSize())
})
}
func generateOIDList() []refs.ObjectID {
const size = 3
mmV2 := make([]refs.ObjectID, size)
for i := 0; i < size; i++ {
oidV2 := make([]byte, sha256.Size)
oidV2[i] = byte(i)
mmV2[i].SetValue(oidV2)
}
return mmV2
}
func TestStorageGroup_SetMembers_DoubleSetting(t *testing.T) {
var sg storagegroup.StorageGroup
mm := []oid.ID{oidtest.ID(), oidtest.ID(), oidtest.ID()} // cap is 3 at least
sg.SetMembers(mm)
// the previous cap is more that a new length;
// slicing should not lead to `out of range`
// and apply update correctly
sg.SetMembers(mm[:1])
}
func TestStorageGroupFromObject(t *testing.T) {
sg := storagegrouptest.StorageGroup()
var o objectSDK.Object
var expAttr objectSDK.Attribute
expAttr.SetKey(objectV2.SysAttributeExpEpoch)
expAttr.SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10))
sgRaw, err := sg.Marshal()
require.NoError(t, err)
o.SetPayload(sgRaw)
o.SetType(objectSDK.TypeStorageGroup)
t.Run("correct object", func(t *testing.T) {
o.SetAttributes(objectSDK.Attribute{}, expAttr, objectSDK.Attribute{})
var sg2 storagegroup.StorageGroup
require.NoError(t, storagegroup.ReadFromObject(&sg2, o))
require.Equal(t, sg, sg2)
})
t.Run("incorrect exp attr", func(t *testing.T) {
var sg2 storagegroup.StorageGroup
expAttr.SetValue(strconv.FormatUint(sg.ExpirationEpoch()+1, 10))
o.SetAttributes(expAttr)
require.Error(t, storagegroup.ReadFromObject(&sg2, o))
})
t.Run("incorrect object type", func(t *testing.T) {
var sg2 storagegroup.StorageGroup
o.SetType(objectSDK.TypeTombstone)
require.Error(t, storagegroup.ReadFromObject(&sg2, o))
})
}
func TestStorageGroupToObject(t *testing.T) {
sg := storagegrouptest.StorageGroup()
sgRaw, err := sg.Marshal()
require.NoError(t, err)
t.Run("empty object", func(t *testing.T) {
var o objectSDK.Object
storagegroup.WriteToObject(sg, &o)
exp, found := expFromObj(t, o)
require.True(t, found)
require.Equal(t, sgRaw, o.Payload())
require.Equal(t, sg.ExpirationEpoch(), exp)
require.Equal(t, objectSDK.TypeStorageGroup, o.Type())
})
t.Run("obj already has exp attr", func(t *testing.T) {
var o objectSDK.Object
var attr objectSDK.Attribute
attr.SetKey(objectV2.SysAttributeExpEpoch)
attr.SetValue(strconv.FormatUint(sg.ExpirationEpoch()+1, 10))
o.SetAttributes(objectSDK.Attribute{}, attr, objectSDK.Attribute{})
storagegroup.WriteToObject(sg, &o)
exp, found := expFromObj(t, o)
require.True(t, found)
require.Equal(t, sgRaw, o.Payload())
require.Equal(t, sg.ExpirationEpoch(), exp)
require.Equal(t, objectSDK.TypeStorageGroup, o.Type())
})
}
func expFromObj(t *testing.T, o objectSDK.Object) (uint64, bool) {
for _, attr := range o.Attributes() {
if attr.Key() == objectV2.SysAttributeExpEpoch {
exp, err := strconv.ParseUint(attr.Value(), 10, 64)
require.NoError(t, err)
return exp, true
}
}
return 0, false
}

View file

@ -1,13 +0,0 @@
/*
Package storagegrouptest provides functions for convenient testing of storagegroup package API.
Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import storagegrouptest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/storagegroup/test"
val := storagegrouptest.StorageGroup()
// test the value
*/
package storagegrouptest

View file

@ -1,20 +0,0 @@
package storagegrouptest
import (
checksumtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum/test"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/storagegroup"
)
// StorageGroup returns random storagegroup.StorageGroup.
func StorageGroup() storagegroup.StorageGroup {
var x storagegroup.StorageGroup
x.SetExpirationEpoch(66)
x.SetValidationDataSize(322)
x.SetValidationDataHash(checksumtest.Checksum())
x.SetMembers([]oid.ID{oidtest.ID(), oidtest.ID()})
return x
}

19
syncTree.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
REVISION="b3695411d907c3c65485bab04f9ff8479a72906b"
echo "tree service revision ${REVISION}"
# regexp below find all link to source code files which end with ".pb.go" and retrieve the file names
# we use `[^.]*` as non greedy workaround for `.*`
FILES=$(curl -s https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/${REVISION}/pkg/services/tree | sed -n "s,.*\"/TrueCloudLab/frostfs-node/src/commit/${REVISION}/pkg/services/tree/\([^.]*\.pb\.go\)\".*,\1,p")
for file in $FILES; do
if [[ $file == *"frostfs"* ]]; then
echo "skip '$file'"
continue
else
echo "sync '$file' in tree service"
fi
curl -s "https://git.frostfs.info/TrueCloudLab/frostfs-node/raw/commit/${REVISION}/pkg/services/tree/${file}" -o "./pool/tree/service/${file}"
done