forked from TrueCloudLab/frostfs-sdk-go
Compare commits
68 commits
db5b89496d
...
6353df8bca
Author | SHA1 | Date | |
---|---|---|---|
6353df8bca | |||
936e6d230b | |||
be28b89312 | |||
9e5faaf829 | |||
3dc8129ed7 | |||
55c52c8d5d | |||
d376302a3b | |||
363f153eaf | |||
95b987b818 | |||
13d0b170d2 | |||
18a9e4bceb | |||
0fe0d71678 | |||
78d1439b2c | |||
0886d80083 | |||
ecb1fef78c | |||
5defed4ab4 | |||
fb05f7dc5e | |||
b91f9d8c79 | |||
b9afe7a2f9 | |||
998fe1a7ab | |||
c359a7465a | |||
d70ef2187b | |||
ac95b87e7c | |||
863be6034f | |||
35346a01c9 | |||
fe35373d8f | |||
388d1ca1de | |||
14ed3e177d | |||
fe28c33277 | |||
98cab7ed61 | |||
37e22b33ad | |||
769f6eec05 | |||
5d62cef27e | |||
c0c0c588b5 | |||
2f88460172 | |||
66cb5dcf34 | |||
91e80ba743 | |||
c243b443bc | |||
aa8ffebc63 | |||
9d40228cec | |||
af40dc68f0 | |||
981d24a493 | |||
19adb4dffa | |||
51e022ab8c | |||
0d3dacb515 | |||
b2e302624d | |||
fcbf96add6 | |||
4f48f6c9e0 | |||
ec59ebfd88 | |||
030ff2f122 | |||
0f7455ff7a | |||
e6b662cfa6 | |||
406c2324d4 | |||
10482ffbed | |||
f5b23eb225 | |||
70f23dd1ea | |||
57f874048b | |||
a397d1fd15 | |||
9803c2816a | |||
d04d96b42e | |||
9a072a8f49 | |||
15b4287092 | |||
d4fe9a193d | |||
c42a6119ff | |||
29b188db57 | |||
38b03ff28b | |||
0fa23a9b14 | |||
d0762d037d |
93 changed files with 9029 additions and 3205 deletions
21
.forgejo/workflows/dco.yml
Normal file
21
.forgejo/workflows/dco.yml
Normal 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
|
32
.forgejo/workflows/tests.yml
Normal file
32
.forgejo/workflows/tests.yml
Normal 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
|
21
.github/workflows/dco.yml
vendored
21
.github/workflows/dco.yml
vendored
|
@ -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 }}
|
52
.github/workflows/tests.yml
vendored
52
.github/workflows/tests.yml
vendored
|
@ -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
6
.gitignore
vendored
|
@ -21,3 +21,9 @@ vendor/
|
|||
# coverage
|
||||
coverage.txt
|
||||
coverage.html
|
||||
|
||||
# antlr tool jar
|
||||
antlr-*.jar
|
||||
|
||||
# tempfiles
|
||||
.cache
|
||||
|
|
|
@ -16,7 +16,7 @@ repos:
|
|||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- id: end-of-file-fixer
|
||||
exclude: ".key$"
|
||||
exclude: "(.key|.interp|.tokens)$"
|
||||
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.51.2
|
||||
|
|
4
Dockerfile
Normal file
4
Dockerfile
Normal 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
|
19
Makefile
19
Makefile
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
ANTLR_VERSION="4.13.0"
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
@go test ./... -cover
|
||||
|
@ -29,6 +31,23 @@ format:
|
|||
@echo "⇒ Processing goimports check"
|
||||
@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
|
||||
help:
|
||||
@echo ' Usage:'
|
||||
|
|
26
audit/doc.go
26
audit/doc.go
|
@ -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
|
377
audit/result.go
377
audit/result.go
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -46,10 +46,12 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
|
|||
return errors.New("missing token body")
|
||||
}
|
||||
|
||||
b.impersonate = body.GetImpersonate()
|
||||
|
||||
eaclTable := body.GetEACL()
|
||||
if b.eaclTableSet = eaclTable != nil; b.eaclTableSet {
|
||||
b.eaclTable = *eacl.NewTableFromV2(eaclTable)
|
||||
} else if checkFieldPresence {
|
||||
} else if checkFieldPresence && !b.impersonate {
|
||||
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")
|
||||
}
|
||||
|
||||
b.impersonate = body.GetImpersonate()
|
||||
|
||||
sig := m.GetSignature()
|
||||
if b.sigSet = sig != nil; sig != nil {
|
||||
b.sig = *sig
|
||||
|
|
|
@ -323,6 +323,10 @@ func TestToken_ReadFromV2(t *testing.T) {
|
|||
|
||||
require.NoError(t, val.ReadFromV2(m))
|
||||
|
||||
body.SetEACL(nil)
|
||||
body.SetImpersonate(true)
|
||||
require.NoError(t, val.ReadFromV2(m))
|
||||
|
||||
var m2 acl.BearerToken
|
||||
|
||||
val.WriteToV2(&m2)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
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/client"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// 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.WithDialTimeout(prm.timeoutDial),
|
||||
client.WithRWTimeout(prm.streamTimeout),
|
||||
client.WithGRPCDialOptions(prm.dialOptions),
|
||||
)...)
|
||||
|
||||
c.setFrostFSAPIServer((*coreServer)(&c.c))
|
||||
|
@ -186,6 +188,8 @@ type PrmDial struct {
|
|||
|
||||
streamTimeoutSet bool
|
||||
streamTimeout time.Duration
|
||||
|
||||
dialOptions []grpc.DialOption
|
||||
}
|
||||
|
||||
// SetServerURI sets server URI in the FrostFS network.
|
||||
|
@ -226,3 +230,8 @@ func (x *PrmDial) SetStreamTimeout(timeout time.Duration) {
|
|||
x.streamTimeoutSet = true
|
||||
x.streamTimeout = timeout
|
||||
}
|
||||
|
||||
// SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
|
||||
func (x *PrmDial) SetGRPCDialOptions(opts ...grpc.DialOption) {
|
||||
x.dialOptions = opts
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ var (
|
|||
errorMissingAnnouncements = errors.New("missing announcements")
|
||||
errorZeroRangeLength = errors.New("zero range length")
|
||||
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.
|
||||
|
|
|
@ -18,29 +18,33 @@ import (
|
|||
|
||||
// PrmContainerDelete groups parameters of ContainerDelete operation.
|
||||
type PrmContainerDelete struct {
|
||||
prmCommonMeta
|
||||
// FrostFS request X-Headers
|
||||
XHeaders []string
|
||||
|
||||
idSet bool
|
||||
id cid.ID
|
||||
ContainerID *cid.ID
|
||||
|
||||
tokSet bool
|
||||
tok session.Container
|
||||
Session *session.Container
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the FrostFS container to be removed.
|
||||
// Required parameter.
|
||||
func (x *PrmContainerDelete) SetContainer(id cid.ID) {
|
||||
x.id = id
|
||||
x.idSet = true
|
||||
//
|
||||
// Deprecated: Use PrmContainerDelete.Container instead.
|
||||
func (prm *PrmContainerDelete) SetContainer(id cid.ID) {
|
||||
prm.ContainerID = &id
|
||||
}
|
||||
|
||||
func (x *PrmContainerDelete) buildRequest(c *Client) (*v2container.DeleteRequest, error) {
|
||||
if !x.idSet {
|
||||
func (prm *PrmContainerDelete) buildRequest(c *Client) (*v2container.DeleteRequest, error) {
|
||||
if prm.ContainerID == nil {
|
||||
return nil, errorMissingContainer
|
||||
}
|
||||
|
||||
if len(prm.XHeaders)%2 != 0 {
|
||||
return nil, errorInvalidXHeaders
|
||||
}
|
||||
|
||||
var cidV2 refs.ContainerID
|
||||
x.id.WriteToV2(&cidV2)
|
||||
prm.ContainerID.WriteToV2(&cidV2)
|
||||
|
||||
// Container contract expects signature of container ID value,
|
||||
// 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)
|
||||
|
||||
var meta v2session.RequestMetaHeader
|
||||
writeXHeadersToMeta(x.prmCommonMeta.xHeaders, &meta)
|
||||
writeXHeadersToMeta(prm.XHeaders, &meta)
|
||||
|
||||
if x.tokSet {
|
||||
if prm.Session != nil {
|
||||
var tokv2 v2session.Token
|
||||
x.tok.WriteToV2(&tokv2)
|
||||
prm.Session.WriteToV2(&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).
|
||||
//
|
||||
// Must be signed.
|
||||
func (x *PrmContainerDelete) WithinSession(tok session.Container) {
|
||||
x.tok = tok
|
||||
x.tokSet = true
|
||||
//
|
||||
// Deprecated: Use PrmContainerDelete.Session instead.
|
||||
func (prm *PrmContainerDelete) WithinSession(tok session.Container) {
|
||||
prm.Session = &tok
|
||||
}
|
||||
|
||||
// ResContainerDelete groups resulting values of ContainerDelete operation.
|
||||
|
|
|
@ -18,26 +18,31 @@ import (
|
|||
|
||||
// PrmContainerGet groups parameters of ContainerGet operation.
|
||||
type PrmContainerGet struct {
|
||||
prmCommonMeta
|
||||
// FrostFS request X-Headers.
|
||||
XHeaders []string
|
||||
|
||||
idSet bool
|
||||
id cid.ID
|
||||
ContainerID *cid.ID
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the container to be read.
|
||||
// Required parameter.
|
||||
func (x *PrmContainerGet) SetContainer(id cid.ID) {
|
||||
x.id = id
|
||||
x.idSet = true
|
||||
//
|
||||
// Deprecated: Use PrmContainerGet.ContainerID instead.
|
||||
func (prm *PrmContainerGet) SetContainer(cid cid.ID) {
|
||||
prm.ContainerID = &cid
|
||||
}
|
||||
|
||||
func (x *PrmContainerGet) buildRequest(c *Client) (*v2container.GetRequest, error) {
|
||||
if !x.idSet {
|
||||
func (prm *PrmContainerGet) buildRequest(c *Client) (*v2container.GetRequest, error) {
|
||||
if prm.ContainerID == nil {
|
||||
return nil, errorMissingContainer
|
||||
}
|
||||
|
||||
if len(prm.XHeaders)%2 != 0 {
|
||||
return nil, errorInvalidXHeaders
|
||||
}
|
||||
|
||||
var cidV2 refs.ContainerID
|
||||
x.id.WriteToV2(&cidV2)
|
||||
prm.ContainerID.WriteToV2(&cidV2)
|
||||
|
||||
reqBody := new(v2container.GetRequestBody)
|
||||
reqBody.SetContainerID(&cidV2)
|
||||
|
|
|
@ -19,20 +19,20 @@ import (
|
|||
|
||||
// PrmContainerPut groups parameters of ContainerPut operation.
|
||||
type PrmContainerPut struct {
|
||||
prmCommonMeta
|
||||
// FrostFS request X-Headers
|
||||
XHeaders []string
|
||||
|
||||
cnrSet bool
|
||||
cnr container.Container
|
||||
Container *container.Container
|
||||
|
||||
sessionSet bool
|
||||
session session.Container
|
||||
Session *session.Container
|
||||
}
|
||||
|
||||
// SetContainer sets structured information about new FrostFS container.
|
||||
// Required parameter.
|
||||
//
|
||||
// Deprecated: Use PrmContainerPut.Container instead.
|
||||
func (x *PrmContainerPut) SetContainer(cnr container.Container) {
|
||||
x.cnr = cnr
|
||||
x.cnrSet = true
|
||||
x.Container = &cnr
|
||||
}
|
||||
|
||||
// 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 operation MUST be session.VerbContainerPut (ForVerb)
|
||||
// - 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) {
|
||||
x.session = s
|
||||
x.sessionSet = true
|
||||
x.Session = &s
|
||||
}
|
||||
|
||||
func (x *PrmContainerPut) buildRequest(c *Client) (*v2container.PutRequest, error) {
|
||||
if !x.cnrSet {
|
||||
if x.Container == nil {
|
||||
return nil, errorMissingContainer
|
||||
}
|
||||
|
||||
if len(x.XHeaders)%2 != 0 {
|
||||
return nil, errorInvalidXHeaders
|
||||
}
|
||||
|
||||
// TODO: check private key is set before forming the request
|
||||
var cnr v2container.Container
|
||||
x.cnr.WriteToV2(&cnr)
|
||||
x.Container.WriteToV2(&cnr)
|
||||
|
||||
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 {
|
||||
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)
|
||||
|
||||
var meta v2session.RequestMetaHeader
|
||||
writeXHeadersToMeta(x.prmCommonMeta.xHeaders, &meta)
|
||||
writeXHeadersToMeta(x.XHeaders, &meta)
|
||||
|
||||
if x.sessionSet {
|
||||
if x.Session != nil {
|
||||
var tokv2 v2session.Token
|
||||
x.session.WriteToV2(&tokv2)
|
||||
x.Session.WriteToV2(&tokv2)
|
||||
|
||||
meta.SetSessionToken(&tokv2)
|
||||
}
|
||||
|
|
|
@ -1,97 +1,64 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
)
|
||||
|
||||
// unwraps err using errors.Unwrap and returns the result.
|
||||
func unwrapErr(err error) error {
|
||||
for e := errors.Unwrap(err); e != nil; e = errors.Unwrap(err) {
|
||||
err = e
|
||||
// wrapsErrType returns true if any error in the error tree of err is of type E.
|
||||
func wrapsErrType[E error](err error) bool {
|
||||
switch e := err.(type) {
|
||||
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
|
||||
// return corresponding to missing container. Supports wrapped errors.
|
||||
func IsErrContainerNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ContainerNotFound,
|
||||
*apistatus.ContainerNotFound:
|
||||
return true
|
||||
}
|
||||
return wrapsErrType[*apistatus.ContainerNotFound](err)
|
||||
}
|
||||
|
||||
// IsErrEACLNotFound checks if err corresponds to FrostFS status
|
||||
// return corresponding to missing eACL table. Supports wrapped errors.
|
||||
func IsErrEACLNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.EACLNotFound,
|
||||
*apistatus.EACLNotFound:
|
||||
return true
|
||||
}
|
||||
return wrapsErrType[*apistatus.EACLNotFound](err)
|
||||
}
|
||||
|
||||
// IsErrObjectNotFound checks if err corresponds to FrostFS status
|
||||
// return corresponding to missing object. Supports wrapped errors.
|
||||
func IsErrObjectNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ObjectNotFound,
|
||||
*apistatus.ObjectNotFound:
|
||||
return true
|
||||
}
|
||||
return wrapsErrType[*apistatus.ObjectNotFound](err)
|
||||
}
|
||||
|
||||
// IsErrObjectAlreadyRemoved checks if err corresponds to FrostFS status
|
||||
// return corresponding to already removed object. Supports wrapped errors.
|
||||
func IsErrObjectAlreadyRemoved(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ObjectAlreadyRemoved,
|
||||
*apistatus.ObjectAlreadyRemoved:
|
||||
return true
|
||||
}
|
||||
return wrapsErrType[*apistatus.ObjectAlreadyRemoved](err)
|
||||
}
|
||||
|
||||
// IsErrSessionExpired checks if err corresponds to FrostFS status return
|
||||
// corresponding to expired session. Supports wrapped errors.
|
||||
func IsErrSessionExpired(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.SessionTokenExpired,
|
||||
*apistatus.SessionTokenExpired:
|
||||
return true
|
||||
}
|
||||
return wrapsErrType[*apistatus.SessionTokenExpired](err)
|
||||
}
|
||||
|
||||
// IsErrSessionNotFound checks if err corresponds to FrostFS status return
|
||||
// corresponding to missing session. Supports wrapped errors.
|
||||
func IsErrSessionNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.SessionTokenNotFound,
|
||||
*apistatus.SessionTokenNotFound:
|
||||
return true
|
||||
}
|
||||
return wrapsErrType[*apistatus.SessionTokenNotFound](err)
|
||||
}
|
||||
|
||||
// returns error describing missing field with the given name.
|
||||
|
|
|
@ -10,59 +10,40 @@ import (
|
|||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
errs := []struct {
|
||||
check func(error) bool
|
||||
errs []error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
check: client.IsErrContainerNotFound,
|
||||
errs: []error{
|
||||
apistatus.ContainerNotFound{},
|
||||
new(apistatus.ContainerNotFound),
|
||||
},
|
||||
err: new(apistatus.ContainerNotFound),
|
||||
},
|
||||
{
|
||||
check: client.IsErrEACLNotFound,
|
||||
errs: []error{
|
||||
apistatus.EACLNotFound{},
|
||||
new(apistatus.EACLNotFound),
|
||||
},
|
||||
err: new(apistatus.EACLNotFound),
|
||||
},
|
||||
{
|
||||
check: client.IsErrObjectNotFound,
|
||||
errs: []error{
|
||||
apistatus.ObjectNotFound{},
|
||||
new(apistatus.ObjectNotFound),
|
||||
},
|
||||
err: new(apistatus.ObjectNotFound),
|
||||
},
|
||||
{
|
||||
check: client.IsErrObjectAlreadyRemoved,
|
||||
errs: []error{
|
||||
apistatus.ObjectAlreadyRemoved{},
|
||||
new(apistatus.ObjectAlreadyRemoved),
|
||||
},
|
||||
err: new(apistatus.ObjectAlreadyRemoved),
|
||||
},
|
||||
{
|
||||
check: client.IsErrSessionExpired,
|
||||
errs: []error{
|
||||
apistatus.SessionTokenExpired{},
|
||||
new(apistatus.SessionTokenExpired),
|
||||
},
|
||||
err: new(apistatus.SessionTokenExpired),
|
||||
}, {
|
||||
check: client.IsErrSessionNotFound,
|
||||
errs: []error{
|
||||
apistatus.SessionTokenNotFound{},
|
||||
new(apistatus.SessionTokenNotFound),
|
||||
err: new(apistatus.SessionTokenNotFound),
|
||||
},
|
||||
},
|
||||
} {
|
||||
require.NotEmpty(t, tc.errs)
|
||||
}
|
||||
|
||||
for i := range tc.errs {
|
||||
require.True(t, tc.check(tc.errs[i]), tc.errs[i])
|
||||
require.True(t, tc.check(fmt.Errorf("top-level context: :%w",
|
||||
fmt.Errorf("inner context: %w", tc.errs[i])),
|
||||
), tc.errs[i])
|
||||
for i := range errs {
|
||||
for j := range errs {
|
||||
nestedErr := fmt.Errorf("top-level context: :%w", fmt.Errorf("inner context: %w", errs[j].err))
|
||||
require.Equal(t, i == j, errs[i].check(errs[j].err))
|
||||
require.Equal(t, i == j, errs[i].check(nestedErr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,28 +3,29 @@ package client
|
|||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"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"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
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"
|
||||
)
|
||||
|
||||
// defaultGRPCPayloadChunkLen default value for maxChunkLen.
|
||||
// See PrmObjectPutInit.SetGRPCPayloadChunkLen for details.
|
||||
const defaultGRPCPayloadChunkLen = 3 << 20
|
||||
|
||||
// PrmObjectPutInit groups parameters of ObjectPutInit operation.
|
||||
type PrmObjectPutInit struct {
|
||||
copyNum []uint32
|
||||
key *ecdsa.PrivateKey
|
||||
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.
|
||||
|
@ -39,6 +40,15 @@ func (x *PrmObjectPutInit) SetCopiesNumberByVectors(copiesNumbers []uint32) {
|
|||
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.
|
||||
type ResObjectPut struct {
|
||||
statusRes
|
||||
|
@ -51,29 +61,35 @@ func (x ResObjectPut) StoredObjectID() oid.ID {
|
|||
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
|
||||
// usage is unsafe.
|
||||
type ObjectWriter struct {
|
||||
cancelCtxStream context.CancelFunc
|
||||
|
||||
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
|
||||
type ObjectWriter interface {
|
||||
// WriteHeader writes header of the object. Result means success.
|
||||
// Failure reason can be received via Close.
|
||||
WriteHeader(context.Context, object.Object) bool
|
||||
// WritePayloadChunk writes chunk of the object payload. Result means success.
|
||||
// Failure reason can be received via Close.
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
Close(context.Context) (*ResObjectPut, error)
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
|
@ -112,129 +128,21 @@ func (x *PrmObjectPutInit) WithXHeaders(hs ...string) {
|
|||
writeXHeadersToMeta(hs, &x.meta)
|
||||
}
|
||||
|
||||
// WriteHeader writes header of the object. Result means success.
|
||||
// Failure reason can be received via Close.
|
||||
func (x *ObjectWriter) WriteHeader(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
|
||||
// WithObjectMaxSize specifies max object size value and use it during object splitting.
|
||||
// When specified, start writing to the stream only after the object is formed.
|
||||
// Continue processing the input only when the previous formed object has been successfully written.
|
||||
func (x *PrmObjectPutInit) WithObjectMaxSize(maxSize uint64) {
|
||||
x.maxSize = maxSize
|
||||
}
|
||||
|
||||
x.err = x.stream.Write(&x.req)
|
||||
return x.err == nil
|
||||
// WithoutHomomorphicHash if set to true do not use Tillich-Zémor hash for payload.
|
||||
func (x *PrmObjectPutInit) WithoutHomomorphicHash(v bool) {
|
||||
x.withoutHomomorphicHash = v
|
||||
}
|
||||
|
||||
// WritePayloadChunk writes chunk of the object payload. Result means success.
|
||||
// Failure reason can be received via Close.
|
||||
func (x *ObjectWriter) WritePayloadChunk(chunk []byte) bool {
|
||||
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
|
||||
// WithEpochSource specifies epoch for object when split it on client side.
|
||||
func (x *PrmObjectPutInit) WithEpochSource(es transformer.EpochSource) {
|
||||
x.epochSource = es
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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) {
|
||||
var w ObjectWriter
|
||||
|
||||
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)
|
||||
func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (ObjectWriter, error) {
|
||||
if prm.maxSize > 0 {
|
||||
return c.objectPutInitTransformer(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
|
||||
return c.objectPutInitRaw(ctx, prm)
|
||||
}
|
||||
|
|
154
client/object_put_raw.go
Normal file
154
client/object_put_raw.go
Normal 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
116
client/object_put_single.go
Normal 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
|
||||
}
|
119
client/object_put_transformer.go
Normal file
119
client/object_put_transformer.go
Normal 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
|
||||
}
|
|
@ -14,7 +14,7 @@ type ServerInternal struct {
|
|||
v2 status.Status
|
||||
}
|
||||
|
||||
func (x ServerInternal) Error() string {
|
||||
func (x *ServerInternal) Error() string {
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(status.Internal, status.GlobalizeCommonFail),
|
||||
x.v2.Message(),
|
||||
|
@ -62,7 +62,7 @@ type WrongMagicNumber struct {
|
|||
v2 status.Status
|
||||
}
|
||||
|
||||
func (x WrongMagicNumber) Error() string {
|
||||
func (x *WrongMagicNumber) Error() string {
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(status.WrongMagicNumber, status.GlobalizeCommonFail),
|
||||
x.v2.Message(),
|
||||
|
@ -132,7 +132,7 @@ type SignatureVerification struct {
|
|||
|
||||
const defaultSignatureVerificationMsg = "signature verification failed"
|
||||
|
||||
func (x SignatureVerification) Error() string {
|
||||
func (x *SignatureVerification) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultSignatureVerificationMsg
|
||||
|
@ -191,7 +191,7 @@ type NodeUnderMaintenance struct {
|
|||
const defaultNodeUnderMaintenanceMsg = "node is under maintenance"
|
||||
|
||||
// Error implements the error interface.
|
||||
func (x NodeUnderMaintenance) Error() string {
|
||||
func (x *NodeUnderMaintenance) Error() string {
|
||||
msg := x.Message()
|
||||
if msg == "" {
|
||||
msg = defaultNodeUnderMaintenanceMsg
|
||||
|
|
|
@ -13,7 +13,7 @@ type ContainerNotFound struct {
|
|||
|
||||
const defaultContainerNotFoundMsg = "container not found"
|
||||
|
||||
func (x ContainerNotFound) Error() string {
|
||||
func (x *ContainerNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultContainerNotFoundMsg
|
||||
|
@ -51,7 +51,7 @@ type EACLNotFound struct {
|
|||
|
||||
const defaultEACLNotFoundMsg = "eACL not found"
|
||||
|
||||
func (x EACLNotFound) Error() string {
|
||||
func (x *EACLNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultEACLNotFoundMsg
|
||||
|
|
|
@ -13,7 +13,7 @@ type ObjectLocked struct {
|
|||
|
||||
const defaultObjectLockedMsg = "object is locked"
|
||||
|
||||
func (x ObjectLocked) Error() string {
|
||||
func (x *ObjectLocked) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectLockedMsg
|
||||
|
@ -50,7 +50,7 @@ type LockNonRegularObject struct {
|
|||
|
||||
const defaultLockNonRegularObjectMsg = "locking non-regular object is forbidden"
|
||||
|
||||
func (x LockNonRegularObject) Error() string {
|
||||
func (x *LockNonRegularObject) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultLockNonRegularObjectMsg
|
||||
|
@ -87,7 +87,7 @@ type ObjectAccessDenied struct {
|
|||
|
||||
const defaultObjectAccessDeniedMsg = "access to object operation denied"
|
||||
|
||||
func (x ObjectAccessDenied) Error() string {
|
||||
func (x *ObjectAccessDenied) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectAccessDeniedMsg
|
||||
|
@ -135,7 +135,7 @@ type ObjectNotFound struct {
|
|||
|
||||
const defaultObjectNotFoundMsg = "object not found"
|
||||
|
||||
func (x ObjectNotFound) Error() string {
|
||||
func (x *ObjectNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectNotFoundMsg
|
||||
|
@ -172,7 +172,7 @@ type ObjectAlreadyRemoved struct {
|
|||
|
||||
const defaultObjectAlreadyRemovedMsg = "object already removed"
|
||||
|
||||
func (x ObjectAlreadyRemoved) Error() string {
|
||||
func (x *ObjectAlreadyRemoved) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectAlreadyRemovedMsg
|
||||
|
@ -210,7 +210,7 @@ type ObjectOutOfRange struct {
|
|||
|
||||
const defaultObjectOutOfRangeMsg = "out of range"
|
||||
|
||||
func (x ObjectOutOfRange) Error() string {
|
||||
func (x *ObjectOutOfRange) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectOutOfRangeMsg
|
||||
|
|
|
@ -13,7 +13,7 @@ type SessionTokenNotFound struct {
|
|||
|
||||
const defaultSessionTokenNotFoundMsg = "session token not found"
|
||||
|
||||
func (x SessionTokenNotFound) Error() string {
|
||||
func (x *SessionTokenNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultSessionTokenNotFoundMsg
|
||||
|
@ -50,7 +50,7 @@ type SessionTokenExpired struct {
|
|||
|
||||
const defaultSessionTokenExpiredMsg = "expired session token"
|
||||
|
||||
func (x SessionTokenExpired) Error() string {
|
||||
func (x *SessionTokenExpired) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultSessionTokenExpiredMsg
|
||||
|
|
|
@ -15,7 +15,7 @@ package apistatus
|
|||
// 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.
|
||||
type Status interface{}
|
||||
type Status any
|
||||
|
||||
// ErrFromStatus converts Status instance to error if it is failed. Returns nil on successful Status.
|
||||
//
|
||||
|
|
|
@ -8,7 +8,7 @@ type unrecognizedStatusV2 struct {
|
|||
v2 status.Status
|
||||
}
|
||||
|
||||
func (x unrecognizedStatusV2) Error() string {
|
||||
func (x *unrecognizedStatusV2) Error() string {
|
||||
return errMessageStatusV2("unrecognized", x.v2.Message())
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ func ToStatusV2(st Status) *status.Status {
|
|||
return internalErrorStatus
|
||||
}
|
||||
|
||||
func errMessageStatusV2(code interface{}, msg string) string {
|
||||
func errMessageStatusV2(code any, msg string) string {
|
||||
const (
|
||||
noMsgFmt = "status: code = %v"
|
||||
msgFmt = noMsgFmt + " message = %s"
|
||||
|
|
|
@ -12,7 +12,7 @@ func TestToStatusV2(t *testing.T) {
|
|||
type statusConstructor func() apistatus.Status
|
||||
|
||||
for _, testItem := range [...]struct {
|
||||
status interface{} // Status or statusConstructor
|
||||
status any // Status or statusConstructor
|
||||
codeV2 uint64
|
||||
messageV2 string
|
||||
}{
|
||||
|
@ -165,7 +165,7 @@ func TestFromStatusV2(t *testing.T) {
|
|||
type statusConstructor func() apistatus.Status
|
||||
|
||||
for _, testItem := range [...]struct {
|
||||
status interface{} // Status or statusConstructor
|
||||
status any // Status or statusConstructor
|
||||
codeV2 uint64
|
||||
messageV2 string
|
||||
}{
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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.
|
||||
//
|
||||
// See also Attribute, IterateAttributes.
|
||||
// See also Attribute, IterateAttributes, IterateUserAttributes.
|
||||
func (x *Container) SetAttribute(key, value string) {
|
||||
if 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 absence.
|
||||
//
|
||||
// See also SetAttribute, IterateAttributes.
|
||||
// See also SetAttribute, IterateAttributes, IterateUserAttributes.
|
||||
func (x Container) Attribute(key string) string {
|
||||
attrs := x.v2.GetAttributes()
|
||||
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.
|
||||
//
|
||||
// See also Name.
|
||||
|
|
|
@ -150,7 +150,7 @@ func assertContainsAttribute(t *testing.T, m v2container.Container, key, val str
|
|||
}
|
||||
|
||||
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"
|
||||
|
||||
val := containertest.Container()
|
||||
|
@ -158,6 +158,12 @@ func TestContainer_Attribute(t *testing.T) {
|
|||
val.SetAttribute(attrKey1, attrVal1)
|
||||
val.SetAttribute(attrKey2, attrVal2)
|
||||
|
||||
var i int
|
||||
val.IterateUserAttributes(func(key, val string) {
|
||||
i++
|
||||
})
|
||||
require.Equal(t, 1, i)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
|
|
4
doc/image/filter_illustration.svg
Normal file
4
doc/image/filter_illustration.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 12 KiB |
4
doc/image/placement_policy.svg
Normal file
4
doc/image/placement_policy.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
4
doc/image/rep_illustration.svg
Normal file
4
doc/image/rep_illustration.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.8 KiB |
4
doc/image/sample_netmap.svg
Normal file
4
doc/image/sample_netmap.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 12 KiB |
4
doc/image/select_illustration.svg
Normal file
4
doc/image/select_illustration.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
445
doc/policy.md
Normal file
445
doc/policy.md
Normal 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
62
go.mod
|
@ -1,56 +1,46 @@
|
|||
module git.frostfs.info/TrueCloudLab/frostfs-sdk-go
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
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/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
|
||||
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/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/nspcc-dev/neo-go v0.100.1
|
||||
github.com/stretchr/testify v1.8.2
|
||||
go.uber.org/atomic v1.10.0
|
||||
github.com/nspcc-dev/neo-go v0.101.2-0.20230601131642-a0117042e8fc
|
||||
github.com/stretchr/testify v1.8.3
|
||||
go.uber.org/zap v1.24.0
|
||||
google.golang.org/grpc v1.55.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.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/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.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/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/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // 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
|
||||
github.com/twmb/murmur3 v1.1.8 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/goleak v1.2.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
133
go.sum
133
go.sum
|
@ -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.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=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.0 h1:oZ0/KiaFeveXRLi5VVEpuLSHczeFyWx4HDl9wTJUtsE=
|
||||
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.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-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44 h1:v6JqBD/VzZx3QSxbaXnUwnnJ1KEYheU4LzLGr3IhsAE=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230802075510-964c3edb3f44/go.mod h1:pKJJRLOChW4zDQsAt1e8k/snWKljJtpkiPfxV53ngjI=
|
||||
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-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/hrw v1.2.0 h1:KvAES7xIqmQBGd2q8KanNosD9+4BhU/zqD5Kt5KSflk=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.0/go.mod h1:mq2sbvYfO+BB6iFZwYBkgC0yc6mJNx+qZi4jW918m+Y=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
||||
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/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
|
||||
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.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/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/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=
|
||||
|
@ -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/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/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
|
||||
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 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
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/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
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/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/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/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.1/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/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-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-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/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=
|
||||
|
@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
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.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.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/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=
|
||||
|
@ -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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
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-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/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 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-20191227052852-215e87163ea7/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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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.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.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
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/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/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.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/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.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.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||
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.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU=
|
||||
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/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=
|
||||
|
@ -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.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.100.1 h1:yugxbQRdzM+ObVa5mtr9/n4rYjxSIrryne8MVr9NBwU=
|
||||
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 h1:fySIWvUQsitK5e5qYIHnTDCXuPpwzz89SEUEIyY11sg=
|
||||
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-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
|
||||
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 h1:vLGuUNDkmQrWMa4rr4vTd1u8ULqejWxVmNz1L7ocTEI=
|
||||
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.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
|
||||
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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
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/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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.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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
|
||||
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.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
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.3/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.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.7.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/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/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
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.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
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.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
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-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.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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-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-20221227203929-1b447090c38c h1:Govq2W3bnHJimHT2ium65kXcI7ZzTniZHcFATnLJM0Q=
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
|
||||
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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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-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-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-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-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
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-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-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-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/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=
|
||||
|
@ -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-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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
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-20180905080454-ebe1bf3edb33/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-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-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-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-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-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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
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-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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.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.5/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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
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-20190308202827-9d24e82272b4/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-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-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
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.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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
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.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
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-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/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.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/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=
|
||||
|
|
59
netmap/bench_test.go
Normal file
59
netmap/bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
|
@ -35,6 +35,11 @@ type context struct {
|
|||
|
||||
// container backup factor
|
||||
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.
|
||||
|
@ -58,6 +63,7 @@ func newContext(nm NetMap) *context {
|
|||
|
||||
numCache: make(map[string]uint64),
|
||||
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 {
|
||||
mean := newMeanAgg()
|
||||
min := newMinAgg()
|
||||
|
|
|
@ -39,7 +39,7 @@ func (c *context) processFilter(f netmap.Filter, top bool) error {
|
|||
inner := f.GetFilters()
|
||||
|
||||
switch op := f.GetOp(); op {
|
||||
case netmap.AND, netmap.OR:
|
||||
case netmap.AND, netmap.OR, netmap.NOT:
|
||||
for i := range inner {
|
||||
if err := c.processFilter(inner[i], false); err != nil {
|
||||
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.
|
||||
func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
|
||||
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:
|
||||
inner := f.GetFilters()
|
||||
for i := range inner {
|
||||
|
|
|
@ -158,6 +158,57 @@ func (m NetMap) PlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeIn
|
|||
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
|
||||
// given PlacementPolicy to the NetMap. Each line of the list corresponds to a
|
||||
// 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))
|
||||
|
||||
// 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 {
|
||||
sName := p.replicas[i].GetSelector()
|
||||
if sName == "" {
|
||||
if len(p.selectors) == 0 {
|
||||
var s netmap.Selector
|
||||
s.SetCount(p.replicas[i].GetCount())
|
||||
s.SetFilter(mainFilterName)
|
||||
|
@ -194,23 +248,30 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, e
|
|||
return nil, err
|
||||
}
|
||||
|
||||
result[i] = flattenNodes(nodes)
|
||||
}
|
||||
result[i] = append(result[i], flattenNodes(nodes)...)
|
||||
|
||||
for i := range p.selectors {
|
||||
result[i] = append(result[i], flattenNodes(c.selections[p.selectors[i].GetName()])...)
|
||||
if p.unique {
|
||||
c.addUsedNodes(result[i]...)
|
||||
}
|
||||
|
||||
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]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("selector not found: REPLICA '%s'", sName)
|
||||
}
|
||||
|
||||
result[i] = append(result[i], flattenNodes(nodes)...)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -61,16 +61,16 @@ func TestNetworkInfo_MsPerBlock(t *testing.T) {
|
|||
}
|
||||
|
||||
func testConfigValue(t *testing.T,
|
||||
getter func(x NetworkInfo) interface{},
|
||||
setter func(x *NetworkInfo, val interface{}),
|
||||
val1, val2 interface{},
|
||||
v2Key string, v2Val func(val interface{}) []byte,
|
||||
getter func(x NetworkInfo) any,
|
||||
setter func(x *NetworkInfo, val any),
|
||||
val1, val2 any,
|
||||
v2Key string, v2Val func(val any) []byte,
|
||||
) {
|
||||
var x NetworkInfo
|
||||
|
||||
require.Zero(t, getter(x))
|
||||
|
||||
checkVal := func(exp interface{}) {
|
||||
checkVal := func(exp any) {
|
||||
require.EqualValues(t, exp, getter(x))
|
||||
|
||||
var m netmap.NetworkInfo
|
||||
|
@ -97,10 +97,10 @@ func testConfigValue(t *testing.T,
|
|||
|
||||
func TestNetworkInfo_AuditFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.AuditFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetAuditFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.AuditFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetAuditFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"AuditFee", func(val interface{}) []byte {
|
||||
"AuditFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -110,10 +110,10 @@ func TestNetworkInfo_AuditFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_StoragePrice(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.StoragePrice() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetStoragePrice(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.StoragePrice() },
|
||||
func(info *NetworkInfo, val any) { info.SetStoragePrice(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"BasicIncomeRate", func(val interface{}) []byte {
|
||||
"BasicIncomeRate", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -123,10 +123,10 @@ func TestNetworkInfo_StoragePrice(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_ContainerFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.ContainerFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetContainerFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.ContainerFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetContainerFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"ContainerFee", func(val interface{}) []byte {
|
||||
"ContainerFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -136,10 +136,10 @@ func TestNetworkInfo_ContainerFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_NamedContainerFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.NamedContainerFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetNamedContainerFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.NamedContainerFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetNamedContainerFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"ContainerAliasFee", func(val interface{}) []byte {
|
||||
"ContainerAliasFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -149,10 +149,10 @@ func TestNetworkInfo_NamedContainerFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_IRCandidateFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.IRCandidateFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetIRCandidateFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.IRCandidateFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetIRCandidateFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"InnerRingCandidateFee", func(val interface{}) []byte {
|
||||
"InnerRingCandidateFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -162,10 +162,10 @@ func TestNetworkInfo_IRCandidateFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_MaxObjectSize(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.MaxObjectSize() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetMaxObjectSize(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.MaxObjectSize() },
|
||||
func(info *NetworkInfo, val any) { info.SetMaxObjectSize(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"MaxObjectSize", func(val interface{}) []byte {
|
||||
"MaxObjectSize", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -175,10 +175,10 @@ func TestNetworkInfo_MaxObjectSize(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_WithdrawalFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.WithdrawalFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetWithdrawalFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.WithdrawalFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetWithdrawalFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"WithdrawFee", func(val interface{}) []byte {
|
||||
"WithdrawFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -188,14 +188,14 @@ func TestNetworkInfo_WithdrawalFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.HomomorphicHashingDisabled() },
|
||||
func(info *NetworkInfo, val interface{}) {
|
||||
func(x NetworkInfo) any { return x.HomomorphicHashingDisabled() },
|
||||
func(info *NetworkInfo, val any) {
|
||||
if val.(bool) {
|
||||
info.DisableHomomorphicHashing()
|
||||
}
|
||||
},
|
||||
true, true, // it is impossible to enable hashing
|
||||
"HomomorphicHashingDisabled", func(val interface{}) []byte {
|
||||
"HomomorphicHashingDisabled", func(val any) []byte {
|
||||
data := make([]byte, 1)
|
||||
|
||||
if val.(bool) {
|
||||
|
@ -209,14 +209,14 @@ func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_MaintenanceModeAllowed(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.MaintenanceModeAllowed() },
|
||||
func(info *NetworkInfo, val interface{}) {
|
||||
func(x NetworkInfo) any { return x.MaintenanceModeAllowed() },
|
||||
func(info *NetworkInfo, val any) {
|
||||
if val.(bool) {
|
||||
info.AllowMaintenanceMode()
|
||||
}
|
||||
},
|
||||
true, true,
|
||||
"MaintenanceModeAllowed", func(val interface{}) []byte {
|
||||
"MaintenanceModeAllowed", func(val any) []byte {
|
||||
if val.(bool) {
|
||||
return []byte{1}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ options {
|
|||
tokenVocab = QueryLexer;
|
||||
}
|
||||
|
||||
policy: repStmt+ cbfStmt? selectStmt* filterStmt* EOF;
|
||||
policy: UNIQUE? repStmt+ cbfStmt? selectStmt* filterStmt* EOF;
|
||||
|
||||
selectFilterExpr: cbfStmt? selectStmt? filterStmt* EOF;
|
||||
|
||||
repStmt:
|
||||
REP Count = NUMBER1 // number of object replicas
|
||||
|
@ -22,7 +24,8 @@ selectStmt:
|
|||
clause: CLAUSE_SAME | CLAUSE_DISTINCT; // nodes from distinct buckets
|
||||
|
||||
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
|
||||
| '(' Inner = filterExpr ')'
|
||||
| expr
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,9 +1,11 @@
|
|||
lexer grammar QueryLexer;
|
||||
|
||||
NOT_OP : 'NOT';
|
||||
AND_OP : 'AND';
|
||||
OR_OP : 'OR';
|
||||
SIMPLE_OP : 'EQ' | 'NE' | 'GE' | 'GT' | 'LT' | 'LE';
|
||||
|
||||
UNIQUE : 'UNIQUE';
|
||||
REP : 'REP';
|
||||
IN : 'IN';
|
||||
AS : 'AS';
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
package parser
|
||||
|
||||
// ANTLR can be downloaded from https://www.antlr.org/download/antlr-4.11.1-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
|
||||
// ANTLR can be downloaded from https://www.antlr.org/download/antlr-4.13.0-complete.jar
|
||||
//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
|
||||
|
|
|
@ -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) {}
|
|
@ -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
|
||||
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
import "github.com/antlr4-go/antlr/v4"
|
||||
|
||||
type BaseQueryVisitor struct {
|
||||
*antlr.BaseParseTreeVisitor
|
||||
|
@ -12,6 +12,10 @@ func (v *BaseQueryVisitor) VisitPolicy(ctx *PolicyContext) interface{} {
|
|||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitSelectFilterExpr(ctx *SelectFilterExprContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitRepStmt(ctx *RepStmtContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
|
@ -22,134 +21,141 @@ type QueryLexer struct {
|
|||
// TODO: EOF string
|
||||
}
|
||||
|
||||
var querylexerLexerStaticData struct {
|
||||
var QueryLexerLexerStaticData struct {
|
||||
once sync.Once
|
||||
serializedATN []int32
|
||||
channelNames []string
|
||||
modeNames []string
|
||||
literalNames []string
|
||||
symbolicNames []string
|
||||
ruleNames []string
|
||||
predictionContextCache *antlr.PredictionContextCache
|
||||
ChannelNames []string
|
||||
ModeNames []string
|
||||
LiteralNames []string
|
||||
SymbolicNames []string
|
||||
RuleNames []string
|
||||
PredictionContextCache *antlr.PredictionContextCache
|
||||
atn *antlr.ATN
|
||||
decisionToDFA []*antlr.DFA
|
||||
}
|
||||
|
||||
func querylexerLexerInit() {
|
||||
staticData := &querylexerLexerStaticData
|
||||
staticData.channelNames = []string{
|
||||
staticData := &QueryLexerLexerStaticData
|
||||
staticData.ChannelNames = []string{
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
|
||||
}
|
||||
staticData.modeNames = []string{
|
||||
staticData.ModeNames = []string{
|
||||
"DEFAULT_MODE",
|
||||
}
|
||||
staticData.literalNames = []string{
|
||||
"", "'AND'", "'OR'", "", "'REP'", "'IN'", "'AS'", "'CBF'", "'SELECT'",
|
||||
"'FROM'", "'FILTER'", "'*'", "'SAME'", "'DISTINCT'", "'('", "')'", "'@'",
|
||||
"", "", "'0'",
|
||||
staticData.LiteralNames = []string{
|
||||
"", "'NOT'", "'AND'", "'OR'", "", "'UNIQUE'", "'REP'", "'IN'", "'AS'",
|
||||
"'CBF'", "'SELECT'", "'FROM'", "'FILTER'", "'*'", "'SAME'", "'DISTINCT'",
|
||||
"'('", "')'", "'@'", "", "", "'0'",
|
||||
}
|
||||
staticData.symbolicNames = []string{
|
||||
"", "AND_OP", "OR_OP", "SIMPLE_OP", "REP", "IN", "AS", "CBF", "SELECT",
|
||||
"FROM", "FILTER", "WILDCARD", "CLAUSE_SAME", "CLAUSE_DISTINCT", "L_PAREN",
|
||||
"R_PAREN", "AT", "IDENT", "NUMBER1", "ZERO", "STRING", "WS",
|
||||
staticData.SymbolicNames = []string{
|
||||
"", "NOT_OP", "AND_OP", "OR_OP", "SIMPLE_OP", "UNIQUE", "REP", "IN",
|
||||
"AS", "CBF", "SELECT", "FROM", "FILTER", "WILDCARD", "CLAUSE_SAME",
|
||||
"CLAUSE_DISTINCT", "L_PAREN", "R_PAREN", "AT", "IDENT", "NUMBER1", "ZERO",
|
||||
"STRING", "WS",
|
||||
}
|
||||
staticData.ruleNames = []string{
|
||||
"AND_OP", "OR_OP", "SIMPLE_OP", "REP", "IN", "AS", "CBF", "SELECT",
|
||||
"FROM", "FILTER", "WILDCARD", "CLAUSE_SAME", "CLAUSE_DISTINCT", "L_PAREN",
|
||||
"R_PAREN", "AT", "IDENT", "Digit", "Nondigit", "NUMBER1", "ZERO", "STRING",
|
||||
"ESC", "UNICODE", "HEX", "SAFECODEPOINTSINGLE", "SAFECODEPOINTDOUBLE",
|
||||
staticData.RuleNames = []string{
|
||||
"NOT_OP", "AND_OP", "OR_OP", "SIMPLE_OP", "UNIQUE", "REP", "IN", "AS",
|
||||
"CBF", "SELECT", "FROM", "FILTER", "WILDCARD", "CLAUSE_SAME", "CLAUSE_DISTINCT",
|
||||
"L_PAREN", "R_PAREN", "AT", "IDENT", "Digit", "Nondigit", "NUMBER1",
|
||||
"ZERO", "STRING", "ESC", "UNICODE", "HEX", "SAFECODEPOINTSINGLE", "SAFECODEPOINTDOUBLE",
|
||||
"WS",
|
||||
}
|
||||
staticData.predictionContextCache = antlr.NewPredictionContextCache()
|
||||
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
|
||||
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,
|
||||
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,
|
||||
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, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3,
|
||||
2, 77, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5,
|
||||
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, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10,
|
||||
1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1,
|
||||
12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15,
|
||||
1, 16, 1, 16, 1, 16, 5, 16, 137, 8, 16, 10, 16, 12, 16, 140, 9, 16, 1,
|
||||
17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 5, 19, 148, 8, 19, 10, 19, 12, 19,
|
||||
151, 9, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 5, 21, 158, 8, 21, 10, 21,
|
||||
12, 21, 161, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 167, 8, 21, 10,
|
||||
21, 12, 21, 170, 9, 21, 1, 21, 3, 21, 173, 8, 21, 1, 22, 1, 22, 1, 22,
|
||||
3, 22, 178, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1,
|
||||
24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 4, 27, 193, 8, 27, 11, 27, 12, 27,
|
||||
194, 1, 27, 1, 27, 0, 0, 28, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7,
|
||||
15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33,
|
||||
17, 35, 0, 37, 0, 39, 18, 41, 19, 43, 20, 45, 0, 47, 0, 49, 0, 51, 0, 53,
|
||||
0, 55, 21, 1, 0, 8, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 1, 0,
|
||||
49, 57, 9, 0, 34, 34, 39, 39, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110,
|
||||
114, 114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 3, 0, 0, 31, 39, 39,
|
||||
92, 92, 3, 0, 0, 31, 34, 34, 92, 92, 3, 0, 9, 10, 13, 13, 32, 32, 205,
|
||||
0, 1, 1, 0, 0, 0, 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, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 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,
|
||||
0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1,
|
||||
0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43,
|
||||
1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 1, 57, 1, 0, 0, 0, 3, 61, 1, 0, 0, 0, 5,
|
||||
76, 1, 0, 0, 0, 7, 78, 1, 0, 0, 0, 9, 82, 1, 0, 0, 0, 11, 85, 1, 0, 0,
|
||||
0, 13, 88, 1, 0, 0, 0, 15, 92, 1, 0, 0, 0, 17, 99, 1, 0, 0, 0, 19, 104,
|
||||
1, 0, 0, 0, 21, 111, 1, 0, 0, 0, 23, 113, 1, 0, 0, 0, 25, 118, 1, 0, 0,
|
||||
0, 27, 127, 1, 0, 0, 0, 29, 129, 1, 0, 0, 0, 31, 131, 1, 0, 0, 0, 33, 133,
|
||||
1, 0, 0, 0, 35, 141, 1, 0, 0, 0, 37, 143, 1, 0, 0, 0, 39, 145, 1, 0, 0,
|
||||
0, 41, 152, 1, 0, 0, 0, 43, 172, 1, 0, 0, 0, 45, 174, 1, 0, 0, 0, 47, 179,
|
||||
1, 0, 0, 0, 49, 185, 1, 0, 0, 0, 51, 187, 1, 0, 0, 0, 53, 189, 1, 0, 0,
|
||||
0, 55, 192, 1, 0, 0, 0, 57, 58, 5, 65, 0, 0, 58, 59, 5, 78, 0, 0, 59, 60,
|
||||
5, 68, 0, 0, 60, 2, 1, 0, 0, 0, 61, 62, 5, 79, 0, 0, 62, 63, 5, 82, 0,
|
||||
0, 63, 4, 1, 0, 0, 0, 64, 65, 5, 69, 0, 0, 65, 77, 5, 81, 0, 0, 66, 67,
|
||||
5, 78, 0, 0, 67, 77, 5, 69, 0, 0, 68, 69, 5, 71, 0, 0, 69, 77, 5, 69, 0,
|
||||
0, 70, 71, 5, 71, 0, 0, 71, 77, 5, 84, 0, 0, 72, 73, 5, 76, 0, 0, 73, 77,
|
||||
5, 84, 0, 0, 74, 75, 5, 76, 0, 0, 75, 77, 5, 69, 0, 0, 76, 64, 1, 0, 0,
|
||||
0, 76, 66, 1, 0, 0, 0, 76, 68, 1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 76, 72,
|
||||
1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 77, 6, 1, 0, 0, 0, 78, 79, 5, 82, 0, 0,
|
||||
79, 80, 5, 69, 0, 0, 80, 81, 5, 80, 0, 0, 81, 8, 1, 0, 0, 0, 82, 83, 5,
|
||||
73, 0, 0, 83, 84, 5, 78, 0, 0, 84, 10, 1, 0, 0, 0, 85, 86, 5, 65, 0, 0,
|
||||
86, 87, 5, 83, 0, 0, 87, 12, 1, 0, 0, 0, 88, 89, 5, 67, 0, 0, 89, 90, 5,
|
||||
66, 0, 0, 90, 91, 5, 70, 0, 0, 91, 14, 1, 0, 0, 0, 92, 93, 5, 83, 0, 0,
|
||||
93, 94, 5, 69, 0, 0, 94, 95, 5, 76, 0, 0, 95, 96, 5, 69, 0, 0, 96, 97,
|
||||
5, 67, 0, 0, 97, 98, 5, 84, 0, 0, 98, 16, 1, 0, 0, 0, 99, 100, 5, 70, 0,
|
||||
0, 100, 101, 5, 82, 0, 0, 101, 102, 5, 79, 0, 0, 102, 103, 5, 77, 0, 0,
|
||||
103, 18, 1, 0, 0, 0, 104, 105, 5, 70, 0, 0, 105, 106, 5, 73, 0, 0, 106,
|
||||
107, 5, 76, 0, 0, 107, 108, 5, 84, 0, 0, 108, 109, 5, 69, 0, 0, 109, 110,
|
||||
5, 82, 0, 0, 110, 20, 1, 0, 0, 0, 111, 112, 5, 42, 0, 0, 112, 22, 1, 0,
|
||||
0, 0, 113, 114, 5, 83, 0, 0, 114, 115, 5, 65, 0, 0, 115, 116, 5, 77, 0,
|
||||
0, 116, 117, 5, 69, 0, 0, 117, 24, 1, 0, 0, 0, 118, 119, 5, 68, 0, 0, 119,
|
||||
120, 5, 73, 0, 0, 120, 121, 5, 83, 0, 0, 121, 122, 5, 84, 0, 0, 122, 123,
|
||||
5, 73, 0, 0, 123, 124, 5, 78, 0, 0, 124, 125, 5, 67, 0, 0, 125, 126, 5,
|
||||
84, 0, 0, 126, 26, 1, 0, 0, 0, 127, 128, 5, 40, 0, 0, 128, 28, 1, 0, 0,
|
||||
0, 129, 130, 5, 41, 0, 0, 130, 30, 1, 0, 0, 0, 131, 132, 5, 64, 0, 0, 132,
|
||||
32, 1, 0, 0, 0, 133, 138, 3, 37, 18, 0, 134, 137, 3, 35, 17, 0, 135, 137,
|
||||
3, 37, 18, 0, 136, 134, 1, 0, 0, 0, 136, 135, 1, 0, 0, 0, 137, 140, 1,
|
||||
0, 0, 0, 138, 136, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, 34, 1, 0, 0,
|
||||
0, 140, 138, 1, 0, 0, 0, 141, 142, 7, 0, 0, 0, 142, 36, 1, 0, 0, 0, 143,
|
||||
144, 7, 1, 0, 0, 144, 38, 1, 0, 0, 0, 145, 149, 7, 2, 0, 0, 146, 148, 3,
|
||||
35, 17, 0, 147, 146, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0,
|
||||
0, 0, 149, 150, 1, 0, 0, 0, 150, 40, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0,
|
||||
152, 153, 5, 48, 0, 0, 153, 42, 1, 0, 0, 0, 154, 159, 5, 34, 0, 0, 155,
|
||||
158, 3, 45, 22, 0, 156, 158, 3, 53, 26, 0, 157, 155, 1, 0, 0, 0, 157, 156,
|
||||
1, 0, 0, 0, 158, 161, 1, 0, 0, 0, 159, 157, 1, 0, 0, 0, 159, 160, 1, 0,
|
||||
0, 0, 160, 162, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 162, 173, 5, 34, 0, 0,
|
||||
163, 168, 5, 39, 0, 0, 164, 167, 3, 45, 22, 0, 165, 167, 3, 51, 25, 0,
|
||||
166, 164, 1, 0, 0, 0, 166, 165, 1, 0, 0, 0, 167, 170, 1, 0, 0, 0, 168,
|
||||
166, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169, 171, 1, 0, 0, 0, 170, 168,
|
||||
1, 0, 0, 0, 171, 173, 5, 39, 0, 0, 172, 154, 1, 0, 0, 0, 172, 163, 1, 0,
|
||||
0, 0, 173, 44, 1, 0, 0, 0, 174, 177, 5, 92, 0, 0, 175, 178, 7, 3, 0, 0,
|
||||
176, 178, 3, 47, 23, 0, 177, 175, 1, 0, 0, 0, 177, 176, 1, 0, 0, 0, 178,
|
||||
46, 1, 0, 0, 0, 179, 180, 5, 117, 0, 0, 180, 181, 3, 49, 24, 0, 181, 182,
|
||||
3, 49, 24, 0, 182, 183, 3, 49, 24, 0, 183, 184, 3, 49, 24, 0, 184, 48,
|
||||
1, 0, 0, 0, 185, 186, 7, 4, 0, 0, 186, 50, 1, 0, 0, 0, 187, 188, 8, 5,
|
||||
0, 0, 188, 52, 1, 0, 0, 0, 189, 190, 8, 6, 0, 0, 190, 54, 1, 0, 0, 0, 191,
|
||||
193, 7, 7, 0, 0, 192, 191, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 192,
|
||||
1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 197, 6, 27,
|
||||
0, 0, 197, 56, 1, 0, 0, 0, 12, 0, 76, 136, 138, 149, 157, 159, 166, 168,
|
||||
172, 177, 194, 1, 6, 0, 0,
|
||||
2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1,
|
||||
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, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6,
|
||||
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, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11,
|
||||
1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1,
|
||||
13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15,
|
||||
1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 5, 18, 152, 8,
|
||||
18, 10, 18, 12, 18, 155, 9, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21,
|
||||
5, 21, 163, 8, 21, 10, 21, 12, 21, 166, 9, 21, 1, 22, 1, 22, 1, 23, 1,
|
||||
23, 1, 23, 5, 23, 173, 8, 23, 10, 23, 12, 23, 176, 9, 23, 1, 23, 1, 23,
|
||||
1, 23, 1, 23, 5, 23, 182, 8, 23, 10, 23, 12, 23, 185, 9, 23, 1, 23, 3,
|
||||
23, 188, 8, 23, 1, 24, 1, 24, 1, 24, 3, 24, 193, 8, 24, 1, 25, 1, 25, 1,
|
||||
25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29,
|
||||
4, 29, 208, 8, 29, 11, 29, 12, 29, 209, 1, 29, 1, 29, 0, 0, 30, 1, 1, 3,
|
||||
2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12,
|
||||
25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 0, 41, 0, 43,
|
||||
20, 45, 21, 47, 22, 49, 0, 51, 0, 53, 0, 55, 0, 57, 0, 59, 23, 1, 0, 8,
|
||||
1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 1, 0, 49, 57, 9, 0, 34, 34,
|
||||
39, 39, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116,
|
||||
3, 0, 48, 57, 65, 70, 97, 102, 3, 0, 0, 31, 39, 39, 92, 92, 3, 0, 0, 31,
|
||||
34, 34, 92, 92, 3, 0, 9, 10, 13, 13, 32, 32, 220, 0, 1, 1, 0, 0, 0, 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,
|
||||
11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 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, 0, 0, 0, 25, 1, 0, 0,
|
||||
0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 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, 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, 5, 69, 1, 0, 0, 0, 7, 84, 1, 0, 0, 0, 9, 86, 1, 0, 0, 0, 11,
|
||||
93, 1, 0, 0, 0, 13, 97, 1, 0, 0, 0, 15, 100, 1, 0, 0, 0, 17, 103, 1, 0,
|
||||
0, 0, 19, 107, 1, 0, 0, 0, 21, 114, 1, 0, 0, 0, 23, 119, 1, 0, 0, 0, 25,
|
||||
126, 1, 0, 0, 0, 27, 128, 1, 0, 0, 0, 29, 133, 1, 0, 0, 0, 31, 142, 1,
|
||||
0, 0, 0, 33, 144, 1, 0, 0, 0, 35, 146, 1, 0, 0, 0, 37, 148, 1, 0, 0, 0,
|
||||
39, 156, 1, 0, 0, 0, 41, 158, 1, 0, 0, 0, 43, 160, 1, 0, 0, 0, 45, 167,
|
||||
1, 0, 0, 0, 47, 187, 1, 0, 0, 0, 49, 189, 1, 0, 0, 0, 51, 194, 1, 0, 0,
|
||||
0, 53, 200, 1, 0, 0, 0, 55, 202, 1, 0, 0, 0, 57, 204, 1, 0, 0, 0, 59, 207,
|
||||
1, 0, 0, 0, 61, 62, 5, 78, 0, 0, 62, 63, 5, 79, 0, 0, 63, 64, 5, 84, 0,
|
||||
0, 64, 2, 1, 0, 0, 0, 65, 66, 5, 65, 0, 0, 66, 67, 5, 78, 0, 0, 67, 68,
|
||||
5, 68, 0, 0, 68, 4, 1, 0, 0, 0, 69, 70, 5, 79, 0, 0, 70, 71, 5, 82, 0,
|
||||
0, 71, 6, 1, 0, 0, 0, 72, 73, 5, 69, 0, 0, 73, 85, 5, 81, 0, 0, 74, 75,
|
||||
5, 78, 0, 0, 75, 85, 5, 69, 0, 0, 76, 77, 5, 71, 0, 0, 77, 85, 5, 69, 0,
|
||||
0, 78, 79, 5, 71, 0, 0, 79, 85, 5, 84, 0, 0, 80, 81, 5, 76, 0, 0, 81, 85,
|
||||
5, 84, 0, 0, 82, 83, 5, 76, 0, 0, 83, 85, 5, 69, 0, 0, 84, 72, 1, 0, 0,
|
||||
0, 84, 74, 1, 0, 0, 0, 84, 76, 1, 0, 0, 0, 84, 78, 1, 0, 0, 0, 84, 80,
|
||||
1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 85, 8, 1, 0, 0, 0, 86, 87, 5, 85, 0, 0,
|
||||
87, 88, 5, 78, 0, 0, 88, 89, 5, 73, 0, 0, 89, 90, 5, 81, 0, 0, 90, 91,
|
||||
5, 85, 0, 0, 91, 92, 5, 69, 0, 0, 92, 10, 1, 0, 0, 0, 93, 94, 5, 82, 0,
|
||||
0, 94, 95, 5, 69, 0, 0, 95, 96, 5, 80, 0, 0, 96, 12, 1, 0, 0, 0, 97, 98,
|
||||
5, 73, 0, 0, 98, 99, 5, 78, 0, 0, 99, 14, 1, 0, 0, 0, 100, 101, 5, 65,
|
||||
0, 0, 101, 102, 5, 83, 0, 0, 102, 16, 1, 0, 0, 0, 103, 104, 5, 67, 0, 0,
|
||||
104, 105, 5, 66, 0, 0, 105, 106, 5, 70, 0, 0, 106, 18, 1, 0, 0, 0, 107,
|
||||
108, 5, 83, 0, 0, 108, 109, 5, 69, 0, 0, 109, 110, 5, 76, 0, 0, 110, 111,
|
||||
5, 69, 0, 0, 111, 112, 5, 67, 0, 0, 112, 113, 5, 84, 0, 0, 113, 20, 1,
|
||||
0, 0, 0, 114, 115, 5, 70, 0, 0, 115, 116, 5, 82, 0, 0, 116, 117, 5, 79,
|
||||
0, 0, 117, 118, 5, 77, 0, 0, 118, 22, 1, 0, 0, 0, 119, 120, 5, 70, 0, 0,
|
||||
120, 121, 5, 73, 0, 0, 121, 122, 5, 76, 0, 0, 122, 123, 5, 84, 0, 0, 123,
|
||||
124, 5, 69, 0, 0, 124, 125, 5, 82, 0, 0, 125, 24, 1, 0, 0, 0, 126, 127,
|
||||
5, 42, 0, 0, 127, 26, 1, 0, 0, 0, 128, 129, 5, 83, 0, 0, 129, 130, 5, 65,
|
||||
0, 0, 130, 131, 5, 77, 0, 0, 131, 132, 5, 69, 0, 0, 132, 28, 1, 0, 0, 0,
|
||||
133, 134, 5, 68, 0, 0, 134, 135, 5, 73, 0, 0, 135, 136, 5, 83, 0, 0, 136,
|
||||
137, 5, 84, 0, 0, 137, 138, 5, 73, 0, 0, 138, 139, 5, 78, 0, 0, 139, 140,
|
||||
5, 67, 0, 0, 140, 141, 5, 84, 0, 0, 141, 30, 1, 0, 0, 0, 142, 143, 5, 40,
|
||||
0, 0, 143, 32, 1, 0, 0, 0, 144, 145, 5, 41, 0, 0, 145, 34, 1, 0, 0, 0,
|
||||
146, 147, 5, 64, 0, 0, 147, 36, 1, 0, 0, 0, 148, 153, 3, 41, 20, 0, 149,
|
||||
152, 3, 39, 19, 0, 150, 152, 3, 41, 20, 0, 151, 149, 1, 0, 0, 0, 151, 150,
|
||||
1, 0, 0, 0, 152, 155, 1, 0, 0, 0, 153, 151, 1, 0, 0, 0, 153, 154, 1, 0,
|
||||
0, 0, 154, 38, 1, 0, 0, 0, 155, 153, 1, 0, 0, 0, 156, 157, 7, 0, 0, 0,
|
||||
157, 40, 1, 0, 0, 0, 158, 159, 7, 1, 0, 0, 159, 42, 1, 0, 0, 0, 160, 164,
|
||||
7, 2, 0, 0, 161, 163, 3, 39, 19, 0, 162, 161, 1, 0, 0, 0, 163, 166, 1,
|
||||
0, 0, 0, 164, 162, 1, 0, 0, 0, 164, 165, 1, 0, 0, 0, 165, 44, 1, 0, 0,
|
||||
0, 166, 164, 1, 0, 0, 0, 167, 168, 5, 48, 0, 0, 168, 46, 1, 0, 0, 0, 169,
|
||||
174, 5, 34, 0, 0, 170, 173, 3, 49, 24, 0, 171, 173, 3, 57, 28, 0, 172,
|
||||
170, 1, 0, 0, 0, 172, 171, 1, 0, 0, 0, 173, 176, 1, 0, 0, 0, 174, 172,
|
||||
1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 177, 1, 0, 0, 0, 176, 174, 1, 0,
|
||||
0, 0, 177, 188, 5, 34, 0, 0, 178, 183, 5, 39, 0, 0, 179, 182, 3, 49, 24,
|
||||
0, 180, 182, 3, 55, 27, 0, 181, 179, 1, 0, 0, 0, 181, 180, 1, 0, 0, 0,
|
||||
182, 185, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184,
|
||||
186, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 186, 188, 5, 39, 0, 0, 187, 169,
|
||||
1, 0, 0, 0, 187, 178, 1, 0, 0, 0, 188, 48, 1, 0, 0, 0, 189, 192, 5, 92,
|
||||
0, 0, 190, 193, 7, 3, 0, 0, 191, 193, 3, 51, 25, 0, 192, 190, 1, 0, 0,
|
||||
0, 192, 191, 1, 0, 0, 0, 193, 50, 1, 0, 0, 0, 194, 195, 5, 117, 0, 0, 195,
|
||||
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)
|
||||
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
|
||||
// of time.
|
||||
func QueryLexerInit() {
|
||||
staticData := &querylexerLexerStaticData
|
||||
staticData := &QueryLexerLexerStaticData
|
||||
staticData.once.Do(querylexerLexerInit)
|
||||
}
|
||||
|
||||
|
@ -175,13 +181,13 @@ func NewQueryLexer(input antlr.CharStream) *QueryLexer {
|
|||
QueryLexerInit()
|
||||
l := new(QueryLexer)
|
||||
l.BaseLexer = antlr.NewBaseLexer(input)
|
||||
staticData := &querylexerLexerStaticData
|
||||
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.predictionContextCache)
|
||||
l.channelNames = staticData.channelNames
|
||||
l.modeNames = staticData.modeNames
|
||||
l.RuleNames = staticData.ruleNames
|
||||
l.LiteralNames = staticData.literalNames
|
||||
l.SymbolicNames = staticData.symbolicNames
|
||||
staticData := &QueryLexerLexerStaticData
|
||||
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache)
|
||||
l.channelNames = staticData.ChannelNames
|
||||
l.modeNames = staticData.ModeNames
|
||||
l.RuleNames = staticData.RuleNames
|
||||
l.LiteralNames = staticData.LiteralNames
|
||||
l.SymbolicNames = staticData.SymbolicNames
|
||||
l.GrammarFileName = "QueryLexer.g4"
|
||||
// TODO: l.EOF = antlr.TokenEOF
|
||||
|
||||
|
@ -190,25 +196,27 @@ func NewQueryLexer(input antlr.CharStream) *QueryLexer {
|
|||
|
||||
// QueryLexer tokens.
|
||||
const (
|
||||
QueryLexerAND_OP = 1
|
||||
QueryLexerOR_OP = 2
|
||||
QueryLexerSIMPLE_OP = 3
|
||||
QueryLexerREP = 4
|
||||
QueryLexerIN = 5
|
||||
QueryLexerAS = 6
|
||||
QueryLexerCBF = 7
|
||||
QueryLexerSELECT = 8
|
||||
QueryLexerFROM = 9
|
||||
QueryLexerFILTER = 10
|
||||
QueryLexerWILDCARD = 11
|
||||
QueryLexerCLAUSE_SAME = 12
|
||||
QueryLexerCLAUSE_DISTINCT = 13
|
||||
QueryLexerL_PAREN = 14
|
||||
QueryLexerR_PAREN = 15
|
||||
QueryLexerAT = 16
|
||||
QueryLexerIDENT = 17
|
||||
QueryLexerNUMBER1 = 18
|
||||
QueryLexerZERO = 19
|
||||
QueryLexerSTRING = 20
|
||||
QueryLexerWS = 21
|
||||
QueryLexerNOT_OP = 1
|
||||
QueryLexerAND_OP = 2
|
||||
QueryLexerOR_OP = 3
|
||||
QueryLexerSIMPLE_OP = 4
|
||||
QueryLexerUNIQUE = 5
|
||||
QueryLexerREP = 6
|
||||
QueryLexerIN = 7
|
||||
QueryLexerAS = 8
|
||||
QueryLexerCBF = 9
|
||||
QueryLexerSELECT = 10
|
||||
QueryLexerFROM = 11
|
||||
QueryLexerFILTER = 12
|
||||
QueryLexerWILDCARD = 13
|
||||
QueryLexerCLAUSE_SAME = 14
|
||||
QueryLexerCLAUSE_DISTINCT = 15
|
||||
QueryLexerL_PAREN = 16
|
||||
QueryLexerR_PAREN = 17
|
||||
QueryLexerAT = 18
|
||||
QueryLexerIDENT = 19
|
||||
QueryLexerNUMBER1 = 20
|
||||
QueryLexerZERO = 21
|
||||
QueryLexerSTRING = 22
|
||||
QueryLexerWS = 23
|
||||
)
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
||||
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.
|
||||
type QueryVisitor interface {
|
||||
|
@ -11,6 +11,9 @@ type QueryVisitor interface {
|
|||
// Visit a parse tree produced by Query#policy.
|
||||
VisitPolicy(ctx *PolicyContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by Query#selectFilterExpr.
|
||||
VisitSelectFilterExpr(ctx *SelectFilterExprContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by Query#repStmt.
|
||||
VisitRepStmt(ctx *RepStmtContext) interface{}
|
||||
|
||||
|
|
151
netmap/policy.go
151
netmap/policy.go
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
"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.
|
||||
|
@ -28,6 +28,8 @@ type PlacementPolicy struct {
|
|||
selectors []netmap.Selector
|
||||
|
||||
replicas []netmap.Replica
|
||||
|
||||
unique bool
|
||||
}
|
||||
|
||||
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.selectors = m.GetSelectors()
|
||||
p.filters = m.GetFilters()
|
||||
p.unique = m.GetUnique()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -113,6 +116,7 @@ func (p PlacementPolicy) WriteToV2(m *netmap.PlacementPolicy) {
|
|||
m.SetFilters(p.filters)
|
||||
m.SetSelectors(p.selectors)
|
||||
m.SetReplicas(p.replicas)
|
||||
m.SetUnique(p.unique)
|
||||
}
|
||||
|
||||
// ReplicaDescriptor replica descriptor characterizes replicas of objects from
|
||||
|
@ -178,6 +182,14 @@ func (p *PlacementPolicy) SetContainerBackupFactor(f uint32) {
|
|||
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
|
||||
// from the bucket taking the nearest nodes to the related container by hash distance.
|
||||
type Selector struct {
|
||||
|
@ -362,7 +374,14 @@ func (p *PlacementPolicy) AddFilters(fs ...Filter) {
|
|||
// the result into w. Returns w's errors directly.
|
||||
//
|
||||
// See also DecodeString.
|
||||
// nolint: funlen
|
||||
func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) {
|
||||
if p.unique {
|
||||
if _, err := w.WriteString("UNIQUE\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
delim := ""
|
||||
|
||||
for i := range p.replicas {
|
||||
|
@ -452,7 +471,7 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
|
|||
unspecified := op == 0
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -464,6 +483,21 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
|
|||
}
|
||||
|
||||
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 {
|
||||
if i != 0 {
|
||||
_, err = w.WriteString(" " + op.String() + " ")
|
||||
|
@ -471,12 +505,12 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = writeFilterStringTo(w, inner[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s = f.GetName(); s != "" && !unspecified {
|
||||
_, err = w.WriteString(" AS " + s)
|
||||
|
@ -526,6 +560,44 @@ func (p *PlacementPolicy) DecodeString(s string) error {
|
|||
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 (
|
||||
// errUnknownFilter is returned when a value of FROM in a query is unknown.
|
||||
errUnknownFilter = errors.New("filter not found")
|
||||
|
@ -541,22 +613,25 @@ type policyVisitor struct {
|
|||
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))
|
||||
}
|
||||
|
||||
func (p *policyVisitor) reportError(err error) interface{} {
|
||||
func (p *policyVisitor) reportError(err error) any {
|
||||
p.errors = append(p.errors, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pl := new(PlacementPolicy)
|
||||
|
||||
pl.unique = ctx.UNIQUE() != nil
|
||||
|
||||
repStmts := ctx.AllRepStmt()
|
||||
pl.replicas = make([]netmap.Replica, 0, len(repStmts))
|
||||
|
||||
|
@ -599,7 +674,40 @@ func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} {
|
|||
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)
|
||||
if err != nil {
|
||||
return p.reportError(errInvalidNumber)
|
||||
|
@ -609,7 +717,7 @@ func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) 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)
|
||||
if err != nil {
|
||||
return p.reportError(errInvalidNumber)
|
||||
|
@ -626,7 +734,7 @@ func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) 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)
|
||||
if err != nil {
|
||||
return p.reportError(errInvalidNumber)
|
||||
|
@ -652,13 +760,13 @@ func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) 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.SetName(ctx.GetName().GetText())
|
||||
return f
|
||||
}
|
||||
|
||||
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface{} {
|
||||
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) any {
|
||||
if eCtx := ctx.Expr(); eCtx != nil {
|
||||
return eCtx.Accept(p)
|
||||
}
|
||||
|
@ -671,6 +779,12 @@ func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface
|
|||
op := operationFromString(ctx.GetOp().GetText())
|
||||
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)
|
||||
f2 := *ctx.GetF2().Accept(p).(*netmap.Filter)
|
||||
|
||||
|
@ -687,7 +801,7 @@ func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) 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 {
|
||||
return id.GetText()
|
||||
}
|
||||
|
@ -696,7 +810,7 @@ func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{}
|
|||
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 {
|
||||
return id.GetText()
|
||||
}
|
||||
|
@ -710,7 +824,7 @@ func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interfa
|
|||
}
|
||||
|
||||
// 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)
|
||||
if flt := ctx.GetFilter(); flt != nil {
|
||||
f.SetName(flt.GetText())
|
||||
|
@ -773,3 +887,12 @@ func operationFromString(s string) (op netmap.Operation) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -23,6 +23,18 @@ FILTER @FromRU AND Rating GT 7 AS Good`,
|
|||
`REP 7 IN 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`,
|
||||
|
||||
`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
|
||||
|
@ -66,3 +78,28 @@ func TestPlacementPolicyEncoding(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,6 +140,9 @@ func (c *context) getSelectionBase(s netmap.Selector) []nodeAttrPair {
|
|||
attr := s.GetAttribute()
|
||||
|
||||
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 attr == "" {
|
||||
// Default attribute is transparent identifier which is different for every node.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"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) {
|
||||
const name = "some name"
|
||||
var s Selector
|
||||
|
|
85
ns/nns.go
85
ns/nns.go
|
@ -10,6 +10,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
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/encoding/address"
|
||||
"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/invoker"
|
||||
|
@ -18,15 +19,28 @@ import (
|
|||
"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.
|
||||
//
|
||||
// Instances are created with a variable declaration. Before work, the connection
|
||||
// to the NNS server MUST be established using Dial method.
|
||||
type NNS struct {
|
||||
nnsContract util.Uint160
|
||||
client multiSchemeClient
|
||||
|
||||
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,
|
||||
// otherwise HTTP.
|
||||
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
|
||||
|
||||
uri, err := url.Parse(address)
|
||||
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 {
|
||||
return fmt.Errorf("create Neo WebSocket client: %w", err)
|
||||
}
|
||||
} else {
|
||||
multiSchemeClient, err = rpcclient.New(context.Background(), address, rpcclient.Options{})
|
||||
n.client, err = rpcclient.New(context.Background(), address, rpcclient.Options{})
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
nnsContract, err := multiSchemeClient.GetContractStateByID(1)
|
||||
nnsContract, err := n.client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
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
|
||||
// by calling `resolve` method of NNS contract. Returns the first record which represents
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ type testNeoClient struct {
|
|||
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
|
||||
|
||||
require.Equal(x.t, x.expectedContract, contract)
|
||||
|
@ -49,7 +49,7 @@ type brokenArrayStackItem struct {
|
|||
stackitem.Item
|
||||
}
|
||||
|
||||
func (x brokenArrayStackItem) Value() interface{} {
|
||||
func (x brokenArrayStackItem) Value() any {
|
||||
return 1
|
||||
}
|
||||
|
||||
|
@ -154,3 +154,86 @@ func TestNNS_ResolveContainerDomain(t *testing.T) {
|
|||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -169,3 +169,11 @@ func (x *Address) DecodeString(s string) error {
|
|||
func (x Address) String() string {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -220,16 +220,6 @@ func TestObject_SetSessionToken(t *testing.T) {
|
|||
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) {
|
||||
o1 := New()
|
||||
|
||||
|
|
|
@ -1,44 +1,28 @@
|
|||
package transformer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||
)
|
||||
|
||||
type chanTarget struct {
|
||||
header *objectSDK.Object
|
||||
payload []byte
|
||||
ch chan<- *objectSDK.Object
|
||||
}
|
||||
|
||||
// NewChannelTarget returns ObjectTarget which writes
|
||||
// object parts to a provided channel.
|
||||
func NewChannelTarget(ch chan<- *objectSDK.Object) ObjectTarget {
|
||||
func NewChannelTarget(ch chan<- *objectSDK.Object) ObjectWriter {
|
||||
return &chanTarget{
|
||||
ch: ch,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader implements the ObjectTarget interface.
|
||||
func (c *chanTarget) WriteHeader(object *objectSDK.Object) error {
|
||||
c.header = object
|
||||
func (c *chanTarget) WriteObject(ctx context.Context, o *objectSDK.Object) error {
|
||||
select {
|
||||
case c.ch <- o:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package transformer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
|
@ -15,9 +16,10 @@ func TestChannelTarget(t *testing.T) {
|
|||
|
||||
ch := make(chan *objectSDK.Object, 10)
|
||||
tt := new(testTarget)
|
||||
ct := NewChannelTarget(ch)
|
||||
|
||||
chTarget, _ := newPayloadSizeLimiter(maxSize, NewChannelTarget(ch))
|
||||
testTarget, _ := newPayloadSizeLimiter(maxSize, tt)
|
||||
chTarget, _ := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return ct })
|
||||
testTarget, _ := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return tt })
|
||||
|
||||
ver := version.Current()
|
||||
cnr := cidtest.ID()
|
||||
|
@ -29,8 +31,9 @@ func TestChannelTarget(t *testing.T) {
|
|||
payload := make([]byte, maxSize*2+maxSize/2)
|
||||
_, _ = rand.Read(payload)
|
||||
|
||||
expectedIDs := writeObject(t, testTarget, hdr, payload)
|
||||
actualIDs := writeObject(t, chTarget, hdr, payload)
|
||||
ctx := context.Background()
|
||||
expectedIDs := writeObject(t, ctx, testTarget, hdr, payload)
|
||||
actualIDs := writeObject(t, ctx, chTarget, hdr, payload)
|
||||
_ = expectedIDs
|
||||
_ = actualIDs
|
||||
//require.Equal(t, expectedIDs, actualIDs)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package transformer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
@ -19,6 +20,7 @@ type payloadSizeLimiter struct {
|
|||
written, writtenCurrent uint64
|
||||
|
||||
current, parent *object.Object
|
||||
payload []byte
|
||||
|
||||
currentHashers, parentHashers []payloadChecksumHasher
|
||||
|
||||
|
@ -27,11 +29,13 @@ type payloadSizeLimiter struct {
|
|||
splitID *object.SplitID
|
||||
|
||||
parAttrs []object.Attribute
|
||||
|
||||
nextTarget ObjectWriter
|
||||
}
|
||||
|
||||
type Params struct {
|
||||
Key *ecdsa.PrivateKey
|
||||
NextTarget ObjectTarget
|
||||
NextTargetInit TargetInitializer
|
||||
SessionToken *session.Object
|
||||
NetworkState EpochSource
|
||||
MaxSize uint64
|
||||
|
@ -45,14 +49,14 @@ type Params struct {
|
|||
// is false.
|
||||
//
|
||||
// Objects w/ payload size less or equal than max size remain untouched.
|
||||
func NewPayloadSizeLimiter(p Params) ObjectTarget {
|
||||
func NewPayloadSizeLimiter(p Params) ChunkedObjectWriter {
|
||||
return &payloadSizeLimiter{
|
||||
Params: p,
|
||||
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.initialize()
|
||||
|
@ -60,16 +64,16 @@ func (s *payloadSizeLimiter) WriteHeader(hdr *object.Object) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *payloadSizeLimiter) Write(p []byte) (int, error) {
|
||||
if err := s.writeChunk(p); err != nil {
|
||||
func (s *payloadSizeLimiter) Write(ctx context.Context, p []byte) (int, error) {
|
||||
if err := s.writeChunk(ctx, p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (s *payloadSizeLimiter) Close() (*AccessIdentifiers, error) {
|
||||
return s.release(true)
|
||||
func (s *payloadSizeLimiter) Close(ctx context.Context) (*AccessIdentifiers, error) {
|
||||
return s.release(ctx, true)
|
||||
}
|
||||
|
||||
func (s *payloadSizeLimiter) initialize() {
|
||||
|
@ -79,11 +83,13 @@ func (s *payloadSizeLimiter) initialize() {
|
|||
if ln := len(s.previous); ln > 0 {
|
||||
// initialize parent object once (after 1st object)
|
||||
if ln == 1 {
|
||||
ver := version.Current()
|
||||
s.parent = fromObject(s.current)
|
||||
s.parentHashers = append(s.parentHashers[:0], s.currentHashers...)
|
||||
|
||||
// return source attributes
|
||||
s.parent.ResetRelations()
|
||||
s.parent.SetSignature(nil)
|
||||
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
|
||||
|
@ -112,9 +118,10 @@ func fromObject(obj *object.Object) *object.Object {
|
|||
}
|
||||
|
||||
func (s *payloadSizeLimiter) initializeCurrent() {
|
||||
// create payload hashers
|
||||
s.nextTarget = s.NextTargetInit()
|
||||
s.writtenCurrent = 0
|
||||
s.initPayloadHashers()
|
||||
s.payload = make([]byte, 0)
|
||||
}
|
||||
|
||||
func (s *payloadSizeLimiter) initPayloadHashers() {
|
||||
|
@ -131,8 +138,7 @@ func (s *payloadSizeLimiter) initPayloadHashers() {
|
|||
}
|
||||
}
|
||||
|
||||
// nolint: funlen
|
||||
func (s *payloadSizeLimiter) release(finalize bool) (*AccessIdentifiers, error) {
|
||||
func (s *payloadSizeLimiter) release(ctx context.Context, finalize bool) (*AccessIdentifiers, error) {
|
||||
// Arg finalize is true only when called from Close method.
|
||||
// We finalize parent and generate linking objects only if it is more
|
||||
// than 1 object in split-chain.
|
||||
|
@ -151,6 +157,33 @@ func (s *payloadSizeLimiter) release(finalize bool) (*AccessIdentifiers, error)
|
|||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
ids := &AccessIdentifiers{
|
||||
return &AccessIdentifiers{
|
||||
ParentID: parID,
|
||||
SelfID: id,
|
||||
ParentHeader: parHdr,
|
||||
}
|
||||
|
||||
// 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
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *payloadSizeLimiter) initializeLinking(parHdr *object.Object) {
|
||||
|
@ -224,7 +233,7 @@ func (s *payloadSizeLimiter) initializeLinking(parHdr *object.Object) {
|
|||
s.current.SetSplitID(s.splitID)
|
||||
}
|
||||
|
||||
func (s *payloadSizeLimiter) writeChunk(chunk []byte) error {
|
||||
func (s *payloadSizeLimiter) writeChunk(ctx context.Context, chunk []byte) error {
|
||||
for {
|
||||
// statement is true if the previous write of bytes reached exactly the boundary.
|
||||
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
|
||||
if _, err := s.release(false); err != nil {
|
||||
if _, err := s.release(ctx, false); err != nil {
|
||||
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 {
|
||||
_, err := s.NextTarget.Write(chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.payload = append(s.payload, chunk...)
|
||||
|
||||
// The `Write` method of `hash.Hash` never returns an error.
|
||||
for i := range s.currentHashers {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package transformer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
@ -19,7 +20,7 @@ func TestTransformer(t *testing.T) {
|
|||
|
||||
tt := new(testTarget)
|
||||
|
||||
target, pk := newPayloadSizeLimiter(maxSize, tt)
|
||||
target, pk := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return tt })
|
||||
|
||||
cnr := cidtest.ID()
|
||||
hdr := newObject(cnr)
|
||||
|
@ -31,7 +32,7 @@ func TestTransformer(t *testing.T) {
|
|||
expectedPayload := make([]byte, maxSize*2+maxSize/2)
|
||||
_, _ = 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
|
||||
|
||||
var actualPayload []byte
|
||||
|
@ -54,15 +55,28 @@ func TestTransformer(t *testing.T) {
|
|||
require.Equal(t, h[:], cs.Value())
|
||||
}
|
||||
|
||||
require.True(t, tt.objects[i].VerifyIDSignature())
|
||||
switch i {
|
||||
case 0, 1:
|
||||
require.EqualValues(t, maxSize, len(payload))
|
||||
require.Nil(t, tt.objects[i].Parent())
|
||||
case 2:
|
||||
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:
|
||||
parID, ok := tt.objects[i].ParentID()
|
||||
require.True(t, ok)
|
||||
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)
|
||||
|
@ -85,13 +99,13 @@ func newObject(cnr cid.ID) *objectSDK.Object {
|
|||
return hdr
|
||||
}
|
||||
|
||||
func writeObject(t *testing.T, target ObjectTarget, header *objectSDK.Object, payload []byte) *AccessIdentifiers {
|
||||
require.NoError(t, target.WriteHeader(header))
|
||||
func writeObject(t *testing.T, ctx context.Context, target ChunkedObjectWriter, header *objectSDK.Object, payload []byte) *AccessIdentifiers {
|
||||
require.NoError(t, target.WriteHeader(ctx, header))
|
||||
|
||||
_, err := target.Write(payload)
|
||||
_, err := target.Write(ctx, payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
ids, err := target.Close()
|
||||
ids, err := target.Close(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
return ids
|
||||
|
@ -112,24 +126,25 @@ func benchmarkTransformer(b *testing.B, header *objectSDK.Object, payloadSize in
|
|||
const maxSize = 64 * 1024 * 1024
|
||||
|
||||
payload := make([]byte, payloadSize)
|
||||
ctx := context.Background()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
f, _ := newPayloadSizeLimiter(maxSize, benchTarget{})
|
||||
if err := f.WriteHeader(header); err != nil {
|
||||
f, _ := newPayloadSizeLimiter(maxSize, func() ObjectWriter { return benchTarget{} })
|
||||
if err := f.WriteHeader(ctx, header); err != nil {
|
||||
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)
|
||||
}
|
||||
if _, err := f.Close(); err != nil {
|
||||
if _, err := f.Close(ctx); err != nil {
|
||||
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()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -137,7 +152,7 @@ func newPayloadSizeLimiter(maxSize uint64, nextTarget ObjectTarget) (ObjectTarge
|
|||
|
||||
return NewPayloadSizeLimiter(Params{
|
||||
Key: &p.PrivateKey,
|
||||
NextTarget: nextTarget,
|
||||
NextTargetInit: nextTarget,
|
||||
NetworkState: dummyEpochSource(123),
|
||||
MaxSize: maxSize,
|
||||
WithoutHomomorphicHash: true,
|
||||
|
@ -152,38 +167,15 @@ func (s dummyEpochSource) CurrentEpoch() uint64 {
|
|||
|
||||
type benchTarget struct{}
|
||||
|
||||
func (benchTarget) WriteHeader(object *objectSDK.Object) error {
|
||||
func (benchTarget) WriteObject(context.Context, *objectSDK.Object) error {
|
||||
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 {
|
||||
current *objectSDK.Object
|
||||
payload []byte
|
||||
objects []*objectSDK.Object
|
||||
}
|
||||
|
||||
func (tt *testTarget) WriteHeader(object *objectSDK.Object) error {
|
||||
tt.current = object
|
||||
return nil
|
||||
}
|
||||
|
||||
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.
|
||||
func (tt *testTarget) WriteObject(_ context.Context, o *objectSDK.Object) error {
|
||||
tt.objects = append(tt.objects, o)
|
||||
return nil // AccessIdentifiers should not be used.
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package transformer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"context"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
|
@ -21,8 +21,9 @@ type EpochSource interface {
|
|||
CurrentEpoch() uint64
|
||||
}
|
||||
|
||||
// ObjectTarget is an interface of the object writer.
|
||||
type ObjectTarget interface {
|
||||
// ChunkedObjectWriter is an interface of the object writer
|
||||
// that writes object chunked.
|
||||
type ChunkedObjectWriter interface {
|
||||
// WriteHeader writes object header w/ payload part.
|
||||
// The payload of the object may be incomplete.
|
||||
//
|
||||
|
@ -31,14 +32,14 @@ type ObjectTarget interface {
|
|||
// that depends on the implementation.
|
||||
//
|
||||
// Must not be called after Close call.
|
||||
WriteHeader(*object.Object) error
|
||||
WriteHeader(context.Context, *object.Object) error
|
||||
|
||||
// Write writes object payload chunk.
|
||||
//
|
||||
// Can be called multiple times.
|
||||
//
|
||||
// Must not be called after Close call.
|
||||
io.Writer
|
||||
Write(context.Context, []byte) (int, error)
|
||||
|
||||
// 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.
|
||||
// Re-calling can lead to undefined behavior
|
||||
// that depends on the implementation.
|
||||
Close() (*AccessIdentifiers, error)
|
||||
Close(context.Context) (*AccessIdentifiers, error)
|
||||
}
|
||||
|
||||
// TargetInitializer represents ObjectTarget constructor.
|
||||
type TargetInitializer func() ObjectTarget
|
||||
// TargetInitializer represents ObjectWriter constructor.
|
||||
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
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ type Type object.Type
|
|||
const (
|
||||
TypeRegular Type = iota
|
||||
TypeTombstone
|
||||
TypeStorageGroup
|
||||
_
|
||||
TypeLock
|
||||
)
|
||||
|
||||
|
@ -25,7 +25,6 @@ func TypeFromV2(t object.Type) Type {
|
|||
//
|
||||
// String mapping:
|
||||
// - TypeTombstone: TOMBSTONE;
|
||||
// - TypeStorageGroup: STORAGE_GROUP;
|
||||
// - TypeLock: LOCK;
|
||||
// - TypeRegular, default: REGULAR.
|
||||
func (t Type) String() string {
|
||||
|
|
|
@ -21,10 +21,6 @@ func TestType_ToV2(t *testing.T) {
|
|||
t: object.TypeTombstone,
|
||||
t2: v2object.TypeTombstone,
|
||||
},
|
||||
{
|
||||
t: object.TypeStorageGroup,
|
||||
t2: v2object.TypeStorageGroup,
|
||||
},
|
||||
{
|
||||
t: object.TypeLock,
|
||||
t2: v2object.TypeLock,
|
||||
|
@ -47,7 +43,6 @@ func TestType_String(t *testing.T) {
|
|||
|
||||
testEnumStrings(t, new(object.Type), []enumStringItem{
|
||||
{val: toPtr(object.TypeTombstone), str: "TOMBSTONE"},
|
||||
{val: toPtr(object.TypeStorageGroup), str: "STORAGE_GROUP"},
|
||||
{val: toPtr(object.TypeRegular), str: "REGULAR"},
|
||||
{val: toPtr(object.TypeLock), str: "LOCK"},
|
||||
})
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
type sessionCache struct {
|
||||
cache *lru.Cache[string, *cacheValue]
|
||||
currentEpoch uint64
|
||||
currentEpoch atomic.Uint64
|
||||
}
|
||||
|
||||
type cacheValue struct {
|
||||
|
@ -58,14 +58,14 @@ func (c *sessionCache) DeleteByPrefix(prefix string) {
|
|||
}
|
||||
|
||||
func (c *sessionCache) updateEpoch(newEpoch uint64) {
|
||||
epoch := atomic.LoadUint64(&c.currentEpoch)
|
||||
epoch := c.currentEpoch.Load()
|
||||
if newEpoch > epoch {
|
||||
atomic.StoreUint64(&c.currentEpoch, newEpoch)
|
||||
c.currentEpoch.Store(newEpoch)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
return val.token.ExpiredAt(epoch + 1)
|
||||
}
|
||||
|
|
187
pool/pool.go
187
pool/pool.go
|
@ -11,6 +11,7 @@ import (
|
|||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
|
@ -29,9 +30,9 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// 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()}
|
||||
}
|
||||
|
||||
healthy := new(atomic.Bool)
|
||||
healthy.Store(true)
|
||||
|
||||
return clientStatusMonitor{
|
||||
logger: logger,
|
||||
addr: addr,
|
||||
healthy: atomic.NewBool(true),
|
||||
healthy: healthy,
|
||||
errorThreshold: errorThreshold,
|
||||
methods: methods,
|
||||
}
|
||||
|
@ -235,6 +239,7 @@ type wrapperPrm struct {
|
|||
errorThreshold uint32
|
||||
responseInfoCallback func(sdkClient.ResponseMetaInfo) error
|
||||
poolRequestInfoCallback func(RequestInfo)
|
||||
dialOptions []grpc.DialOption
|
||||
}
|
||||
|
||||
// setAddress sets endpoint to connect in FrostFS network.
|
||||
|
@ -273,6 +278,11 @@ func (x *wrapperPrm) setResponseInfoCallback(f func(sdkClient.ResponseMetaInfo)
|
|||
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.
|
||||
func newWrapper(prm wrapperPrm) *clientWrapper {
|
||||
var cl sdkClient.Client
|
||||
|
@ -304,6 +314,7 @@ func (c *clientWrapper) dial(ctx context.Context) error {
|
|||
prmDial.SetServerURI(c.prm.address)
|
||||
prmDial.SetTimeout(c.prm.dialTimeout)
|
||||
prmDial.SetStreamTimeout(c.prm.streamTimeout)
|
||||
prmDial.SetGRPCDialOptions(c.prm.dialOptions...)
|
||||
|
||||
if err = cl.Dial(ctx, prmDial); err != nil {
|
||||
c.setUnhealthy()
|
||||
|
@ -334,6 +345,7 @@ func (c *clientWrapper) restartIfUnhealthy(ctx context.Context) (healthy, change
|
|||
prmDial.SetServerURI(c.prm.address)
|
||||
prmDial.SetTimeout(c.prm.dialTimeout)
|
||||
prmDial.SetStreamTimeout(c.prm.streamTimeout)
|
||||
prmDial.SetGRPCDialOptions(c.prm.dialOptions...)
|
||||
|
||||
if err := cl.Dial(ctx, prmDial); err != nil {
|
||||
c.setUnhealthy()
|
||||
|
@ -395,7 +407,7 @@ func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) (
|
|||
}
|
||||
|
||||
start := time.Now()
|
||||
res, err := cl.ContainerPut(ctx, prm.prmClient)
|
||||
res, err := cl.ContainerPut(ctx, prm.ClientParams)
|
||||
c.incRequests(time.Since(start), methodContainerPut)
|
||||
var st apistatus.Status
|
||||
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)
|
||||
}
|
||||
|
||||
if !prm.waitParamsSet {
|
||||
prm.waitParams.setDefaults()
|
||||
if prm.WaitParams == nil {
|
||||
prm.WaitParams = defaultWaitParams()
|
||||
}
|
||||
if err = prm.WaitParams.CheckValidity(); err != nil {
|
||||
return cid.ID{}, fmt.Errorf("invalid wait parameters: %w", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
var cliPrm sdkClient.PrmContainerGet
|
||||
cliPrm.SetContainer(prm.cnrID)
|
||||
cliPrm := sdkClient.PrmContainerGet{
|
||||
ContainerID: &prm.ContainerID,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
res, err := cl.ContainerGet(ctx, cliPrm)
|
||||
|
@ -474,10 +490,9 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
|
|||
return err
|
||||
}
|
||||
|
||||
var cliPrm sdkClient.PrmContainerDelete
|
||||
cliPrm.SetContainer(prm.cnrID)
|
||||
if prm.stokenSet {
|
||||
cliPrm.WithinSession(prm.stoken)
|
||||
cliPrm := sdkClient.PrmContainerDelete{
|
||||
ContainerID: &prm.ContainerID,
|
||||
Session: prm.Session,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if !prm.waitParamsSet {
|
||||
prm.waitParams.setDefaults()
|
||||
if prm.WaitParams == nil {
|
||||
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.
|
||||
|
@ -615,7 +633,7 @@ func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (oid.ID
|
|||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
if wObj.WriteHeader(prm.hdr) {
|
||||
if wObj.WriteHeader(ctx, prm.hdr) {
|
||||
sz := prm.hdr.PayloadSize()
|
||||
|
||||
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)
|
||||
if n > 0 {
|
||||
start = time.Now()
|
||||
successWrite := wObj.WritePayloadChunk(buf[:n])
|
||||
successWrite := wObj.WritePayloadChunk(ctx, buf[:n])
|
||||
c.incRequests(time.Since(start), methodObjectPut)
|
||||
if !successWrite {
|
||||
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
|
||||
if res != nil {
|
||||
st = res.Status()
|
||||
|
@ -1002,10 +1020,10 @@ func (c *clientStatusMonitor) handleError(ctx context.Context, st apistatus.Stat
|
|||
|
||||
err = apistatus.ErrFromStatus(st)
|
||||
switch err.(type) {
|
||||
case apistatus.ServerInternal, *apistatus.ServerInternal,
|
||||
apistatus.WrongMagicNumber, *apistatus.WrongMagicNumber,
|
||||
apistatus.SignatureVerification, *apistatus.SignatureVerification,
|
||||
apistatus.NodeUnderMaintenance, *apistatus.NodeUnderMaintenance:
|
||||
case *apistatus.ServerInternal,
|
||||
*apistatus.WrongMagicNumber,
|
||||
*apistatus.SignatureVerification,
|
||||
*apistatus.NodeUnderMaintenance:
|
||||
c.incErrorRate()
|
||||
}
|
||||
|
||||
|
@ -1051,6 +1069,7 @@ type InitParameters struct {
|
|||
errorThreshold uint32
|
||||
nodeParams []NodeParam
|
||||
requestCallback func(RequestInfo)
|
||||
dialOptions []grpc.DialOption
|
||||
|
||||
clientBuilder clientBuilder
|
||||
}
|
||||
|
@ -1110,6 +1129,11 @@ func (x *InitParameters) AddNode(nodeParam 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.
|
||||
// Wraps setClientBuilderContext without a context.
|
||||
func (x *InitParameters) setClientBuilder(builder clientBuilder) {
|
||||
|
@ -1157,44 +1181,82 @@ func (x *NodeParam) SetPriority(priority int) {
|
|||
x.priority = priority
|
||||
}
|
||||
|
||||
// Priority returns priority of the node.
|
||||
func (x *NodeParam) Priority() int {
|
||||
return x.priority
|
||||
}
|
||||
|
||||
// SetAddress specifies address of the node.
|
||||
func (x *NodeParam) SetAddress(address string) {
|
||||
x.address = address
|
||||
}
|
||||
|
||||
// Address returns address of the node.
|
||||
func (x *NodeParam) Address() string {
|
||||
return x.address
|
||||
}
|
||||
|
||||
// SetWeight specifies weight of the node.
|
||||
func (x *NodeParam) SetWeight(weight float64) {
|
||||
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.
|
||||
type WaitParams struct {
|
||||
timeout time.Duration
|
||||
pollInterval time.Duration
|
||||
Timeout time.Duration
|
||||
PollInterval time.Duration
|
||||
}
|
||||
|
||||
// SetTimeout specifies the time to wait for the operation to complete.
|
||||
//
|
||||
// Deprecated: Use WaitParams.Timeout instead.
|
||||
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.
|
||||
//
|
||||
// Deprecated: Use WaitParams.PollInterval instead.
|
||||
func (x *WaitParams) SetPollInterval(tick time.Duration) {
|
||||
x.pollInterval = tick
|
||||
x.PollInterval = tick
|
||||
}
|
||||
|
||||
// Deprecated: Use defaultWaitParams() instead.
|
||||
func (x *WaitParams) setDefaults() {
|
||||
x.timeout = 120 * time.Second
|
||||
x.pollInterval = 5 * time.Second
|
||||
x.Timeout = 120 * 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.
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
defaultSession bool
|
||||
verb session.ObjectVerb
|
||||
|
@ -1257,7 +1319,7 @@ type PrmObjectPut struct {
|
|||
|
||||
payload io.Reader
|
||||
|
||||
copiesNumber uint32
|
||||
copiesNumber []uint32
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Zero means using default behavior.
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1361,45 +1429,51 @@ func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) {
|
|||
|
||||
// PrmContainerPut groups parameters of PutContainer operation.
|
||||
type PrmContainerPut struct {
|
||||
prmClient sdkClient.PrmContainerPut
|
||||
ClientParams sdkClient.PrmContainerPut
|
||||
|
||||
waitParams WaitParams
|
||||
waitParamsSet bool
|
||||
WaitParams *WaitParams
|
||||
}
|
||||
|
||||
// SetContainer container structure to be used as a parameter of the base
|
||||
// client's operation.
|
||||
//
|
||||
// See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.SetContainer.
|
||||
//
|
||||
// Deprecated: Use PrmContainerPut.ClientParams.Container instead.
|
||||
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
|
||||
// client's operation.
|
||||
//
|
||||
// See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.WithinSession.
|
||||
//
|
||||
// Deprecated: Use PrmContainerPut.ClientParams.Session instead.
|
||||
func (x *PrmContainerPut) WithinSession(s session.Container) {
|
||||
x.prmClient.WithinSession(s)
|
||||
x.ClientParams.WithinSession(s)
|
||||
}
|
||||
|
||||
// SetWaitParams specifies timeout params to complete operation.
|
||||
// If not provided the default one will be used.
|
||||
// Panics if any of the wait params isn't positive.
|
||||
//
|
||||
// Deprecated: Use PrmContainerPut.ClientParams.WaitParams instead.
|
||||
func (x *PrmContainerPut) SetWaitParams(waitParams WaitParams) {
|
||||
waitParams.checkForPositive()
|
||||
x.waitParams = waitParams
|
||||
x.waitParamsSet = true
|
||||
x.WaitParams = &waitParams
|
||||
}
|
||||
|
||||
// PrmContainerGet groups parameters of GetContainer operation.
|
||||
type PrmContainerGet struct {
|
||||
cnrID cid.ID
|
||||
ContainerID cid.ID
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -1414,33 +1488,35 @@ func (x *PrmContainerList) SetOwnerID(ownerID user.ID) {
|
|||
|
||||
// PrmContainerDelete groups parameters of DeleteContainer operation.
|
||||
type PrmContainerDelete struct {
|
||||
cnrID cid.ID
|
||||
ContainerID cid.ID
|
||||
|
||||
stoken session.Container
|
||||
stokenSet bool
|
||||
Session *session.Container
|
||||
|
||||
waitParams WaitParams
|
||||
waitParamsSet bool
|
||||
WaitParams *WaitParams
|
||||
}
|
||||
|
||||
// SetContainerID specifies identifier of the FrostFS container to be removed.
|
||||
//
|
||||
// Deprecated: Use PrmContainerDelete.ContainerID instead.
|
||||
func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) {
|
||||
x.cnrID = cnrID
|
||||
x.ContainerID = cnrID
|
||||
}
|
||||
|
||||
// SetSessionToken specifies session within which operation should be performed.
|
||||
//
|
||||
// Deprecated: Use PrmContainerDelete.Session instead.
|
||||
func (x *PrmContainerDelete) SetSessionToken(token session.Container) {
|
||||
x.stoken = token
|
||||
x.stokenSet = true
|
||||
x.Session = &token
|
||||
}
|
||||
|
||||
// SetWaitParams specifies timeout params to complete operation.
|
||||
// If not provided the default one will be used.
|
||||
// Panics if any of the wait params isn't positive.
|
||||
//
|
||||
// Deprecated: Use PrmContainerDelete.WaitParams instead.
|
||||
func (x *PrmContainerDelete) SetWaitParams(waitParams WaitParams) {
|
||||
waitParams.checkForPositive()
|
||||
x.waitParams = waitParams
|
||||
x.waitParamsSet = true
|
||||
x.WaitParams = &waitParams
|
||||
}
|
||||
|
||||
// PrmContainerEACL groups parameters of GetEACL operation.
|
||||
|
@ -1711,6 +1787,7 @@ func fillDefaultInitParams(params *InitParameters, cache *sessionCache) {
|
|||
prm.setStreamTimeout(params.nodeStreamTimeout)
|
||||
prm.setErrorThreshold(params.errorThreshold)
|
||||
prm.setPoolRequestCallback(params.requestCallback)
|
||||
prm.setGRPCDialOptions(params.dialOptions)
|
||||
prm.setResponseInfoCallback(func(info sdkClient.ResponseMetaInfo) error {
|
||||
cache.updateEpoch(info.Epoch())
|
||||
return nil
|
||||
|
@ -1790,7 +1867,7 @@ func (p *Pool) updateInnerNodesHealth(ctx context.Context, i int, bufferWeights
|
|||
pool := p.innerPools[i]
|
||||
options := p.rebalanceParams
|
||||
|
||||
healthyChanged := atomic.NewBool(false)
|
||||
healthyChanged := new(atomic.Bool)
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
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.
|
||||
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()
|
||||
ticker := time.NewTimer(params.pollInterval)
|
||||
ticker := time.NewTimer(params.PollInterval)
|
||||
defer ticker.Stop()
|
||||
wdone := wctx.Done()
|
||||
done := ctx.Done()
|
||||
|
@ -2480,7 +2557,7 @@ func waitFor(ctx context.Context, params *WaitParams, condition func(context.Con
|
|||
if condition(ctx) {
|
||||
return nil
|
||||
}
|
||||
ticker.Reset(params.pollInterval)
|
||||
ticker.Reset(params.PollInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ func TestSessionCache(t *testing.T) {
|
|||
|
||||
mockClientBuilder := func(addr string) client {
|
||||
mockCli := newMockClient(addr, *key)
|
||||
mockCli.statusOnGetObject(apistatus.SessionTokenNotFound{})
|
||||
mockCli.statusOnGetObject(new(apistatus.SessionTokenNotFound))
|
||||
return mockCli
|
||||
}
|
||||
|
||||
|
@ -483,8 +483,8 @@ func TestWaitPresence(t *testing.T) {
|
|||
var idCnr cid.ID
|
||||
|
||||
err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{
|
||||
timeout: 120 * time.Second,
|
||||
pollInterval: 5 * time.Second,
|
||||
Timeout: 120 * time.Second,
|
||||
PollInterval: 5 * time.Second,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "context canceled")
|
||||
|
@ -494,8 +494,8 @@ func TestWaitPresence(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
var idCnr cid.ID
|
||||
err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{
|
||||
timeout: 500 * time.Millisecond,
|
||||
pollInterval: 5 * time.Second,
|
||||
Timeout: 500 * time.Millisecond,
|
||||
PollInterval: 5 * time.Second,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "context deadline exceeded")
|
||||
|
@ -505,8 +505,8 @@ func TestWaitPresence(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
var idCnr cid.ID
|
||||
err := waitForContainerPresence(ctx, mockCli, idCnr, &WaitParams{
|
||||
timeout: 10 * time.Second,
|
||||
pollInterval: 500 * time.Millisecond,
|
||||
Timeout: 10 * time.Second,
|
||||
PollInterval: 500 * time.Millisecond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
@ -548,14 +548,14 @@ func TestHandleError(t *testing.T) {
|
|||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.SuccessDefaultV2{},
|
||||
status: new(apistatus.SuccessDefaultV2),
|
||||
err: nil,
|
||||
expectedError: false,
|
||||
countError: false,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.SuccessDefaultV2{},
|
||||
status: new(apistatus.SuccessDefaultV2),
|
||||
err: errors.New("error"),
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
|
@ -569,42 +569,42 @@ func TestHandleError(t *testing.T) {
|
|||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.ObjectNotFound{},
|
||||
status: new(apistatus.ObjectNotFound),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: false,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.ServerInternal{},
|
||||
status: new(apistatus.ServerInternal),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.WrongMagicNumber{},
|
||||
status: new(apistatus.WrongMagicNumber),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.SignatureVerification{},
|
||||
status: new(apistatus.SignatureVerification),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: &apistatus.SignatureVerification{},
|
||||
status: new(apistatus.SignatureVerification),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: apistatus.NodeUnderMaintenance{},
|
||||
status: new(apistatus.NodeUnderMaintenance),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
|
@ -649,7 +649,7 @@ func TestSwitchAfterErrorThreshold(t *testing.T) {
|
|||
if addr == nodes[0].address {
|
||||
mockCli := newMockClient(addr, *key)
|
||||
mockCli.setThreshold(uint32(errorThreshold))
|
||||
mockCli.statusOnGetObject(apistatus.ServerInternal{})
|
||||
mockCli.statusOnGetObject(new(apistatus.ServerInternal))
|
||||
return mockCli
|
||||
}
|
||||
|
||||
|
|
136
pool/tree/client.go
Normal file
136
pool/tree/client.go
Normal 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
755
pool/tree/pool.go
Normal 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))
|
||||
}
|
25
pool/tree/pool_signature.go
Normal file
25
pool/tree/pool_signature.go
Normal 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
301
pool/tree/pool_test.go
Normal 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
|
||||
}
|
3585
pool/tree/service/service.pb.go
Normal file
3585
pool/tree/service/service.pb.go
Normal file
File diff suppressed because it is too large
Load diff
520
pool/tree/service/service_grpc.pb.go
Normal file
520
pool/tree/service/service_grpc.pb.go
Normal 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",
|
||||
}
|
320
pool/tree/service/types.pb.go
Normal file
320
pool/tree/service/types.pb.go
Normal 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
|
||||
}
|
|
@ -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
|
|
@ -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...)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
19
syncTree.sh
Executable 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
|
Loading…
Reference in a new issue