forked from TrueCloudLab/frostfs-node
Compare commits
255 commits
feat/strea
...
master
Author | SHA1 | Date | |
---|---|---|---|
64c1392513 | |||
dcfd895449 | |||
6730e27ae7 | |||
f7779adf71 | |||
f93b96c601 | |||
aed84b567c | |||
fe29ed043a | |||
4f9d237042 | |||
dfdeedfc6f | |||
2394ae6ce0 | |||
c274bbeb7c | |||
faec499b38 | |||
46fd5e17b2 | |||
17cba3387e | |||
766d9ec46b | |||
0e1b01b15f | |||
4c03561aa2 | |||
f4696e8964 | |||
b0ef737a74 | |||
6f7b6b65f3 | |||
923f0acf8f | |||
9b5c1da40f | |||
b4b053cecd | |||
0c5d74729c | |||
c4f941a5f5 | |||
d933609084 | |||
3d771aa21c | |||
52367dc9b2 | |||
979d4bb2ae | |||
fbc623f34e | |||
5350632e01 | |||
2938498b52 | |||
272128e61f | |||
50dccff7c1 | |||
634de97509 | |||
2a6cdbdb72 | |||
11493d587b | |||
b924ecb850 | |||
e142d25fac | |||
bd1c18e117 | |||
b27f7d1d17 | |||
3cd8080232 | |||
a11b54ca15 | |||
b112a92408 | |||
19ca907223 | |||
f62d81e26a | |||
27899598dc | |||
bc6cc9ae2a | |||
6e1576cfdb | |||
a5bae6c5af | |||
5a13830a94 | |||
dcb2b23a7d | |||
115aae7c34 | |||
12a0537a7a | |||
30d4692c3e | |||
2254c8aff5 | |||
d432bebef4 | |||
d144abc977 | |||
a2053870e2 | |||
d00c606fee | |||
60446bb668 | |||
bd8ab2d84a | |||
bce2f7bef0 | |||
c2c05e2228 | |||
0a38571a10 | |||
632bd8e38d | |||
3bbee1b554 | |||
9358938222 | |||
5470b205fd | |||
163e2e9f83 | |||
0c664fa804 | |||
0a9d139e20 | |||
3bb1fb744a | |||
ccdd6cb767 | |||
73e35bc885 | |||
eed0824590 | |||
a4da1da767 | |||
30099194ba | |||
e7e91ef634 | |||
4919b6a206 | |||
d951289131 | |||
016f2e11e3 | |||
9aa486c9d8 | |||
af76350bfb | |||
3fa5c22ddf | |||
5385f9994f | |||
eea46a599d | |||
049a650b89 | |||
3f4717a37f | |||
60cea8c714 | |||
7df2912a83 | |||
affab25512 | |||
45b7796151 | |||
e8801dbf49 | |||
eb9df85b98 | |||
21bed3362c | |||
af5b3575d0 | |||
a49f0717b3 | |||
a7ac30da9c | |||
39f549a7ab | |||
760b6a44ea | |||
a11b2d27e4 | |||
a405fb1f39 | |||
a7319bc979 | |||
fc743cc537 | |||
54ef71a92f | |||
91c7b39232 | |||
ef6ac751df | |||
fde2649e60 | |||
07a660fbc4 | |||
7893d763d1 | |||
ff4e9b6ae1 | |||
997759994a | |||
ecb6b0793c | |||
460e5cbccf | |||
155d3ddb6e | |||
40536d8a06 | |||
d66bffb191 | |||
bcc84c85a0 | |||
737788b35f | |||
2005fdda09 | |||
597bce7a87 | |||
4ed2bbdb0f | |||
3727d60331 | |||
d36afa31c7 | |||
8643e0abc5 | |||
bd61f7bf0a | |||
df6d9da82a | |||
aab8addae0 | |||
9e31cb249f | |||
6260d703ce | |||
a17c3356fa | |||
471aeeaff3 | |||
4c8f9580a1 | |||
bf8914fedc | |||
5ba0e2918e | |||
4685afb1dc | |||
eb8b9b2b3b | |||
6c6e463b73 | |||
401d96a89e | |||
8ed71a969e | |||
c2d855aedd | |||
2162f8e189 | |||
b9360be1dc | |||
ceff5e1f6a | |||
e0dc3c3d0c | |||
92a67a6716 | |||
98d6125029 | |||
0991077cb3 | |||
c660271039 | |||
92ab58984b | |||
dae0949f6e | |||
5590886599 | |||
f0b2017057 | |||
dce269c62e | |||
a97bded440 | |||
9a0507704a | |||
2ff032db90 | |||
37972a91c1 | |||
003d568ae2 | |||
b2adf1109e | |||
02f3a7f65c | |||
9b29e7392f | |||
fe0cf86dc6 | |||
1bcaa1af1f | |||
304bee938b | |||
b2163ff44c | |||
076952f4c7 | |||
a7145ca9bf | |||
5d79abe523 | |||
0671c277db | |||
92450a76ba | |||
abba5b2089 | |||
fd0c6c461d | |||
bfe325e61d | |||
acec938b2d | |||
dc6aea7b79 | |||
170860c14a | |||
12da2f8262 | |||
f6b3f79e89 | |||
9729f31e5c | |||
155f9eecb0 | |||
69c35b1d61 | |||
9b113c3156 | |||
4de5fca547 | |||
9c4c5a5262 | |||
6fcae9f75a | |||
1df64c5cab | |||
6a580db55e | |||
24054cf6f4 | |||
9ee3dd4e91 | |||
78bfd12229 | |||
57dc0a8e9e | |||
b309b34bfc | |||
c8acdf40bb | |||
6410542d19 | |||
c0a341a7f6 | |||
e1a984e9d8 | |||
abfd9657f9 | |||
a788d44773 | |||
603015d029 | |||
30e14d50ef | |||
951a7ee1c7 | |||
0bcbeb26b2 | |||
c98357606b | |||
80de5d70bf | |||
57efa0bc8e | |||
26e0c82fb8 | |||
4538ccb12a | |||
84e1599997 | |||
5a270e2e61 | |||
436d65d784 | |||
c3c034ecca | |||
05fd999162 | |||
eff95bd632 | |||
fb928616cc | |||
4d5ae59a52 | |||
a9f27e074b | |||
6c51f48aab | |||
a2485637bb | |||
09faca034c | |||
ceac1c8709 | |||
f7e75b13b0 | |||
198aaebc94 | |||
85af6bcd5c | |||
8a658de0b2 | |||
3900b92927 | |||
5ccb3394b4 | |||
dc410fca90 | |||
cddcd73f04 | |||
d7fcc5ce30 | |||
c0221d76e6 | |||
242f0095d0 | |||
6fe34d266a | |||
fa08bfa553 | |||
0da998ef50 | |||
e44782473a | |||
9cd1bcef06 | |||
ca0a33ea0f | |||
f6c5222952 | |||
ea868e09f8 | |||
31d3d299bf | |||
b5b4f78b49 | |||
2832f44437 | |||
7c3bcb0f44 | |||
e64871c3fd | |||
303cd35a01 | |||
bb9ba1bce2 | |||
db03742d33 | |||
148d68933b | |||
51ee132ea3 | |||
226dd25dd0 | |||
bd0197eaa8 | |||
e44b84c18c | |||
bed49e6ace |
461 changed files with 10036 additions and 6402 deletions
87
.ci/Jenkinsfile
vendored
Normal file
87
.ci/Jenkinsfile
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
def golang = ['1.23', '1.24']
|
||||
def golangDefault = "golang:${golang.last()}"
|
||||
|
||||
async {
|
||||
|
||||
for (version in golang) {
|
||||
def go = version
|
||||
|
||||
task("test/go${go}") {
|
||||
container("golang:${go}") {
|
||||
sh 'make test'
|
||||
}
|
||||
}
|
||||
|
||||
task("build/go${go}") {
|
||||
container("golang:${go}") {
|
||||
for (app in ['cli', 'node', 'ir', 'adm', 'lens']) {
|
||||
sh """
|
||||
make bin/frostfs-${app}
|
||||
bin/frostfs-${app} --version
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task('test/race') {
|
||||
container(golangDefault) {
|
||||
sh 'make test GOFLAGS="-count=1 -race"'
|
||||
}
|
||||
}
|
||||
|
||||
task('lint') {
|
||||
container(golangDefault) {
|
||||
sh 'make lint-install lint'
|
||||
}
|
||||
}
|
||||
|
||||
task('staticcheck') {
|
||||
container(golangDefault) {
|
||||
sh 'make staticcheck-install staticcheck-run'
|
||||
}
|
||||
}
|
||||
|
||||
task('gopls') {
|
||||
container(golangDefault) {
|
||||
sh 'make gopls-install gopls-run'
|
||||
}
|
||||
}
|
||||
|
||||
task('gofumpt') {
|
||||
container(golangDefault) {
|
||||
sh '''
|
||||
make fumpt-install
|
||||
make fumpt
|
||||
git diff --exit-code --quiet
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
task('vulncheck') {
|
||||
container(golangDefault) {
|
||||
sh '''
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
task('pre-commit') {
|
||||
dockerfile("""
|
||||
FROM ${golangDefault}
|
||||
RUN apt update && \
|
||||
apt install -y --no-install-recommends pre-commit
|
||||
""") {
|
||||
withEnv(['SKIP=make-lint,go-staticcheck-repo-mod,go-unit-tests,gofumpt']) {
|
||||
sh 'pre-commit run --color=always --hook-stage=manual --all-files'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task('dco') {
|
||||
container('git.frostfs.info/truecloudlab/commit-check:master') {
|
||||
sh 'FROM=pull_request_target commit-check'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.22', '1.23' ]
|
||||
go_versions: [ '1.23', '1.24' ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Run commit format checker
|
||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
|
||||
|
|
28
.forgejo/workflows/oci-image.yml
Normal file
28
.forgejo/workflows/oci-image.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: OCI image
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
image:
|
||||
name: Build container images
|
||||
runs-on: docker
|
||||
container: git.frostfs.info/truecloudlab/env:oci-image-builder-bookworm
|
||||
steps:
|
||||
- name: Clone git repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build OCI image
|
||||
run: make images
|
||||
|
||||
- name: Push image to OCI registry
|
||||
run: |
|
||||
echo "$REGISTRY_PASSWORD" \
|
||||
| docker login --username truecloudlab --password-stdin git.frostfs.info
|
||||
make push-images
|
||||
if: >-
|
||||
startsWith(github.ref, 'refs/tags/v') &&
|
||||
(github.event_name == 'workflow_dispatch' || github.event_name == 'push')
|
||||
env:
|
||||
REGISTRY_PASSWORD: ${{secrets.FORGEJO_OCI_REGISTRY_PUSH_TOKEN}}
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.23
|
||||
go-version: 1.24
|
||||
- name: Set up Python
|
||||
run: |
|
||||
apt update
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
|
||||
- name: Install linters
|
||||
|
@ -30,7 +30,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.22', '1.23' ]
|
||||
go_versions: [ '1.23', '1.24' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -53,7 +53,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
|
||||
- name: Install staticcheck
|
||||
|
@ -104,7 +104,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
|
||||
- name: Install gofumpt
|
||||
|
|
|
@ -18,7 +18,8 @@ jobs:
|
|||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: '1.24'
|
||||
check-latest: true
|
||||
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
|
172
.golangci.yml
172
.golangci.yml
|
@ -1,93 +1,103 @@
|
|||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
version: "2"
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 20m
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: false
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
formats:
|
||||
- format: tab
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
# indicates that switch statements are to be considered exhaustive if a
|
||||
# 'default' case is present, even if all enum members aren't listed in the
|
||||
# switch
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
staticcheck:
|
||||
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||
funlen:
|
||||
lines: 80 # default 60
|
||||
statements: 60 # default 40
|
||||
gocognit:
|
||||
min-complexity: 40 # default 30
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||
alias: objectSDK
|
||||
unused:
|
||||
field-writes-are-uses: false
|
||||
exported-fields-are-used: false
|
||||
local-variables-are-used: false
|
||||
custom:
|
||||
truecloudlab-linters:
|
||||
path: bin/linters/external_linters.so
|
||||
original-url: git.frostfs.info/TrueCloudLab/linters.git
|
||||
settings:
|
||||
noliteral:
|
||||
target-methods : ["reportFlushError", "reportError"]
|
||||
disable-packages: ["codes", "err", "res","exec"]
|
||||
constants-package: "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
|
||||
tab:
|
||||
path: stdout
|
||||
colors: false
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
# mandatory linters
|
||||
- govet
|
||||
- revive
|
||||
|
||||
# some default golangci-lint linters
|
||||
- errcheck
|
||||
- gosimple
|
||||
- godot
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
# extra linters
|
||||
- bidichk
|
||||
- durationcheck
|
||||
- exhaustive
|
||||
- copyloopvar
|
||||
- gofmt
|
||||
- goimports
|
||||
- misspell
|
||||
- predeclared
|
||||
- reassign
|
||||
- whitespace
|
||||
- containedctx
|
||||
- contextcheck
|
||||
- copyloopvar
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- funlen
|
||||
- gocognit
|
||||
- contextcheck
|
||||
- godot
|
||||
- importas
|
||||
- truecloudlab-linters
|
||||
- perfsprint
|
||||
- testifylint
|
||||
- protogetter
|
||||
- ineffassign
|
||||
- intrange
|
||||
- tenv
|
||||
disable-all: true
|
||||
fast: false
|
||||
- misspell
|
||||
- perfsprint
|
||||
- predeclared
|
||||
- protogetter
|
||||
- reassign
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- truecloudlab-linters
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usetesting
|
||||
- whitespace
|
||||
settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
funlen:
|
||||
lines: 80
|
||||
statements: 60
|
||||
gocognit:
|
||||
min-complexity: 40
|
||||
importas:
|
||||
alias:
|
||||
- pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||
alias: objectSDK
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -QF1002
|
||||
unused:
|
||||
field-writes-are-uses: false
|
||||
exported-fields-are-used: false
|
||||
local-variables-are-used: false
|
||||
custom:
|
||||
truecloudlab-linters:
|
||||
path: bin/linters/external_linters.so
|
||||
original-url: git.frostfs.info/TrueCloudLab/linters.git
|
||||
settings:
|
||||
noliteral:
|
||||
constants-package: git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs
|
||||
disable-packages:
|
||||
- codes
|
||||
- err
|
||||
- res
|
||||
- exec
|
||||
target-methods:
|
||||
- reportFlushError
|
||||
- reportError
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
custom-order: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
67
Makefile
67
Makefile
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/make -f
|
||||
SHELL = bash
|
||||
.SHELLFLAGS = -euo pipefail -c
|
||||
|
||||
REPO ?= $(shell go list -m)
|
||||
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||
|
@ -7,16 +8,16 @@ VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8
|
|||
HUB_IMAGE ?= git.frostfs.info/truecloudlab/frostfs
|
||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||
|
||||
GO_VERSION ?= 1.22
|
||||
LINT_VERSION ?= 1.62.0
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.8
|
||||
GO_VERSION ?= 1.23
|
||||
LINT_VERSION ?= 2.0.2
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.10
|
||||
PROTOC_VERSION ?= 25.0
|
||||
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-sdk-go)
|
||||
PROTOC_OS_VERSION=osx-x86_64
|
||||
ifeq ($(shell uname), Linux)
|
||||
PROTOC_OS_VERSION=linux-x86_64
|
||||
endif
|
||||
STATICCHECK_VERSION ?= 2024.1.1
|
||||
STATICCHECK_VERSION ?= 2025.1.1
|
||||
ARCH = amd64
|
||||
|
||||
BIN = bin
|
||||
|
@ -42,7 +43,7 @@ GOFUMPT_VERSION ?= v0.7.0
|
|||
GOFUMPT_DIR ?= $(abspath $(BIN))/gofumpt
|
||||
GOFUMPT_VERSION_DIR ?= $(GOFUMPT_DIR)/$(GOFUMPT_VERSION)
|
||||
|
||||
GOPLS_VERSION ?= v0.15.1
|
||||
GOPLS_VERSION ?= v0.17.1
|
||||
GOPLS_DIR ?= $(abspath $(BIN))/gopls
|
||||
GOPLS_VERSION_DIR ?= $(GOPLS_DIR)/$(GOPLS_VERSION)
|
||||
GOPLS_TEMP_FILE := $(shell mktemp)
|
||||
|
@ -115,7 +116,7 @@ protoc:
|
|||
# Install protoc
|
||||
protoc-install:
|
||||
@rm -rf $(PROTOBUF_DIR)
|
||||
@mkdir $(PROTOBUF_DIR)
|
||||
@mkdir -p $(PROTOBUF_DIR)
|
||||
@echo "⇒ Installing protoc... "
|
||||
@wget -q -O $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip 'https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS_VERSION).zip'
|
||||
@unzip -q -o $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip -d $(PROTOC_DIR)
|
||||
|
@ -139,6 +140,15 @@ images: image-storage image-ir image-cli image-adm
|
|||
# Build dirty local Docker images
|
||||
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
||||
|
||||
# Push FrostFS components' docker image to the registry
|
||||
push-image-%:
|
||||
@echo "⇒ Publish FrostFS $* docker image "
|
||||
@docker push $(HUB_IMAGE)-$*:$(HUB_TAG)
|
||||
|
||||
# Push all Docker images to the registry
|
||||
.PHONY: push-images
|
||||
push-images: push-image-storage push-image-ir push-image-cli push-image-adm
|
||||
|
||||
# Run `make %` in Golang container
|
||||
docker/%:
|
||||
docker run --rm -t \
|
||||
|
@ -160,7 +170,7 @@ imports:
|
|||
# Install gofumpt
|
||||
fumpt-install:
|
||||
@rm -rf $(GOFUMPT_DIR)
|
||||
@mkdir $(GOFUMPT_DIR)
|
||||
@mkdir -p $(GOFUMPT_DIR)
|
||||
@GOBIN=$(GOFUMPT_VERSION_DIR) go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION)
|
||||
|
||||
# Run gofumpt
|
||||
|
@ -177,21 +187,44 @@ test:
|
|||
@echo "⇒ Running go test"
|
||||
@GOFLAGS="$(GOFLAGS)" go test ./...
|
||||
|
||||
# Install Gerrit commit-msg hook
|
||||
review-install: GIT_HOOK_DIR := $(shell git rev-parse --git-dir)/hooks
|
||||
review-install:
|
||||
@git config remote.review.url \
|
||||
|| git remote add review ssh://review.frostfs.info:2222/TrueCloudLab/frostfs-node
|
||||
@mkdir -p $(GIT_HOOK_DIR)/
|
||||
@curl -Lo $(GIT_HOOK_DIR)/commit-msg https://review.frostfs.info/tools/hooks/commit-msg
|
||||
@chmod +x $(GIT_HOOK_DIR)/commit-msg
|
||||
@echo -e '#!/bin/sh\n"$$(git rev-parse --git-path hooks)"/commit-msg "$$1"' >$(GIT_HOOK_DIR)/prepare-commit-msg
|
||||
@chmod +x $(GIT_HOOK_DIR)/prepare-commit-msg
|
||||
|
||||
# Create a PR in Gerrit
|
||||
review: BRANCH ?= master
|
||||
review:
|
||||
@git push review HEAD:refs/for/$(BRANCH) \
|
||||
--push-option r=e.stratonikov@yadro.com \
|
||||
--push-option r=d.stepanov@yadro.com \
|
||||
--push-option r=an.nikiforov@yadro.com \
|
||||
--push-option r=a.arifullin@yadro.com \
|
||||
--push-option r=ekaterina.lebedeva@yadro.com \
|
||||
--push-option r=a.savchuk@yadro.com \
|
||||
--push-option r=a.chuprov@yadro.com
|
||||
|
||||
# Run pre-commit
|
||||
pre-commit-run:
|
||||
@pre-commit run -a --hook-stage manual
|
||||
|
||||
# Install linters
|
||||
lint-install:
|
||||
lint-install: $(BIN)
|
||||
@rm -rf $(OUTPUT_LINT_DIR)
|
||||
@mkdir $(OUTPUT_LINT_DIR)
|
||||
@mkdir -p $(OUTPUT_LINT_DIR)
|
||||
@mkdir -p $(TMP_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install -trimpath github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install -trimpath github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
|
@ -203,7 +236,7 @@ lint:
|
|||
# Install staticcheck
|
||||
staticcheck-install:
|
||||
@rm -rf $(STATICCHECK_DIR)
|
||||
@mkdir $(STATICCHECK_DIR)
|
||||
@mkdir -p $(STATICCHECK_DIR)
|
||||
@GOBIN=$(STATICCHECK_VERSION_DIR) go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)
|
||||
|
||||
# Run staticcheck
|
||||
|
@ -216,7 +249,7 @@ staticcheck-run:
|
|||
# Install gopls
|
||||
gopls-install:
|
||||
@rm -rf $(GOPLS_DIR)
|
||||
@mkdir $(GOPLS_DIR)
|
||||
@mkdir -p $(GOPLS_DIR)
|
||||
@GOBIN=$(GOPLS_VERSION_DIR) go install golang.org/x/tools/gopls@$(GOPLS_VERSION)
|
||||
|
||||
# Run gopls
|
||||
|
@ -270,10 +303,12 @@ env-up: all
|
|||
echo "Frostfs contracts not found"; exit 1; \
|
||||
fi
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph init --contracts ${FROSTFS_CONTRACTS_PATH}
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet01.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet02.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet03.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet04.json --gas 10.0
|
||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --gas 10.0 \
|
||||
--storage-wallet ./dev/storage/wallet01.json \
|
||||
--storage-wallet ./dev/storage/wallet02.json \
|
||||
--storage-wallet ./dev/storage/wallet03.json \
|
||||
--storage-wallet ./dev/storage/wallet04.json
|
||||
|
||||
@if [ ! -f "$(LOCODE_DB_PATH)" ]; then \
|
||||
make locode-download; \
|
||||
fi
|
||||
|
|
|
@ -16,9 +16,16 @@ const (
|
|||
EndpointFlagDesc = "N3 RPC node endpoint"
|
||||
EndpointFlagShort = "r"
|
||||
|
||||
WalletPath = "wallet"
|
||||
WalletPathShorthand = "w"
|
||||
WalletPathUsage = "Path to the wallet"
|
||||
|
||||
AlphabetWalletsFlag = "alphabet-wallets"
|
||||
AlphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
||||
|
||||
AdminWalletPath = "wallet-admin"
|
||||
AdminWalletUsage = "Path to the admin wallet"
|
||||
|
||||
LocalDumpFlag = "local-dump"
|
||||
ProtoConfigPath = "protocol"
|
||||
ContractsInitFlag = "contracts"
|
||||
|
|
15
cmd/frostfs-adm/internal/modules/maintenance/root.go
Normal file
15
cmd/frostfs-adm/internal/modules/maintenance/root.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package maintenance
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/maintenance/zombie"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "maintenance",
|
||||
Short: "Section for maintenance commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(zombie.Cmd)
|
||||
}
|
70
cmd/frostfs-adm/internal/modules/maintenance/zombie/key.go
Normal file
70
cmd/frostfs-adm/internal/modules/maintenance/zombie/key.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
nodeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/node"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getPrivateKey(cmd *cobra.Command, appCfg *config.Config) *ecdsa.PrivateKey {
|
||||
keyDesc := viper.GetString(walletFlag)
|
||||
if keyDesc == "" {
|
||||
return &nodeconfig.Key(appCfg).PrivateKey
|
||||
}
|
||||
data, err := os.ReadFile(keyDesc)
|
||||
commonCmd.ExitOnErr(cmd, "open wallet file: %w", err)
|
||||
|
||||
priv, err := keys.NewPrivateKeyFromBytes(data)
|
||||
if err != nil {
|
||||
w, err := wallet.NewWalletFromFile(keyDesc)
|
||||
commonCmd.ExitOnErr(cmd, "provided key is incorrect, only wallet or binary key supported: %w", err)
|
||||
return fromWallet(cmd, w, viper.GetString(addressFlag))
|
||||
}
|
||||
return &priv.PrivateKey
|
||||
}
|
||||
|
||||
func fromWallet(cmd *cobra.Command, w *wallet.Wallet, addrStr string) *ecdsa.PrivateKey {
|
||||
var (
|
||||
addr util.Uint160
|
||||
err error
|
||||
)
|
||||
|
||||
if addrStr == "" {
|
||||
addr = w.GetChangeAddress()
|
||||
} else {
|
||||
addr, err = flags.ParseAddress(addrStr)
|
||||
commonCmd.ExitOnErr(cmd, "--address option must be specified and valid: %w", err)
|
||||
}
|
||||
|
||||
acc := w.GetAccount(addr)
|
||||
if acc == nil {
|
||||
commonCmd.ExitOnErr(cmd, "--address option must be specified and valid: %w", fmt.Errorf("can't find wallet account for %s", addrStr))
|
||||
}
|
||||
|
||||
pass, err := getPassword()
|
||||
commonCmd.ExitOnErr(cmd, "invalid password for the encrypted key: %w", err)
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "can't decrypt account: %w", acc.Decrypt(pass, keys.NEP2ScryptParams()))
|
||||
|
||||
return &acc.PrivateKey().PrivateKey
|
||||
}
|
||||
|
||||
func getPassword() (string, error) {
|
||||
// this check allows empty passwords
|
||||
if viper.IsSet("password") {
|
||||
return viper.GetString("password"), nil
|
||||
}
|
||||
|
||||
return input.ReadPassword("Enter password > ")
|
||||
}
|
31
cmd/frostfs-adm/internal/modules/maintenance/zombie/list.go
Normal file
31
cmd/frostfs-adm/internal/modules/maintenance/zombie/list.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func list(cmd *cobra.Command, _ []string) {
|
||||
configFile, _ := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||
configDir, _ := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||
appCfg := config.New(configFile, configDir, config.EnvPrefix)
|
||||
storageEngine := newEngine(cmd, appCfg)
|
||||
q := createQuarantine(cmd, storageEngine.DumpInfo())
|
||||
var containerID *cid.ID
|
||||
if cidStr, _ := cmd.Flags().GetString(cidFlag); cidStr != "" {
|
||||
containerID = &cid.ID{}
|
||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", containerID.DecodeString(cidStr))
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "iterate over quarantine: %w", q.Iterate(cmd.Context(), func(a oid.Address) error {
|
||||
if containerID != nil && a.Container() != *containerID {
|
||||
return nil
|
||||
}
|
||||
cmd.Println(a.EncodeToString())
|
||||
return nil
|
||||
}))
|
||||
}
|
46
cmd/frostfs-adm/internal/modules/maintenance/zombie/morph.go
Normal file
46
cmd/frostfs-adm/internal/modules/maintenance/zombie/morph.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
morphconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/morph"
|
||||
nodeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/node"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||
netmapClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func createMorphClient(cmd *cobra.Command, appCfg *config.Config) *client.Client {
|
||||
addresses := morphconfig.RPCEndpoint(appCfg)
|
||||
if len(addresses) == 0 {
|
||||
commonCmd.ExitOnErr(cmd, "create morph client: %w", errors.New("no morph endpoints found"))
|
||||
}
|
||||
key := nodeconfig.Key(appCfg)
|
||||
cli, err := client.New(cmd.Context(),
|
||||
key,
|
||||
client.WithDialTimeout(morphconfig.DialTimeout(appCfg)),
|
||||
client.WithEndpoints(addresses...),
|
||||
client.WithSwitchInterval(morphconfig.SwitchInterval(appCfg)),
|
||||
)
|
||||
commonCmd.ExitOnErr(cmd, "create morph client: %w", err)
|
||||
return cli
|
||||
}
|
||||
|
||||
func createContainerClient(cmd *cobra.Command, morph *client.Client) *cntClient.Client {
|
||||
hs, err := morph.NNSContractAddress(client.NNSContainerContractName)
|
||||
commonCmd.ExitOnErr(cmd, "resolve container contract hash: %w", err)
|
||||
cc, err := cntClient.NewFromMorph(morph, hs, 0)
|
||||
commonCmd.ExitOnErr(cmd, "create morph container client: %w", err)
|
||||
return cc
|
||||
}
|
||||
|
||||
func createNetmapClient(cmd *cobra.Command, morph *client.Client) *netmapClient.Client {
|
||||
hs, err := morph.NNSContractAddress(client.NNSNetmapContractName)
|
||||
commonCmd.ExitOnErr(cmd, "resolve netmap contract hash: %w", err)
|
||||
cli, err := netmapClient.NewFromMorph(morph, hs, 0)
|
||||
commonCmd.ExitOnErr(cmd, "create morph netmap client: %w", err)
|
||||
return cli
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
objectcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type quarantine struct {
|
||||
// mtx protects current field.
|
||||
mtx sync.Mutex
|
||||
current int
|
||||
trees []*fstree.FSTree
|
||||
}
|
||||
|
||||
func createQuarantine(cmd *cobra.Command, engineInfo engine.Info) *quarantine {
|
||||
var paths []string
|
||||
for _, sh := range engineInfo.Shards {
|
||||
var storagePaths []string
|
||||
for _, st := range sh.BlobStorInfo.SubStorages {
|
||||
storagePaths = append(storagePaths, st.Path)
|
||||
}
|
||||
if len(storagePaths) == 0 {
|
||||
continue
|
||||
}
|
||||
paths = append(paths, filepath.Join(commonPath(storagePaths), "quarantine"))
|
||||
}
|
||||
q, err := newQuarantine(paths)
|
||||
commonCmd.ExitOnErr(cmd, "create quarantine: %w", err)
|
||||
return q
|
||||
}
|
||||
|
||||
func commonPath(paths []string) string {
|
||||
if len(paths) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(paths) == 1 {
|
||||
return paths[0]
|
||||
}
|
||||
minLen := math.MaxInt
|
||||
for _, p := range paths {
|
||||
if len(p) < minLen {
|
||||
minLen = len(p)
|
||||
}
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for i := range minLen {
|
||||
for _, path := range paths[1:] {
|
||||
if paths[0][i] != path[i] {
|
||||
return sb.String()
|
||||
}
|
||||
}
|
||||
sb.WriteByte(paths[0][i])
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func newQuarantine(paths []string) (*quarantine, error) {
|
||||
var q quarantine
|
||||
for i := range paths {
|
||||
f := fstree.New(
|
||||
fstree.WithDepth(1),
|
||||
fstree.WithDirNameLen(1),
|
||||
fstree.WithPath(paths[i]),
|
||||
fstree.WithPerm(os.ModePerm),
|
||||
)
|
||||
if err := f.Open(mode.ComponentReadWrite); err != nil {
|
||||
return nil, fmt.Errorf("open fstree %s: %w", paths[i], err)
|
||||
}
|
||||
if err := f.Init(); err != nil {
|
||||
return nil, fmt.Errorf("init fstree %s: %w", paths[i], err)
|
||||
}
|
||||
q.trees = append(q.trees, f)
|
||||
}
|
||||
return &q, nil
|
||||
}
|
||||
|
||||
func (q *quarantine) Get(ctx context.Context, a oid.Address) (*objectSDK.Object, error) {
|
||||
for i := range q.trees {
|
||||
res, err := q.trees[i].Get(ctx, common.GetPrm{Address: a})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return res.Object, nil
|
||||
}
|
||||
return nil, &apistatus.ObjectNotFound{}
|
||||
}
|
||||
|
||||
func (q *quarantine) Delete(ctx context.Context, a oid.Address) error {
|
||||
for i := range q.trees {
|
||||
_, err := q.trees[i].Delete(ctx, common.DeletePrm{Address: a})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &apistatus.ObjectNotFound{}
|
||||
}
|
||||
|
||||
func (q *quarantine) Put(ctx context.Context, obj *objectSDK.Object) error {
|
||||
data, err := obj.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var prm common.PutPrm
|
||||
prm.Address = objectcore.AddressOf(obj)
|
||||
prm.Object = obj
|
||||
prm.RawData = data
|
||||
|
||||
q.mtx.Lock()
|
||||
current := q.current
|
||||
q.current = (q.current + 1) % len(q.trees)
|
||||
q.mtx.Unlock()
|
||||
|
||||
_, err = q.trees[current].Put(ctx, prm)
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *quarantine) Iterate(ctx context.Context, f func(oid.Address) error) error {
|
||||
var prm common.IteratePrm
|
||||
prm.Handler = func(elem common.IterationElement) error {
|
||||
return f(elem.Address)
|
||||
}
|
||||
for i := range q.trees {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
_, err := q.trees[i].Iterate(ctx, prm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func remove(cmd *cobra.Command, _ []string) {
|
||||
configFile, _ := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||
configDir, _ := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||
appCfg := config.New(configFile, configDir, config.EnvPrefix)
|
||||
storageEngine := newEngine(cmd, appCfg)
|
||||
q := createQuarantine(cmd, storageEngine.DumpInfo())
|
||||
|
||||
var containerID cid.ID
|
||||
cidStr, _ := cmd.Flags().GetString(cidFlag)
|
||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", containerID.DecodeString(cidStr))
|
||||
|
||||
var objectID *oid.ID
|
||||
oidStr, _ := cmd.Flags().GetString(oidFlag)
|
||||
if oidStr != "" {
|
||||
objectID = &oid.ID{}
|
||||
commonCmd.ExitOnErr(cmd, "decode object ID string: %w", objectID.DecodeString(oidStr))
|
||||
}
|
||||
|
||||
if objectID != nil {
|
||||
var addr oid.Address
|
||||
addr.SetContainer(containerID)
|
||||
addr.SetObject(*objectID)
|
||||
removeObject(cmd, q, addr)
|
||||
} else {
|
||||
commonCmd.ExitOnErr(cmd, "iterate over quarantine: %w", q.Iterate(cmd.Context(), func(addr oid.Address) error {
|
||||
if addr.Container() != containerID {
|
||||
return nil
|
||||
}
|
||||
removeObject(cmd, q, addr)
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func removeObject(cmd *cobra.Command, q *quarantine, addr oid.Address) {
|
||||
err := q.Delete(cmd.Context(), addr)
|
||||
if errors.Is(err, new(apistatus.ObjectNotFound)) {
|
||||
return
|
||||
}
|
||||
commonCmd.ExitOnErr(cmd, "remove object from quarantine: %w", err)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
containerCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
||||
cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func restore(cmd *cobra.Command, _ []string) {
|
||||
configFile, _ := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||
configDir, _ := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||
appCfg := config.New(configFile, configDir, config.EnvPrefix)
|
||||
storageEngine := newEngine(cmd, appCfg)
|
||||
q := createQuarantine(cmd, storageEngine.DumpInfo())
|
||||
morphClient := createMorphClient(cmd, appCfg)
|
||||
cnrCli := createContainerClient(cmd, morphClient)
|
||||
|
||||
var containerID cid.ID
|
||||
cidStr, _ := cmd.Flags().GetString(cidFlag)
|
||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", containerID.DecodeString(cidStr))
|
||||
|
||||
var objectID *oid.ID
|
||||
oidStr, _ := cmd.Flags().GetString(oidFlag)
|
||||
if oidStr != "" {
|
||||
objectID = &oid.ID{}
|
||||
commonCmd.ExitOnErr(cmd, "decode object ID string: %w", objectID.DecodeString(oidStr))
|
||||
}
|
||||
|
||||
if objectID != nil {
|
||||
var addr oid.Address
|
||||
addr.SetContainer(containerID)
|
||||
addr.SetObject(*objectID)
|
||||
restoreObject(cmd, storageEngine, q, addr, cnrCli)
|
||||
} else {
|
||||
commonCmd.ExitOnErr(cmd, "iterate over quarantine: %w", q.Iterate(cmd.Context(), func(addr oid.Address) error {
|
||||
if addr.Container() != containerID {
|
||||
return nil
|
||||
}
|
||||
restoreObject(cmd, storageEngine, q, addr, cnrCli)
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func restoreObject(cmd *cobra.Command, storageEngine *engine.StorageEngine, q *quarantine, addr oid.Address, cnrCli *cntClient.Client) {
|
||||
obj, err := q.Get(cmd.Context(), addr)
|
||||
commonCmd.ExitOnErr(cmd, "get object from quarantine: %w", err)
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
|
||||
cid := addr.Container()
|
||||
cid.Encode(rawCID)
|
||||
cnr, err := cnrCli.Get(cmd.Context(), rawCID)
|
||||
commonCmd.ExitOnErr(cmd, "get container: %w", err)
|
||||
|
||||
putPrm := engine.PutPrm{
|
||||
Object: obj,
|
||||
IsIndexedContainer: containerCore.IsIndexedContainer(cnr.Value),
|
||||
}
|
||||
commonCmd.ExitOnErr(cmd, "put object to storage engine: %w", storageEngine.Put(cmd.Context(), putPrm))
|
||||
commonCmd.ExitOnErr(cmd, "remove object from quarantine: %w", q.Delete(cmd.Context(), addr))
|
||||
}
|
123
cmd/frostfs-adm/internal/modules/maintenance/zombie/root.go
Normal file
123
cmd/frostfs-adm/internal/modules/maintenance/zombie/root.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
flagBatchSize = "batch-size"
|
||||
flagBatchSizeUsage = "Objects iteration batch size"
|
||||
cidFlag = "cid"
|
||||
cidFlagUsage = "Container ID"
|
||||
oidFlag = "oid"
|
||||
oidFlagUsage = "Object ID"
|
||||
walletFlag = "wallet"
|
||||
walletFlagShorthand = "w"
|
||||
walletFlagUsage = "Path to the wallet or binary key"
|
||||
addressFlag = "address"
|
||||
addressFlagUsage = "Address of wallet account"
|
||||
moveFlag = "move"
|
||||
moveFlagUsage = "Move objects from storage engine to quarantine"
|
||||
)
|
||||
|
||||
var (
|
||||
Cmd = &cobra.Command{
|
||||
Use: "zombie",
|
||||
Short: "Zombie objects related commands",
|
||||
}
|
||||
scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan storage engine for zombie objects and move them to quarantine",
|
||||
Long: "",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.ConfigFlag, cmd.Flags().Lookup(commonflags.ConfigFlag))
|
||||
_ = viper.BindPFlag(commonflags.ConfigDirFlag, cmd.Flags().Lookup(commonflags.ConfigDirFlag))
|
||||
_ = viper.BindPFlag(walletFlag, cmd.Flags().Lookup(walletFlag))
|
||||
_ = viper.BindPFlag(addressFlag, cmd.Flags().Lookup(addressFlag))
|
||||
_ = viper.BindPFlag(flagBatchSize, cmd.Flags().Lookup(flagBatchSize))
|
||||
_ = viper.BindPFlag(moveFlag, cmd.Flags().Lookup(moveFlag))
|
||||
},
|
||||
Run: scan,
|
||||
}
|
||||
listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List zombie objects from quarantine",
|
||||
Long: "",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.ConfigFlag, cmd.Flags().Lookup(commonflags.ConfigFlag))
|
||||
_ = viper.BindPFlag(commonflags.ConfigDirFlag, cmd.Flags().Lookup(commonflags.ConfigDirFlag))
|
||||
_ = viper.BindPFlag(cidFlag, cmd.Flags().Lookup(cidFlag))
|
||||
},
|
||||
Run: list,
|
||||
}
|
||||
restoreCmd = &cobra.Command{
|
||||
Use: "restore",
|
||||
Short: "Restore zombie objects from quarantine",
|
||||
Long: "",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.ConfigFlag, cmd.Flags().Lookup(commonflags.ConfigFlag))
|
||||
_ = viper.BindPFlag(commonflags.ConfigDirFlag, cmd.Flags().Lookup(commonflags.ConfigDirFlag))
|
||||
_ = viper.BindPFlag(cidFlag, cmd.Flags().Lookup(cidFlag))
|
||||
_ = viper.BindPFlag(oidFlag, cmd.Flags().Lookup(oidFlag))
|
||||
},
|
||||
Run: restore,
|
||||
}
|
||||
removeCmd = &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove zombie objects from quarantine",
|
||||
Long: "",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.ConfigFlag, cmd.Flags().Lookup(commonflags.ConfigFlag))
|
||||
_ = viper.BindPFlag(commonflags.ConfigDirFlag, cmd.Flags().Lookup(commonflags.ConfigDirFlag))
|
||||
_ = viper.BindPFlag(cidFlag, cmd.Flags().Lookup(cidFlag))
|
||||
_ = viper.BindPFlag(oidFlag, cmd.Flags().Lookup(oidFlag))
|
||||
},
|
||||
Run: remove,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
initScanCmd()
|
||||
initListCmd()
|
||||
initRestoreCmd()
|
||||
initRemoveCmd()
|
||||
}
|
||||
|
||||
func initScanCmd() {
|
||||
Cmd.AddCommand(scanCmd)
|
||||
|
||||
scanCmd.Flags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||
scanCmd.Flags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||
scanCmd.Flags().Uint32(flagBatchSize, 1000, flagBatchSizeUsage)
|
||||
scanCmd.Flags().StringP(walletFlag, walletFlagShorthand, "", walletFlagUsage)
|
||||
scanCmd.Flags().String(addressFlag, "", addressFlagUsage)
|
||||
scanCmd.Flags().Bool(moveFlag, false, moveFlagUsage)
|
||||
}
|
||||
|
||||
func initListCmd() {
|
||||
Cmd.AddCommand(listCmd)
|
||||
|
||||
listCmd.Flags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||
listCmd.Flags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||
listCmd.Flags().String(cidFlag, "", cidFlagUsage)
|
||||
}
|
||||
|
||||
func initRestoreCmd() {
|
||||
Cmd.AddCommand(restoreCmd)
|
||||
|
||||
restoreCmd.Flags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||
restoreCmd.Flags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||
restoreCmd.Flags().String(cidFlag, "", cidFlagUsage)
|
||||
restoreCmd.Flags().String(oidFlag, "", oidFlagUsage)
|
||||
}
|
||||
|
||||
func initRemoveCmd() {
|
||||
Cmd.AddCommand(removeCmd)
|
||||
|
||||
removeCmd.Flags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||
removeCmd.Flags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||
removeCmd.Flags().String(cidFlag, "", cidFlagUsage)
|
||||
removeCmd.Flags().String(oidFlag, "", oidFlagUsage)
|
||||
}
|
281
cmd/frostfs-adm/internal/modules/maintenance/zombie/scan.go
Normal file
281
cmd/frostfs-adm/internal/modules/maintenance/zombie/scan.go
Normal file
|
@ -0,0 +1,281 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
apiclientconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/apiclient"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
clientCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/client"
|
||||
netmapCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
||||
cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache"
|
||||
clientSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func scan(cmd *cobra.Command, _ []string) {
|
||||
configFile, _ := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||
configDir, _ := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||
appCfg := config.New(configFile, configDir, config.EnvPrefix)
|
||||
batchSize, _ := cmd.Flags().GetUint32(flagBatchSize)
|
||||
if batchSize == 0 {
|
||||
commonCmd.ExitOnErr(cmd, "invalid batch size: %w", errors.New("batch size must be positive value"))
|
||||
}
|
||||
move, _ := cmd.Flags().GetBool(moveFlag)
|
||||
|
||||
storageEngine := newEngine(cmd, appCfg)
|
||||
morphClient := createMorphClient(cmd, appCfg)
|
||||
cnrCli := createContainerClient(cmd, morphClient)
|
||||
nmCli := createNetmapClient(cmd, morphClient)
|
||||
q := createQuarantine(cmd, storageEngine.DumpInfo())
|
||||
pk := getPrivateKey(cmd, appCfg)
|
||||
|
||||
epoch, err := nmCli.Epoch(cmd.Context())
|
||||
commonCmd.ExitOnErr(cmd, "read epoch from morph: %w", err)
|
||||
|
||||
nm, err := nmCli.GetNetMapByEpoch(cmd.Context(), epoch)
|
||||
commonCmd.ExitOnErr(cmd, "read netmap from morph: %w", err)
|
||||
|
||||
cmd.Printf("Epoch: %d\n", nm.Epoch())
|
||||
cmd.Printf("Nodes in the netmap: %d\n", len(nm.Nodes()))
|
||||
|
||||
ps := &processStatus{
|
||||
statusCount: make(map[status]uint64),
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
start := time.Now()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
tick := time.NewTicker(time.Second)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-cmd.Context().Done():
|
||||
return
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-tick.C:
|
||||
fmt.Printf("Objects processed: %d; Time elapsed: %s\n", ps.total(), time.Since(start))
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = scanStorageEngine(cmd, batchSize, storageEngine, ps, appCfg, cnrCli, nmCli, q, pk, move)
|
||||
close(stopCh)
|
||||
}()
|
||||
wg.Wait()
|
||||
commonCmd.ExitOnErr(cmd, "scan storage engine for zombie objects: %w", err)
|
||||
|
||||
cmd.Println()
|
||||
cmd.Println("Status description:")
|
||||
cmd.Println("undefined -- nothing is clear")
|
||||
cmd.Println("found -- object is found in cluster")
|
||||
cmd.Println("quarantine -- object is not found in cluster")
|
||||
cmd.Println()
|
||||
for status, count := range ps.statusCount {
|
||||
cmd.Printf("Status: %s, Count: %d\n", status, count)
|
||||
}
|
||||
}
|
||||
|
||||
type status string
|
||||
|
||||
const (
|
||||
statusUndefined status = "undefined"
|
||||
statusFound status = "found"
|
||||
statusQuarantine status = "quarantine"
|
||||
)
|
||||
|
||||
func checkAddr(ctx context.Context, cnrCli *cntClient.Client, nmCli *netmap.Client, cc *cache.ClientCache, obj object.Info) (status, error) {
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
cid := obj.Address.Container()
|
||||
cid.Encode(rawCID)
|
||||
|
||||
cnr, err := cnrCli.Get(ctx, rawCID)
|
||||
if err != nil {
|
||||
var errContainerNotFound *apistatus.ContainerNotFound
|
||||
if errors.As(err, &errContainerNotFound) {
|
||||
// Policer will deal with this object.
|
||||
return statusFound, nil
|
||||
}
|
||||
return statusUndefined, fmt.Errorf("read container %s from morph: %w", cid, err)
|
||||
}
|
||||
nm, err := nmCli.NetMap(ctx)
|
||||
if err != nil {
|
||||
return statusUndefined, fmt.Errorf("read netmap from morph: %w", err)
|
||||
}
|
||||
|
||||
nodes, err := nm.ContainerNodes(cnr.Value.PlacementPolicy(), rawCID)
|
||||
if err != nil {
|
||||
// Not enough nodes, check all netmap nodes.
|
||||
nodes = append([][]netmap.NodeInfo{}, nm.Nodes())
|
||||
}
|
||||
|
||||
objID := obj.Address.Object()
|
||||
cnrID := obj.Address.Container()
|
||||
local := true
|
||||
raw := false
|
||||
if obj.ECInfo != nil {
|
||||
objID = obj.ECInfo.ParentID
|
||||
local = false
|
||||
raw = true
|
||||
}
|
||||
prm := clientSDK.PrmObjectHead{
|
||||
ObjectID: &objID,
|
||||
ContainerID: &cnrID,
|
||||
Local: local,
|
||||
Raw: raw,
|
||||
}
|
||||
|
||||
var ni clientCore.NodeInfo
|
||||
for i := range nodes {
|
||||
for j := range nodes[i] {
|
||||
if err := clientCore.NodeInfoFromRawNetmapElement(&ni, netmapCore.Node(nodes[i][j])); err != nil {
|
||||
return statusUndefined, fmt.Errorf("parse node info: %w", err)
|
||||
}
|
||||
c, err := cc.Get(ni)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
res, err := c.ObjectHead(ctx, prm)
|
||||
if err != nil {
|
||||
var errECInfo *objectSDK.ECInfoError
|
||||
if raw && errors.As(err, &errECInfo) {
|
||||
return statusFound, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := apistatus.ErrFromStatus(res.Status()); err != nil {
|
||||
continue
|
||||
}
|
||||
return statusFound, nil
|
||||
}
|
||||
}
|
||||
|
||||
if cnr.Value.PlacementPolicy().NumberOfReplicas() == 1 && cnr.Value.PlacementPolicy().ReplicaDescriptor(0).NumberOfObjects() == 1 {
|
||||
return statusFound, nil
|
||||
}
|
||||
return statusQuarantine, nil
|
||||
}
|
||||
|
||||
func scanStorageEngine(cmd *cobra.Command, batchSize uint32, storageEngine *engine.StorageEngine, ps *processStatus,
|
||||
appCfg *config.Config, cnrCli *cntClient.Client, nmCli *netmap.Client, q *quarantine, pk *ecdsa.PrivateKey, move bool,
|
||||
) error {
|
||||
cc := cache.NewSDKClientCache(cache.ClientCacheOpts{
|
||||
DialTimeout: apiclientconfig.DialTimeout(appCfg),
|
||||
StreamTimeout: apiclientconfig.StreamTimeout(appCfg),
|
||||
ReconnectTimeout: apiclientconfig.ReconnectTimeout(appCfg),
|
||||
Key: pk,
|
||||
AllowExternal: apiclientconfig.AllowExternal(appCfg),
|
||||
})
|
||||
ctx := cmd.Context()
|
||||
|
||||
var cursor *engine.Cursor
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
var prm engine.ListWithCursorPrm
|
||||
prm.WithCursor(cursor)
|
||||
prm.WithCount(batchSize)
|
||||
|
||||
res, err := storageEngine.ListWithCursor(ctx, prm)
|
||||
if err != nil {
|
||||
if errors.Is(err, engine.ErrEndOfListing) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("list with cursor: %w", err)
|
||||
}
|
||||
|
||||
cursor = res.Cursor()
|
||||
addrList := res.AddressList()
|
||||
eg, egCtx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(int(batchSize))
|
||||
|
||||
for i := range addrList {
|
||||
addr := addrList[i]
|
||||
eg.Go(func() error {
|
||||
result, err := checkAddr(egCtx, cnrCli, nmCli, cc, addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check object %s status: %w", addr.Address, err)
|
||||
}
|
||||
ps.add(result)
|
||||
|
||||
if !move && result == statusQuarantine {
|
||||
cmd.Println(addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
if result == statusQuarantine {
|
||||
return moveToQuarantine(egCtx, storageEngine, q, addr.Address)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return fmt.Errorf("process objects batch: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func moveToQuarantine(ctx context.Context, storageEngine *engine.StorageEngine, q *quarantine, addr oid.Address) error {
|
||||
var getPrm engine.GetPrm
|
||||
getPrm.WithAddress(addr)
|
||||
res, err := storageEngine.Get(ctx, getPrm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get object %s from storage engine: %w", addr, err)
|
||||
}
|
||||
|
||||
if err := q.Put(ctx, res.Object()); err != nil {
|
||||
return fmt.Errorf("put object %s to quarantine: %w", addr, err)
|
||||
}
|
||||
|
||||
var delPrm engine.DeletePrm
|
||||
delPrm.WithForceRemoval()
|
||||
delPrm.WithAddress(addr)
|
||||
|
||||
if err = storageEngine.Delete(ctx, delPrm); err != nil {
|
||||
return fmt.Errorf("delete object %s from storage engine: %w", addr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type processStatus struct {
|
||||
guard sync.RWMutex
|
||||
statusCount map[status]uint64
|
||||
count uint64
|
||||
}
|
||||
|
||||
func (s *processStatus) add(st status) {
|
||||
s.guard.Lock()
|
||||
defer s.guard.Unlock()
|
||||
s.statusCount[st]++
|
||||
s.count++
|
||||
}
|
||||
|
||||
func (s *processStatus) total() uint64 {
|
||||
s.guard.RLock()
|
||||
defer s.guard.RUnlock()
|
||||
return s.count
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
package zombie
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||
engineconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine"
|
||||
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
|
||||
blobovniczaconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/blobovnicza"
|
||||
fstreeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/fstree"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/qos"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
||||
meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"github.com/spf13/cobra"
|
||||
"go.etcd.io/bbolt"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func newEngine(cmd *cobra.Command, c *config.Config) *engine.StorageEngine {
|
||||
ngOpts := storageEngineOptions(c)
|
||||
shardOpts := shardOptions(cmd, c)
|
||||
e := engine.New(ngOpts...)
|
||||
for _, opts := range shardOpts {
|
||||
_, err := e.AddShard(cmd.Context(), opts...)
|
||||
commonCmd.ExitOnErr(cmd, "iterate shards from config: %w", err)
|
||||
}
|
||||
commonCmd.ExitOnErr(cmd, "open storage engine: %w", e.Open(cmd.Context()))
|
||||
commonCmd.ExitOnErr(cmd, "init storage engine: %w", e.Init(cmd.Context()))
|
||||
return e
|
||||
}
|
||||
|
||||
func storageEngineOptions(c *config.Config) []engine.Option {
|
||||
return []engine.Option{
|
||||
engine.WithErrorThreshold(engineconfig.ShardErrorThreshold(c)),
|
||||
engine.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
engine.WithLowMemoryConsumption(engineconfig.EngineLowMemoryConsumption(c)),
|
||||
}
|
||||
}
|
||||
|
||||
func shardOptions(cmd *cobra.Command, c *config.Config) [][]shard.Option {
|
||||
var result [][]shard.Option
|
||||
err := engineconfig.IterateShards(c, false, func(sh *shardconfig.Config) error {
|
||||
result = append(result, getShardOpts(cmd, c, sh))
|
||||
return nil
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "iterate shards from config: %w", err)
|
||||
return result
|
||||
}
|
||||
|
||||
func getShardOpts(cmd *cobra.Command, c *config.Config, sh *shardconfig.Config) []shard.Option {
|
||||
wc, wcEnabled := getWriteCacheOpts(sh)
|
||||
return []shard.Option{
|
||||
shard.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
shard.WithRefillMetabase(sh.RefillMetabase()),
|
||||
shard.WithRefillMetabaseWorkersCount(sh.RefillMetabaseWorkersCount()),
|
||||
shard.WithMode(sh.Mode()),
|
||||
shard.WithBlobStorOptions(getBlobstorOpts(cmd.Context(), sh)...),
|
||||
shard.WithMetaBaseOptions(getMetabaseOpts(sh)...),
|
||||
shard.WithPiloramaOptions(getPiloramaOpts(c, sh)...),
|
||||
shard.WithWriteCache(wcEnabled),
|
||||
shard.WithWriteCacheOptions(wc),
|
||||
shard.WithRemoverBatchSize(sh.GC().RemoverBatchSize()),
|
||||
shard.WithGCRemoverSleepInterval(sh.GC().RemoverSleepInterval()),
|
||||
shard.WithExpiredCollectorBatchSize(sh.GC().ExpiredCollectorBatchSize()),
|
||||
shard.WithExpiredCollectorWorkerCount(sh.GC().ExpiredCollectorWorkerCount()),
|
||||
shard.WithGCWorkerPoolInitializer(func(sz int) util.WorkerPool {
|
||||
pool, err := ants.NewPool(sz)
|
||||
commonCmd.ExitOnErr(cmd, "init GC pool: %w", err)
|
||||
return pool
|
||||
}),
|
||||
shard.WithLimiter(qos.NewNoopLimiter()),
|
||||
}
|
||||
}
|
||||
|
||||
func getWriteCacheOpts(sh *shardconfig.Config) ([]writecache.Option, bool) {
|
||||
if wc := sh.WriteCache(); wc != nil && wc.Enabled() {
|
||||
var result []writecache.Option
|
||||
result = append(result,
|
||||
writecache.WithPath(wc.Path()),
|
||||
writecache.WithFlushSizeLimit(wc.MaxFlushingObjectsSize()),
|
||||
writecache.WithMaxObjectSize(wc.MaxObjectSize()),
|
||||
writecache.WithFlushWorkersCount(wc.WorkerCount()),
|
||||
writecache.WithMaxCacheSize(wc.SizeLimit()),
|
||||
writecache.WithMaxCacheCount(wc.CountLimit()),
|
||||
writecache.WithNoSync(wc.NoSync()),
|
||||
writecache.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
writecache.WithQoSLimiter(qos.NewNoopLimiter()),
|
||||
)
|
||||
return result, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func getPiloramaOpts(c *config.Config, sh *shardconfig.Config) []pilorama.Option {
|
||||
var piloramaOpts []pilorama.Option
|
||||
if config.BoolSafe(c.Sub("tree"), "enabled") {
|
||||
pr := sh.Pilorama()
|
||||
piloramaOpts = append(piloramaOpts,
|
||||
pilorama.WithPath(pr.Path()),
|
||||
pilorama.WithPerm(pr.Perm()),
|
||||
pilorama.WithNoSync(pr.NoSync()),
|
||||
pilorama.WithMaxBatchSize(pr.MaxBatchSize()),
|
||||
pilorama.WithMaxBatchDelay(pr.MaxBatchDelay()),
|
||||
)
|
||||
}
|
||||
return piloramaOpts
|
||||
}
|
||||
|
||||
func getMetabaseOpts(sh *shardconfig.Config) []meta.Option {
|
||||
return []meta.Option{
|
||||
meta.WithPath(sh.Metabase().Path()),
|
||||
meta.WithPermissions(sh.Metabase().BoltDB().Perm()),
|
||||
meta.WithMaxBatchSize(sh.Metabase().BoltDB().MaxBatchSize()),
|
||||
meta.WithMaxBatchDelay(sh.Metabase().BoltDB().MaxBatchDelay()),
|
||||
meta.WithBoltDBOptions(&bbolt.Options{
|
||||
Timeout: 100 * time.Millisecond,
|
||||
}),
|
||||
meta.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
meta.WithEpochState(&epochState{}),
|
||||
}
|
||||
}
|
||||
|
||||
func getBlobstorOpts(ctx context.Context, sh *shardconfig.Config) []blobstor.Option {
|
||||
result := []blobstor.Option{
|
||||
blobstor.WithCompressObjects(sh.Compress()),
|
||||
blobstor.WithUncompressableContentTypes(sh.UncompressableContentTypes()),
|
||||
blobstor.WithCompressibilityEstimate(sh.EstimateCompressibility()),
|
||||
blobstor.WithCompressibilityEstimateThreshold(sh.EstimateCompressibilityThreshold()),
|
||||
blobstor.WithStorages(getSubStorages(ctx, sh)),
|
||||
blobstor.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getSubStorages(ctx context.Context, sh *shardconfig.Config) []blobstor.SubStorage {
|
||||
var ss []blobstor.SubStorage
|
||||
for _, storage := range sh.BlobStor().Storages() {
|
||||
switch storage.Type() {
|
||||
case blobovniczatree.Type:
|
||||
sub := blobovniczaconfig.From((*config.Config)(storage))
|
||||
blobTreeOpts := []blobovniczatree.Option{
|
||||
blobovniczatree.WithRootPath(storage.Path()),
|
||||
blobovniczatree.WithPermissions(storage.Perm()),
|
||||
blobovniczatree.WithBlobovniczaSize(sub.Size()),
|
||||
blobovniczatree.WithBlobovniczaShallowDepth(sub.ShallowDepth()),
|
||||
blobovniczatree.WithBlobovniczaShallowWidth(sub.ShallowWidth()),
|
||||
blobovniczatree.WithOpenedCacheSize(sub.OpenedCacheSize()),
|
||||
blobovniczatree.WithOpenedCacheTTL(sub.OpenedCacheTTL()),
|
||||
blobovniczatree.WithOpenedCacheExpInterval(sub.OpenedCacheExpInterval()),
|
||||
blobovniczatree.WithInitWorkerCount(sub.InitWorkerCount()),
|
||||
blobovniczatree.WithWaitBeforeDropDB(sub.RebuildDropTimeout()),
|
||||
blobovniczatree.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
blobovniczatree.WithObjectSizeLimit(sh.SmallSizeLimit()),
|
||||
}
|
||||
|
||||
ss = append(ss, blobstor.SubStorage{
|
||||
Storage: blobovniczatree.NewBlobovniczaTree(ctx, blobTreeOpts...),
|
||||
Policy: func(_ *objectSDK.Object, data []byte) bool {
|
||||
return uint64(len(data)) < sh.SmallSizeLimit()
|
||||
},
|
||||
})
|
||||
case fstree.Type:
|
||||
sub := fstreeconfig.From((*config.Config)(storage))
|
||||
fstreeOpts := []fstree.Option{
|
||||
fstree.WithPath(storage.Path()),
|
||||
fstree.WithPerm(storage.Perm()),
|
||||
fstree.WithDepth(sub.Depth()),
|
||||
fstree.WithNoSync(sub.NoSync()),
|
||||
fstree.WithLogger(logger.NewLoggerWrapper(zap.NewNop())),
|
||||
}
|
||||
|
||||
ss = append(ss, blobstor.SubStorage{
|
||||
Storage: fstree.New(fstreeOpts...),
|
||||
Policy: func(_ *objectSDK.Object, _ []byte) bool {
|
||||
return true
|
||||
},
|
||||
})
|
||||
default:
|
||||
// should never happen, that has already
|
||||
// been handled: when the config was read
|
||||
}
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
type epochState struct{}
|
||||
|
||||
func (epochState) CurrentEpoch() uint64 {
|
||||
return 0
|
||||
}
|
|
@ -28,6 +28,7 @@ const (
|
|||
var (
|
||||
errNoPathsFound = errors.New("no metabase paths found")
|
||||
errNoMorphEndpointsFound = errors.New("no morph endpoints found")
|
||||
errUpgradeFailed = errors.New("upgrade failed")
|
||||
)
|
||||
|
||||
var UpgradeCmd = &cobra.Command{
|
||||
|
@ -91,14 +92,19 @@ func upgrade(cmd *cobra.Command, _ []string) error {
|
|||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
allSuccess := true
|
||||
for mb, ok := range result {
|
||||
if ok {
|
||||
cmd.Println(mb, ": success")
|
||||
} else {
|
||||
cmd.Println(mb, ": failed")
|
||||
allSuccess = false
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if allSuccess {
|
||||
return nil
|
||||
}
|
||||
return errUpgradeFailed
|
||||
}
|
||||
|
||||
func getMetabasePaths(appCfg *config.Config) ([]string, error) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package ape
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
|
@ -76,7 +78,8 @@ func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *he
|
|||
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||
|
||||
ac, err := helper.NewLocalActor(cmd, c, constants.ConsensusAccountName)
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
ac, err := helper.NewLocalActor(c, &helper.AlphabetWallets{Path: walletDir, Label: constants.ConsensusAccountName})
|
||||
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||
|
||||
var ch util.Uint160
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -161,9 +162,7 @@ func printAlphabetContractBalances(cmd *cobra.Command, c helper.Client, inv *inv
|
|||
helper.GetAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
assert.NoError(w.Err)
|
||||
|
||||
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
|
||||
if err != nil {
|
||||
|
@ -226,9 +225,7 @@ func fetchBalances(c *invoker.Invoker, gasHash util.Uint160, accounts []accBalan
|
|||
for i := range accounts {
|
||||
emit.AppCall(w.BinWriter, gasHash, "balanceOf", callflag.ReadStates, accounts[i].scriptHash)
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
assert.NoError(w.Err)
|
||||
|
||||
res, err := c.Run(w.Bytes())
|
||||
if err != nil || res.State != vmstate.Halt.String() || len(res.Stack) != len(accounts) {
|
||||
|
|
|
@ -65,14 +65,14 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
|||
nbuf := make([]byte, 8)
|
||||
copy(nbuf[:], v)
|
||||
n := binary.LittleEndian.Uint64(nbuf)
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "%s:\t%d (int)\n", k, n))
|
||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||
if len(v) == 0 || len(v) > 1 {
|
||||
return helper.InvalidConfigValueErr(k)
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "%s:\t%t (bool)\n", k, v[0] == 1))
|
||||
default:
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "%s:\t%s (hex)\n", k, hex.EncodeToString(v)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
|
@ -235,9 +236,7 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
|
|||
|
||||
putContainer(bw, ch, cnt)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
assert.NoError(bw.Err)
|
||||
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
|
@ -120,9 +121,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if writer.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||
}
|
||||
assert.NoError(writer.Err, "can't create deployment script")
|
||||
|
||||
if err := c.SendCommitteeTx(writer.Bytes(), false); err != nil {
|
||||
return err
|
||||
|
@ -173,9 +172,8 @@ func registerNNS(nnsCs *state.Contract, c *helper.InitializeContext, zone string
|
|||
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
|
||||
}
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
|
||||
} else if bw.Len() != start {
|
||||
assert.NoError(bw.Err, "can't create deployment script")
|
||||
if bw.Len() != start {
|
||||
writer.WriteBytes(bw.Bytes())
|
||||
emit.Opcodes(writer.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(writer.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
|
@ -219,8 +220,8 @@ func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
|
|||
if info.version == "" {
|
||||
info.version = "unknown"
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t(%s):\t%s\n",
|
||||
info.name, info.version, info.hash.StringLE())))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "%s\t(%s):\t%s\n",
|
||||
info.name, info.version, info.hash.StringLE()))
|
||||
}
|
||||
_ = tw.Flush()
|
||||
|
||||
|
@ -236,21 +237,17 @@ func fillContractVersion(cmd *cobra.Command, c helper.Client, infos []contractDu
|
|||
} else {
|
||||
sub.Reset()
|
||||
emit.AppCall(sub.BinWriter, infos[i].hash, "version", callflag.NoneFlag)
|
||||
if sub.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err))
|
||||
}
|
||||
assert.NoError(sub.Err, "can't create version script")
|
||||
|
||||
script := sub.Bytes()
|
||||
emit.Instruction(bw.BinWriter, opcode.TRY, []byte{byte(3 + len(script) + 2), 0})
|
||||
bw.BinWriter.WriteBytes(script)
|
||||
bw.WriteBytes(script)
|
||||
emit.Instruction(bw.BinWriter, opcode.ENDTRY, []byte{2 + 1})
|
||||
emit.Opcodes(bw.BinWriter, opcode.PUSH0)
|
||||
}
|
||||
}
|
||||
emit.Opcodes(bw.BinWriter, opcode.NOP) // for the last ENDTRY target
|
||||
if bw.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't create version script: %w", bw.Err))
|
||||
}
|
||||
assert.NoError(bw.Err, "can't create version script")
|
||||
|
||||
res, err := c.InvokeScript(bw.Bytes(), nil)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package frostfsid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
@ -33,11 +34,16 @@ const (
|
|||
subjectNameFlag = "subject-name"
|
||||
subjectKeyFlag = "subject-key"
|
||||
subjectAddressFlag = "subject-address"
|
||||
includeNamesFlag = "include-names"
|
||||
extendedFlag = "extended"
|
||||
groupNameFlag = "group-name"
|
||||
groupIDFlag = "group-id"
|
||||
|
||||
rootNamespacePlaceholder = "<root>"
|
||||
|
||||
keyFlag = "key"
|
||||
keyDescFlag = "Key for storing a value in the subject's KV storage"
|
||||
valueFlag = "value"
|
||||
valueDescFlag = "Value to be stored in the subject's KV storage"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -151,6 +157,23 @@ var (
|
|||
},
|
||||
Run: frostfsidListGroupSubjects,
|
||||
}
|
||||
|
||||
frostfsidSetKVCmd = &cobra.Command{
|
||||
Use: "set-kv",
|
||||
Short: "Store a key-value pair in the subject's KV storage",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidSetKV,
|
||||
}
|
||||
frostfsidDeleteKVCmd = &cobra.Command{
|
||||
Use: "delete-kv",
|
||||
Short: "Delete a value from the subject's KV storage",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
},
|
||||
Run: frostfsidDeleteKV,
|
||||
}
|
||||
)
|
||||
|
||||
func initFrostfsIDCreateNamespaceCmd() {
|
||||
|
@ -186,7 +209,7 @@ func initFrostfsIDListSubjectsCmd() {
|
|||
Cmd.AddCommand(frostfsidListSubjectsCmd)
|
||||
frostfsidListSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
||||
frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListSubjectsCmd.Flags().Bool(extendedFlag, false, "Whether include subject info (require additional requests)")
|
||||
}
|
||||
|
||||
func initFrostfsIDCreateGroupCmd() {
|
||||
|
@ -233,7 +256,22 @@ func initFrostfsIDListGroupSubjectsCmd() {
|
|||
frostfsidListGroupSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)")
|
||||
frostfsidListGroupSubjectsCmd.Flags().Bool(extendedFlag, false, "Whether include subject info (require additional requests)")
|
||||
}
|
||||
|
||||
func initFrostfsIDSetKVCmd() {
|
||||
Cmd.AddCommand(frostfsidSetKVCmd)
|
||||
frostfsidSetKVCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidSetKVCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidSetKVCmd.Flags().String(keyFlag, "", keyDescFlag)
|
||||
frostfsidSetKVCmd.Flags().String(valueFlag, "", valueDescFlag)
|
||||
}
|
||||
|
||||
func initFrostfsIDDeleteKVCmd() {
|
||||
Cmd.AddCommand(frostfsidDeleteKVCmd)
|
||||
frostfsidDeleteKVCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
frostfsidDeleteKVCmd.Flags().String(subjectAddressFlag, "", "Subject address")
|
||||
frostfsidDeleteKVCmd.Flags().String(keyFlag, "", keyDescFlag)
|
||||
}
|
||||
|
||||
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {
|
||||
|
@ -253,7 +291,7 @@ func frostfsidListNamespaces(cmd *cobra.Command, _ []string) {
|
|||
reader := frostfsidrpclient.NewReader(inv, hash)
|
||||
sessionID, it, err := reader.ListNamespaces()
|
||||
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
|
||||
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
|
||||
items, err := readIterator(inv, &it, sessionID)
|
||||
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
|
||||
|
||||
namespaces, err := frostfsidclient.ParseNamespaces(items)
|
||||
|
@ -298,34 +336,32 @@ func frostfsidDeleteSubject(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func frostfsidListSubjects(cmd *cobra.Command, _ []string) {
|
||||
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
|
||||
extended, _ := cmd.Flags().GetBool(extendedFlag)
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
inv, _, hash := initInvoker(cmd)
|
||||
reader := frostfsidrpclient.NewReader(inv, hash)
|
||||
sessionID, it, err := reader.ListNamespaceSubjects(ns)
|
||||
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
|
||||
|
||||
subAddresses, err := frostfsidclient.UnwrapArrayOfUint160(readIterator(inv, &it, iteratorBatchSize, sessionID))
|
||||
subAddresses, err := frostfsidclient.UnwrapArrayOfUint160(readIterator(inv, &it, sessionID))
|
||||
commonCmd.ExitOnErr(cmd, "can't unwrap: %w", err)
|
||||
|
||||
sort.Slice(subAddresses, func(i, j int) bool { return subAddresses[i].Less(subAddresses[j]) })
|
||||
|
||||
for _, addr := range subAddresses {
|
||||
if !includeNames {
|
||||
if !extended {
|
||||
cmd.Println(address.Uint160ToString(addr))
|
||||
continue
|
||||
}
|
||||
|
||||
sessionID, it, err := reader.ListSubjects()
|
||||
items, err := reader.GetSubject(addr)
|
||||
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
|
||||
|
||||
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
|
||||
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
|
||||
|
||||
subj, err := frostfsidclient.ParseSubject(items)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse subject: %w", err)
|
||||
|
||||
cmd.Printf("%s (%s)\n", address.Uint160ToString(addr), subj.Name)
|
||||
printSubjectInfo(cmd, addr, subj)
|
||||
cmd.Println()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,7 +401,7 @@ func frostfsidListGroups(cmd *cobra.Command, _ []string) {
|
|||
sessionID, it, err := reader.ListGroups(ns)
|
||||
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
|
||||
|
||||
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
|
||||
items, err := readIterator(inv, &it, sessionID)
|
||||
commonCmd.ExitOnErr(cmd, "can't list groups: %w", err)
|
||||
groups, err := frostfsidclient.ParseGroups(items)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse groups: %w", err)
|
||||
|
@ -403,10 +439,49 @@ func frostfsidRemoveSubjectFromGroup(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "remove subject from group error: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidSetKV(cmd *cobra.Command, _ []string) {
|
||||
subjectAddress := getFrostfsIDSubjectAddress(cmd)
|
||||
key, _ := cmd.Flags().GetString(keyFlag)
|
||||
value, _ := cmd.Flags().GetString(valueFlag)
|
||||
|
||||
if key == "" {
|
||||
commonCmd.ExitOnErr(cmd, "", errors.New("key can't be empty"))
|
||||
}
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
method, args := ffsid.roCli.SetSubjectKVCall(subjectAddress, key, value)
|
||||
|
||||
ffsid.addCall(method, args)
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "set KV: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidDeleteKV(cmd *cobra.Command, _ []string) {
|
||||
subjectAddress := getFrostfsIDSubjectAddress(cmd)
|
||||
key, _ := cmd.Flags().GetString(keyFlag)
|
||||
|
||||
if key == "" {
|
||||
commonCmd.ExitOnErr(cmd, "", errors.New("key can't be empty"))
|
||||
}
|
||||
|
||||
ffsid, err := newFrostfsIDClient(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "init contract client: %w", err)
|
||||
|
||||
method, args := ffsid.roCli.DeleteSubjectKVCall(subjectAddress, key)
|
||||
|
||||
ffsid.addCall(method, args)
|
||||
|
||||
err = ffsid.sendWait()
|
||||
commonCmd.ExitOnErr(cmd, "delete KV: %w", err)
|
||||
}
|
||||
|
||||
func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
||||
ns := getFrostfsIDNamespace(cmd)
|
||||
groupID := getFrostfsIDGroupID(cmd)
|
||||
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
|
||||
extended, _ := cmd.Flags().GetBool(extendedFlag)
|
||||
inv, cs, hash := initInvoker(cmd)
|
||||
_, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.FrostfsIDContract))
|
||||
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||
|
@ -415,7 +490,7 @@ func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
|||
sessionID, it, err := reader.ListGroupSubjects(ns, big.NewInt(groupID))
|
||||
commonCmd.ExitOnErr(cmd, "can't list groups: %w", err)
|
||||
|
||||
items, err := readIterator(inv, &it, iteratorBatchSize, sessionID)
|
||||
items, err := readIterator(inv, &it, sessionID)
|
||||
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
|
||||
|
||||
subjects, err := frostfsidclient.UnwrapArrayOfUint160(items, err)
|
||||
|
@ -424,7 +499,7 @@ func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
|||
sort.Slice(subjects, func(i, j int) bool { return subjects[i].Less(subjects[j]) })
|
||||
|
||||
for _, subjAddr := range subjects {
|
||||
if !includeNames {
|
||||
if !extended {
|
||||
cmd.Println(address.Uint160ToString(subjAddr))
|
||||
continue
|
||||
}
|
||||
|
@ -433,7 +508,8 @@ func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
|
||||
subj, err := frostfsidclient.ParseSubject(items)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse subject: %w", err)
|
||||
cmd.Printf("%s (%s)\n", address.Uint160ToString(subjAddr), subj.Name)
|
||||
printSubjectInfo(cmd, subjAddr, subj)
|
||||
cmd.Println()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,17 +568,17 @@ func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
|||
return f.roCli.Wait(f.wCtx.SentTxs[0].Hash, f.wCtx.SentTxs[0].Vub, nil)
|
||||
}
|
||||
|
||||
func readIterator(inv *invoker.Invoker, iter *result.Iterator, batchSize int, sessionID uuid.UUID) ([]stackitem.Item, error) {
|
||||
func readIterator(inv *invoker.Invoker, iter *result.Iterator, sessionID uuid.UUID) ([]stackitem.Item, error) {
|
||||
var shouldStop bool
|
||||
res := make([]stackitem.Item, 0)
|
||||
for !shouldStop {
|
||||
items, err := inv.TraverseIterator(sessionID, iter, batchSize)
|
||||
items, err := inv.TraverseIterator(sessionID, iter, iteratorBatchSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, items...)
|
||||
shouldStop = len(items) < batchSize
|
||||
shouldStop = len(items) < iteratorBatchSize
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
@ -523,3 +599,30 @@ func initInvoker(cmd *cobra.Command) (*invoker.Invoker, *state.Contract, util.Ui
|
|||
|
||||
return inv, cs, nmHash
|
||||
}
|
||||
|
||||
func printSubjectInfo(cmd *cobra.Command, addr util.Uint160, subj *frostfsidclient.Subject) {
|
||||
cmd.Printf("Address: %s\n", address.Uint160ToString(addr))
|
||||
pk := "<nil>"
|
||||
if subj.PrimaryKey != nil {
|
||||
pk = subj.PrimaryKey.String()
|
||||
}
|
||||
cmd.Printf("Primary key: %s\n", pk)
|
||||
cmd.Printf("Name: %s\n", subj.Name)
|
||||
cmd.Printf("Namespace: %s\n", subj.Namespace)
|
||||
if len(subj.AdditionalKeys) > 0 {
|
||||
cmd.Printf("Additional keys:\n")
|
||||
for _, key := range subj.AdditionalKeys {
|
||||
k := "<nil>"
|
||||
if key != nil {
|
||||
k = key.String()
|
||||
}
|
||||
cmd.Printf("- %s\n", k)
|
||||
}
|
||||
}
|
||||
if len(subj.KV) > 0 {
|
||||
cmd.Printf("KV:\n")
|
||||
for k, v := range subj.KV {
|
||||
cmd.Printf("- %s: %s\n", k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ func init() {
|
|||
initFrostfsIDAddSubjectToGroupCmd()
|
||||
initFrostfsIDRemoveSubjectFromGroupCmd()
|
||||
initFrostfsIDListGroupSubjectsCmd()
|
||||
initFrostfsIDSetKVCmd()
|
||||
initFrostfsIDDeleteKVCmd()
|
||||
initFrostfsIDAddSubjectKeyCmd()
|
||||
initFrostfsIDRemoveSubjectKeyCmd()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
|
@ -141,60 +140,29 @@ func addMultisigAccount(w *wallet.Wallet, m int, name, password string, pubs key
|
|||
}
|
||||
|
||||
func generateStorageCreds(cmd *cobra.Command, _ []string) error {
|
||||
return refillGas(cmd, storageGasConfigFlag, true)
|
||||
}
|
||||
|
||||
func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error) {
|
||||
// storage wallet path is not part of the config
|
||||
storageWalletPath, _ := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||
// wallet address is not part of the config
|
||||
walletAddress, _ := cmd.Flags().GetString(walletAddressFlag)
|
||||
|
||||
var gasReceiver util.Uint160
|
||||
|
||||
if len(walletAddress) != 0 {
|
||||
gasReceiver, err = address.StringToUint160(walletAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid wallet address %s: %w", walletAddress, err)
|
||||
}
|
||||
} else {
|
||||
if storageWalletPath == "" {
|
||||
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
var w *wallet.Wallet
|
||||
|
||||
if createWallet {
|
||||
w, err = wallet.NewWallet(storageWalletPath)
|
||||
} else {
|
||||
w, err = wallet.NewWalletFromFile(storageWalletPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
|
||||
if createWallet {
|
||||
var password string
|
||||
|
||||
label, _ := cmd.Flags().GetString(storageWalletLabelFlag)
|
||||
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
if label == "" {
|
||||
label = constants.SingleAccountName
|
||||
}
|
||||
|
||||
if err := w.CreateAccount(label, password); err != nil {
|
||||
return fmt.Errorf("can't create account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
gasReceiver = w.Accounts[0].Contract.ScriptHash()
|
||||
walletPath, _ := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||
w, err := wallet.NewWallet(walletPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create wallet: %w", err)
|
||||
}
|
||||
|
||||
label, _ := cmd.Flags().GetString(storageWalletLabelFlag)
|
||||
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
if label == "" {
|
||||
label = constants.SingleAccountName
|
||||
}
|
||||
|
||||
if err := w.CreateAccount(label, password); err != nil {
|
||||
return fmt.Errorf("can't create account: %w", err)
|
||||
}
|
||||
return refillGas(cmd, storageGasConfigFlag, w.Accounts[0].ScriptHash())
|
||||
}
|
||||
|
||||
func refillGas(cmd *cobra.Command, gasFlag string, gasReceivers ...util.Uint160) (err error) {
|
||||
gasStr := viper.GetString(gasFlag)
|
||||
|
||||
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||
|
@ -208,9 +176,11 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
|||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
||||
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
for _, gasReceiver := range gasReceivers {
|
||||
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
||||
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
if bw.Err != nil {
|
||||
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -33,7 +38,27 @@ var (
|
|||
_ = viper.BindPFlag(commonflags.RefillGasAmountFlag, cmd.Flags().Lookup(commonflags.RefillGasAmountFlag))
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return refillGas(cmd, commonflags.RefillGasAmountFlag, false)
|
||||
storageWalletPaths, _ := cmd.Flags().GetStringArray(commonflags.StorageWalletFlag)
|
||||
walletAddresses, _ := cmd.Flags().GetStringArray(walletAddressFlag)
|
||||
|
||||
var gasReceivers []util.Uint160
|
||||
for _, walletAddress := range walletAddresses {
|
||||
addr, err := address.StringToUint160(walletAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid wallet address %s: %w", walletAddress, err)
|
||||
}
|
||||
|
||||
gasReceivers = append(gasReceivers, addr)
|
||||
}
|
||||
for _, storageWalletPath := range storageWalletPaths {
|
||||
w, err := wallet.NewWalletFromFile(storageWalletPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create wallet: %w", err)
|
||||
}
|
||||
|
||||
gasReceivers = append(gasReceivers, w.Accounts[0].Contract.ScriptHash())
|
||||
}
|
||||
return refillGas(cmd, commonflags.RefillGasAmountFlag, gasReceivers...)
|
||||
},
|
||||
}
|
||||
GenerateAlphabetCmd = &cobra.Command{
|
||||
|
@ -50,10 +75,10 @@ var (
|
|||
func initRefillGasCmd() {
|
||||
RefillGasCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
RefillGasCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RefillGasCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
RefillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
||||
RefillGasCmd.Flags().StringArray(commonflags.StorageWalletFlag, nil, "Path to storage node wallet")
|
||||
RefillGasCmd.Flags().StringArray(walletAddressFlag, nil, "Address of wallet")
|
||||
RefillGasCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
||||
RefillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, commonflags.StorageWalletFlag)
|
||||
RefillGasCmd.MarkFlagsOneRequired(walletAddressFlag, commonflags.StorageWalletFlag)
|
||||
}
|
||||
|
||||
func initGenerateStorageCmd() {
|
||||
|
|
|
@ -3,9 +3,6 @@ package helper
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
|
@ -16,7 +13,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
@ -28,32 +24,86 @@ type LocalActor struct {
|
|||
rpcInvoker invoker.RPCInvoke
|
||||
}
|
||||
|
||||
type AlphabetWallets struct {
|
||||
Label string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (a *AlphabetWallets) GetAccount(v *viper.Viper) ([]*wallet.Account, error) {
|
||||
w, err := GetAlphabetWallets(v, a.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var accounts []*wallet.Account
|
||||
for _, wall := range w {
|
||||
acc, err := GetWalletAccount(wall, a.Label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts = append(accounts, acc)
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
type RegularWallets struct{ Path string }
|
||||
|
||||
func (r *RegularWallets) GetAccount() ([]*wallet.Account, error) {
|
||||
w, err := getRegularWallet(r.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*wallet.Account{w.GetAccount(w.GetChangeAddress())}, nil
|
||||
}
|
||||
|
||||
// NewLocalActor create LocalActor with accounts form provided wallets.
|
||||
// In case of empty wallets provided created actor with dummy account only for read operation.
|
||||
//
|
||||
// If wallets are provided, the contract client will use accounts with accName name from these wallets.
|
||||
// To determine which account name should be used in a contract client, refer to how the contract
|
||||
// verifies the transaction signature.
|
||||
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor, accName string) (*LocalActor, error) {
|
||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
||||
func NewLocalActor(c actor.RPCActor, alphabet *AlphabetWallets, regularWallets ...*RegularWallets) (*LocalActor, error) {
|
||||
var act *actor.Actor
|
||||
var accounts []*wallet.Account
|
||||
var signers []actor.SignerAccount
|
||||
|
||||
wallets, err := GetAlphabetWallets(viper.GetViper(), walletDir)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
|
||||
if alphabet != nil {
|
||||
account, err := alphabet.GetAccount(viper.GetViper())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, w := range wallets {
|
||||
acc, err := GetWalletAccount(w, accName)
|
||||
commonCmd.ExitOnErr(cmd, fmt.Sprintf("can't find %s account: %%w", accName), err)
|
||||
accounts = append(accounts, acc)
|
||||
accounts = append(accounts, account...)
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: transaction.Signer{
|
||||
Account: account[0].Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: account[0],
|
||||
})
|
||||
}
|
||||
act, err = actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: accounts[0].Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: accounts[0],
|
||||
}})
|
||||
|
||||
for _, w := range regularWallets {
|
||||
if w == nil {
|
||||
continue
|
||||
}
|
||||
account, err := w.GetAccount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts = append(accounts, account...)
|
||||
signers = append(signers, actor.SignerAccount{
|
||||
Signer: transaction.Signer{
|
||||
Account: account[0].Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Account: account[0],
|
||||
})
|
||||
}
|
||||
|
||||
act, err := actor.New(c, signers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
nns2 "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
|
@ -13,9 +14,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
nns2 "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -187,19 +186,9 @@ func NNSResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*
|
|||
}
|
||||
|
||||
func NNSIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||
switch c.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c, nil)
|
||||
reader := nns2.NewReader(inv, nnsHash)
|
||||
return reader.IsAvailable(name)
|
||||
default:
|
||||
b, err := unwrap.Bool(InvokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("`isAvailable`: invalid response: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
inv := invoker.New(c, nil)
|
||||
reader := nns2.NewReader(inv, nnsHash)
|
||||
return reader.IsAvailable(name)
|
||||
}
|
||||
|
||||
func CheckNotaryEnabled(c Client) error {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
|
@ -28,7 +30,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -375,9 +376,7 @@ func (c *InitializeContext) sendMultiTx(script []byte, tryGroup bool, withConsen
|
|||
}
|
||||
act, err = actor.New(c.Client, signers)
|
||||
} else {
|
||||
if withConsensus {
|
||||
panic("BUG: should never happen")
|
||||
}
|
||||
assert.False(withConsensus, "BUG: should never happen")
|
||||
act, err = c.CommitteeAct, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -411,11 +410,9 @@ func (c *InitializeContext) MultiSignAndSend(tx *transaction.Transaction, accTyp
|
|||
|
||||
func (c *InitializeContext) MultiSign(tx *transaction.Transaction, accType string) error {
|
||||
version, err := c.Client.GetVersion()
|
||||
if err != nil {
|
||||
// error appears only if client
|
||||
// has not been initialized
|
||||
panic(err)
|
||||
}
|
||||
// error appears only if client
|
||||
// has not been initialized
|
||||
assert.NoError(err)
|
||||
network := version.Protocol.Network
|
||||
|
||||
// Use parameter context to avoid dealing with signature order.
|
||||
|
@ -447,12 +444,12 @@ func (c *InitializeContext) MultiSign(tx *transaction.Transaction, accType strin
|
|||
|
||||
for i := range tx.Signers {
|
||||
if tx.Signers[i].Account == h {
|
||||
assert.True(i <= len(tx.Scripts), "BUG: invalid signing order")
|
||||
if i < len(tx.Scripts) {
|
||||
tx.Scripts[i] = *w
|
||||
} else if i == len(tx.Scripts) {
|
||||
}
|
||||
if i == len(tx.Scripts) {
|
||||
tx.Scripts = append(tx.Scripts, *w)
|
||||
} else {
|
||||
panic("BUG: invalid signing order")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -510,9 +507,7 @@ func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.U
|
|||
int64(constants.DefaultExpirationTime), constants.NNSTtlDefVal)
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
if bw.Err != nil {
|
||||
panic(bw.Err)
|
||||
}
|
||||
assert.NoError(bw.Err)
|
||||
return bw.Bytes(), false, nil
|
||||
}
|
||||
|
||||
|
@ -524,12 +519,8 @@ func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.U
|
|||
}
|
||||
|
||||
func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
||||
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res.State == vmstate.Halt.String(), nil
|
||||
avail, err := unwrap.Bool(c.CommitteeAct.Call(nnsHash, "isAvailable", zone))
|
||||
return !avail, err
|
||||
}
|
||||
|
||||
func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *ContractState) bool {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
|
@ -316,9 +317,7 @@ func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint
|
|||
func (l *LocalClient) putTransactions() error {
|
||||
// 1. Prepare new block.
|
||||
lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.NoError(err)
|
||||
defer func() { l.transactions = l.transactions[:0] }()
|
||||
|
||||
b := &block.Block{
|
||||
|
@ -359,9 +358,7 @@ func InvokeFunction(c Client, h util.Uint160, method string, parameters []any, s
|
|||
w := io.NewBufBinWriter()
|
||||
emit.Array(w.BinWriter, parameters...)
|
||||
emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All)
|
||||
if w.Err != nil {
|
||||
panic(fmt.Sprintf("BUG: invalid parameters for '%s': %v", method, w.Err))
|
||||
}
|
||||
assert.True(w.Err == nil, fmt.Sprintf("BUG: invalid parameters for '%s': %v", method, w.Err))
|
||||
return c.InvokeScript(w.Bytes(), signers)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package helper
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
|
@ -118,11 +119,8 @@ func MergeNetmapConfig(roInvoker *invoker.Invoker, md map[string]any) error {
|
|||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
for _, key := range NetmapConfigKeys {
|
||||
if k == key {
|
||||
md[k] = v
|
||||
break
|
||||
}
|
||||
if slices.Contains(NetmapConfigKeys, k) {
|
||||
md[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
|
@ -22,6 +23,27 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getRegularWallet(walletPath string) (*wallet.Wallet, error) {
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
password, err := input.ReadPassword("Enter password for wallet:")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch password: %w", err)
|
||||
}
|
||||
|
||||
for i := range w.Accounts {
|
||||
if err = w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil {
|
||||
err = fmt.Errorf("can't unlock wallet: %w", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return w, err
|
||||
}
|
||||
|
||||
func GetAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||
wallets, err := openAlphabetWallets(v, walletDir)
|
||||
if err != nil {
|
||||
|
@ -51,7 +73,7 @@ func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, er
|
|||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
} else {
|
||||
err = fmt.Errorf("can't open wallet: %w", err)
|
||||
err = fmt.Errorf("can't open alphabet wallet: %w", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -111,9 +112,7 @@ func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []b
|
|||
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
|
||||
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
|
||||
|
||||
if w.Err != nil {
|
||||
panic(fmt.Errorf("BUG: can't wrap register script: %w", w.Err))
|
||||
}
|
||||
assert.NoError(w.Err, "can't wrap register script")
|
||||
}
|
||||
|
||||
func nnsRegisterDomain(c *helper.InitializeContext, nnsHash, expectedHash util.Uint160, domain string) error {
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
package initialize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -30,7 +27,8 @@ const (
|
|||
)
|
||||
|
||||
func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
|
||||
regPrice, err := getCandidateRegisterPrice(c)
|
||||
reader := neo.NewReader(c.ReadOnlyInvoker)
|
||||
regPrice, err := reader.GetRegisterPrice()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||
}
|
||||
|
@ -42,9 +40,7 @@ func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
|
|||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, regPrice)
|
||||
if w.Err != nil {
|
||||
panic(fmt.Sprintf("BUG: %v", w.Err))
|
||||
}
|
||||
assert.NoError(w.Err)
|
||||
|
||||
signers := []actor.SignerAccount{{
|
||||
Signer: c.GetSigner(false, c.CommitteeAcc),
|
||||
|
@ -116,7 +112,7 @@ func registerCandidates(c *helper.InitializeContext) error {
|
|||
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
||||
neoHash := neo.Hash
|
||||
|
||||
ok, err := transferNEOFinished(c, neoHash)
|
||||
ok, err := transferNEOFinished(c)
|
||||
if ok || err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -139,33 +135,8 @@ func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
|||
return c.AwaitTx()
|
||||
}
|
||||
|
||||
func transferNEOFinished(c *helper.InitializeContext, neoHash util.Uint160) (bool, error) {
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
|
||||
func transferNEOFinished(c *helper.InitializeContext) (bool, error) {
|
||||
r := neo.NewReader(c.ReadOnlyInvoker)
|
||||
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
||||
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
|
||||
}
|
||||
|
||||
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
||||
|
||||
func getCandidateRegisterPrice(c *helper.InitializeContext) (int64, error) {
|
||||
switch c.Client.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c.Client, nil)
|
||||
reader := neo.NewReader(inv)
|
||||
return reader.GetRegisterPrice()
|
||||
default:
|
||||
neoHash := neo.Hash
|
||||
res, err := helper.InvokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(res.Stack) == 0 {
|
||||
return 0, errGetPriceInvalid
|
||||
}
|
||||
bi, err := res.Stack[0].TryInteger()
|
||||
if err != nil || !bi.IsInt64() {
|
||||
return 0, errGetPriceInvalid
|
||||
}
|
||||
return bi.Int64(), nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,15 +22,14 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
gasInitialTotalSupply = 30000000 * native.GASFactor
|
||||
// initialAlphabetGASAmount represents the amount of GAS given to each alphabet node.
|
||||
initialAlphabetGASAmount = 10_000 * native.GASFactor
|
||||
// initialProxyGASAmount represents the amount of GAS given to a proxy contract.
|
||||
initialProxyGASAmount = 50_000 * native.GASFactor
|
||||
)
|
||||
|
||||
func initialCommitteeGASAmount(c *helper.InitializeContext) int64 {
|
||||
return (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2
|
||||
func initialCommitteeGASAmount(c *helper.InitializeContext, initialGasDistribution int64) int64 {
|
||||
return (initialGasDistribution - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2
|
||||
}
|
||||
|
||||
func transferFunds(c *helper.InitializeContext) error {
|
||||
|
@ -42,6 +41,11 @@ func transferFunds(c *helper.InitializeContext) error {
|
|||
return err
|
||||
}
|
||||
|
||||
version, err := c.Client.GetVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var transfers []transferTarget
|
||||
for _, acc := range c.Accounts {
|
||||
to := acc.Contract.ScriptHash()
|
||||
|
@ -59,7 +63,7 @@ func transferFunds(c *helper.InitializeContext) error {
|
|||
transferTarget{
|
||||
Token: gas.Hash,
|
||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||
Amount: initialCommitteeGASAmount(c),
|
||||
Amount: initialCommitteeGASAmount(c, int64(version.Protocol.InitialGasDistribution)),
|
||||
},
|
||||
transferTarget{
|
||||
Token: neo.Hash,
|
||||
|
@ -83,16 +87,23 @@ func transferFunds(c *helper.InitializeContext) error {
|
|||
// transferFundsFinished checks balances of accounts we transfer GAS to.
|
||||
// The stage is considered finished if the balance is greater than the half of what we need to transfer.
|
||||
func transferFundsFinished(c *helper.InitializeContext) (bool, error) {
|
||||
acc := c.Accounts[0]
|
||||
|
||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||
res, err := r.BalanceOf(acc.Contract.ScriptHash())
|
||||
if err != nil || res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) != 1 {
|
||||
res, err := r.BalanceOf(c.ConsensusAcc.ScriptHash())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
version, err := c.Client.GetVersion()
|
||||
if err != nil || res.Cmp(big.NewInt(int64(version.Protocol.InitialGasDistribution))) != -1 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
res, err = r.BalanceOf(c.CommitteeAcc.ScriptHash())
|
||||
return res != nil && res.Cmp(big.NewInt(initialCommitteeGASAmount(c)/2)) == 1, err
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res != nil && res.Cmp(big.NewInt(initialCommitteeGASAmount(c, int64(version.Protocol.InitialGasDistribution)))) == 1, err
|
||||
}
|
||||
|
||||
func transferGASToProxy(c *helper.InitializeContext) error {
|
||||
|
|
|
@ -6,7 +6,9 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func initRegisterCmd() {
|
||||
|
@ -19,6 +21,7 @@ func initRegisterCmd() {
|
|||
registerCmd.Flags().Int64(nnsRetryFlag, constants.NNSRetryDefVal, "SOA record RETRY parameter")
|
||||
registerCmd.Flags().Int64(nnsExpireFlag, int64(constants.DefaultExpirationTime), "SOA record EXPIRE parameter")
|
||||
registerCmd.Flags().Int64(nnsTTLFlag, constants.NNSTtlDefVal, "SOA record TTL parameter")
|
||||
registerCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||
|
||||
_ = cobra.MarkFlagRequired(registerCmd.Flags(), nnsNameFlag)
|
||||
}
|
||||
|
@ -48,6 +51,7 @@ func initDeleteCmd() {
|
|||
deleteCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
deleteCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
deleteCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
deleteCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||
|
||||
_ = cobra.MarkFlagRequired(deleteCmd.Flags(), nnsNameFlag)
|
||||
}
|
||||
|
@ -62,3 +66,28 @@ func deleteDomain(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "delete domain error: %w", err)
|
||||
cmd.Println("Domain deleted successfully")
|
||||
}
|
||||
|
||||
func initSetAdminCmd() {
|
||||
Cmd.AddCommand(setAdminCmd)
|
||||
setAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
setAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
setAdminCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
setAdminCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||
setAdminCmd.Flags().String(commonflags.AdminWalletPath, "", commonflags.AdminWalletUsage)
|
||||
_ = setAdminCmd.MarkFlagRequired(commonflags.AdminWalletPath)
|
||||
|
||||
_ = cobra.MarkFlagRequired(setAdminCmd.Flags(), nnsNameFlag)
|
||||
}
|
||||
|
||||
func setAdmin(cmd *cobra.Command, _ []string) {
|
||||
c, actor := nnsWriter(cmd)
|
||||
|
||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||
w, err := wallet.NewWalletFromFile(viper.GetString(commonflags.AdminWalletPath))
|
||||
commonCmd.ExitOnErr(cmd, "can't get admin wallet: %w", err)
|
||||
h, vub, err := c.SetAdmin(name, w.GetAccount(w.GetChangeAddress()).ScriptHash())
|
||||
|
||||
_, err = actor.Wait(h, vub, err)
|
||||
commonCmd.ExitOnErr(cmd, "Set admin error: %w", err)
|
||||
cmd.Println("Set admin successfully")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package nns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
|
@ -16,7 +20,32 @@ func nnsWriter(cmd *cobra.Command) (*client.Contract, *helper.LocalActor) {
|
|||
c, err := helper.NewRemoteClient(v)
|
||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||
|
||||
ac, err := helper.NewLocalActor(cmd, c, constants.CommitteeAccountName)
|
||||
alphabetWalletPath := config.ResolveHomePath(v.GetString(commonflags.AlphabetWalletsFlag))
|
||||
walletPath := config.ResolveHomePath(v.GetString(commonflags.WalletPath))
|
||||
adminWalletPath := config.ResolveHomePath(v.GetString(commonflags.AdminWalletPath))
|
||||
|
||||
var (
|
||||
alphabet *helper.AlphabetWallets
|
||||
regularWallets []*helper.RegularWallets
|
||||
)
|
||||
|
||||
if alphabetWalletPath != "" {
|
||||
alphabet = &helper.AlphabetWallets{Path: alphabetWalletPath, Label: constants.ConsensusAccountName}
|
||||
}
|
||||
|
||||
if walletPath != "" {
|
||||
regularWallets = append(regularWallets, &helper.RegularWallets{Path: walletPath})
|
||||
}
|
||||
|
||||
if adminWalletPath != "" {
|
||||
regularWallets = append(regularWallets, &helper.RegularWallets{Path: adminWalletPath})
|
||||
}
|
||||
|
||||
if alphabet == nil && regularWallets == nil {
|
||||
commonCmd.ExitOnErr(cmd, "", errors.New("no wallets provided"))
|
||||
}
|
||||
|
||||
ac, err := helper.NewLocalActor(c, alphabet, regularWallets...)
|
||||
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||
|
||||
r := management.NewReader(ac.Invoker)
|
||||
|
|
|
@ -19,6 +19,7 @@ func initAddRecordCmd() {
|
|||
addRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
addRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||
addRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
||||
addRecordCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||
|
||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsNameFlag)
|
||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsRecordTypeFlag)
|
||||
|
@ -40,6 +41,7 @@ func initDelRecordsCmd() {
|
|||
delRecordsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
delRecordsCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
delRecordsCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||
delRecordsCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||
|
||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsNameFlag)
|
||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsRecordTypeFlag)
|
||||
|
@ -52,6 +54,7 @@ func initDelRecordCmd() {
|
|||
delRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||
delRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||
delRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
||||
delRecordCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||
|
||||
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsNameFlag)
|
||||
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordTypeFlag)
|
||||
|
|
|
@ -39,6 +39,7 @@ var (
|
|||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
},
|
||||
Run: registerDomain,
|
||||
}
|
||||
|
@ -48,6 +49,7 @@ var (
|
|||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
},
|
||||
Run: deleteDomain,
|
||||
}
|
||||
|
@ -75,6 +77,7 @@ var (
|
|||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
},
|
||||
Run: addRecord,
|
||||
}
|
||||
|
@ -92,6 +95,7 @@ var (
|
|||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
},
|
||||
Run: delRecords,
|
||||
}
|
||||
|
@ -101,9 +105,21 @@ var (
|
|||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
},
|
||||
Run: delRecord,
|
||||
}
|
||||
setAdminCmd = &cobra.Command{
|
||||
Use: "set-admin",
|
||||
Short: "Sets admin for domain",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
_ = viper.BindPFlag(commonflags.AdminWalletPath, cmd.Flags().Lookup(commonflags.AdminWalletPath))
|
||||
},
|
||||
Run: setAdmin,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -116,4 +132,5 @@ func init() {
|
|||
initGetRecordsCmd()
|
||||
initDelRecordsCmd()
|
||||
initDelRecordCmd()
|
||||
initSetAdminCmd()
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||
|
@ -41,7 +40,8 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
accHash := w.GetChangeAddress()
|
||||
if addr, err := cmd.Flags().GetString(walletAccountFlag); err == nil {
|
||||
addr, _ := cmd.Flags().GetString(walletAccountFlag)
|
||||
if addr != "" {
|
||||
accHash, err = address.StringToUint160(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid address: %s", addr)
|
||||
|
@ -53,7 +53,7 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
return fmt.Errorf("can't find account for %s", accHash)
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf("Enter password for %s >", address.Uint160ToString(accHash))
|
||||
prompt := fmt.Sprintf("Enter password for %s > ", address.Uint160ToString(accHash))
|
||||
pass, err := input.ReadPassword(prompt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get password: %v", err)
|
||||
|
@ -73,16 +73,9 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
till := int64(defaultNotaryDepositLifetime)
|
||||
tillStr, err := cmd.Flags().GetString(notaryDepositTillFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tillStr != "" {
|
||||
till, err = strconv.ParseInt(tillStr, 10, 64)
|
||||
if err != nil || till <= 0 {
|
||||
return errInvalidNotaryDepositLifetime
|
||||
}
|
||||
till, _ := cmd.Flags().GetInt64(notaryDepositTillFlag)
|
||||
if till <= 0 {
|
||||
return errInvalidNotaryDepositLifetime
|
||||
}
|
||||
|
||||
return transferGas(cmd, acc, accHash, gasAmount, till)
|
||||
|
|
|
@ -20,7 +20,7 @@ func initDepositoryNotaryCmd() {
|
|||
DepositCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||
DepositCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
|
||||
DepositCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Amount of GAS to deposit")
|
||||
DepositCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
||||
DepositCmd.Flags().Int64(notaryDepositTillFlag, defaultNotaryDepositLifetime, "Notary deposit duration in blocks")
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -80,9 +80,9 @@ func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
|||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Execution Fee Factor:\t%d (int)\n", execFee)))
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Fee Per Byte:\t%d (int)\n", feePerByte)))
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Storage Price:\t%d (int)\n", storagePrice)))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "Execution Fee Factor:\t%d (int)\n", execFee))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "Fee Per Byte:\t%d (int)\n", feePerByte))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "Storage Price:\t%d (int)\n", storagePrice))
|
||||
|
||||
_ = tw.Flush()
|
||||
cmd.Print(buf.String())
|
||||
|
|
|
@ -20,23 +20,32 @@ const (
|
|||
accountAddressFlag = "account"
|
||||
)
|
||||
|
||||
func parseAddresses(cmd *cobra.Command) []util.Uint160 {
|
||||
var addrs []util.Uint160
|
||||
|
||||
accs, _ := cmd.Flags().GetStringArray(accountAddressFlag)
|
||||
for _, acc := range accs {
|
||||
addr, err := address.StringToUint160(acc)
|
||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
||||
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func addProxyAccount(cmd *cobra.Command, _ []string) {
|
||||
acc, _ := cmd.Flags().GetString(accountAddressFlag)
|
||||
addr, err := address.StringToUint160(acc)
|
||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
||||
err = processAccount(cmd, addr, "addAccount")
|
||||
addrs := parseAddresses(cmd)
|
||||
err := processAccount(cmd, addrs, "addAccount")
|
||||
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
||||
}
|
||||
|
||||
func removeProxyAccount(cmd *cobra.Command, _ []string) {
|
||||
acc, _ := cmd.Flags().GetString(accountAddressFlag)
|
||||
addr, err := address.StringToUint160(acc)
|
||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
||||
err = processAccount(cmd, addr, "removeAccount")
|
||||
addrs := parseAddresses(cmd)
|
||||
err := processAccount(cmd, addrs, "removeAccount")
|
||||
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
||||
}
|
||||
|
||||
func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error {
|
||||
func processAccount(cmd *cobra.Command, addrs []util.Uint160, method string) error {
|
||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't initialize context: %w", err)
|
||||
|
@ -54,7 +63,9 @@ func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error
|
|||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
||||
for _, addr := range addrs {
|
||||
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
||||
}
|
||||
|
||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||
return err
|
||||
|
|
|
@ -29,13 +29,15 @@ var (
|
|||
|
||||
func initProxyAddAccount() {
|
||||
AddAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
AddAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
AddAccountCmd.Flags().StringArray(accountAddressFlag, nil, "Wallet address string")
|
||||
_ = AddAccountCmd.MarkFlagRequired(accountAddressFlag)
|
||||
AddAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
func initProxyRemoveAccount() {
|
||||
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||
RemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||
RemoveAccountCmd.Flags().StringArray(accountAddressFlag, nil, "Wallet address string")
|
||||
_ = AddAccountCmd.MarkFlagRequired(accountAddressFlag)
|
||||
RemoveAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/maintenance"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/metabase"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||
|
@ -41,8 +41,8 @@ func init() {
|
|||
|
||||
rootCmd.AddCommand(config.RootCmd)
|
||||
rootCmd.AddCommand(morph.RootCmd)
|
||||
rootCmd.AddCommand(storagecfg.RootCmd)
|
||||
rootCmd.AddCommand(metabase.RootCmd)
|
||||
rootCmd.AddCommand(maintenance.RootCmd)
|
||||
|
||||
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
package storagecfg
|
||||
|
||||
const configTemplate = `logger:
|
||||
level: info # logger level: one of "debug", "info" (default), "warn", "error", "dpanic", "panic", "fatal"
|
||||
|
||||
node:
|
||||
wallet:
|
||||
path: {{ .Wallet.Path }} # path to a NEO wallet; ignored if key is presented
|
||||
address: {{ .Wallet.Account }} # address of a NEO account in the wallet; ignored if key is presented
|
||||
password: {{ .Wallet.Password }} # password for a NEO account in the wallet; ignored if key is presented
|
||||
addresses: # list of addresses announced by Storage node in the Network map
|
||||
- {{ .AnnouncedAddress }}
|
||||
attribute_0: UN-LOCODE:{{ .Attribute.Locode }}
|
||||
relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map
|
||||
|
||||
grpc:
|
||||
num: 1 # total number of listener endpoints
|
||||
0:
|
||||
endpoint: {{ .Endpoint }} # endpoint for gRPC server
|
||||
tls:{{if .TLSCert}}
|
||||
enabled: true # enable TLS for a gRPC connection (min version is TLS 1.2)
|
||||
certificate: {{ .TLSCert }} # path to TLS certificate
|
||||
key: {{ .TLSKey }} # path to TLS key
|
||||
{{- else }}
|
||||
enabled: false # disable TLS for a gRPC connection
|
||||
{{- end}}
|
||||
|
||||
control:
|
||||
authorized_keys: # list of hex-encoded public keys that have rights to use the Control Service
|
||||
{{- range .AuthorizedKeys }}
|
||||
- {{.}}{{end}}
|
||||
grpc:
|
||||
endpoint: {{.ControlEndpoint}} # endpoint that is listened by the Control Service
|
||||
|
||||
morph:
|
||||
dial_timeout: 20s # timeout for side chain NEO RPC client connection
|
||||
cache_ttl: 15s # use TTL cache for side chain GET operations
|
||||
rpc_endpoint: # side chain N3 RPC endpoints
|
||||
{{- range .MorphRPC }}
|
||||
- address: wss://{{.}}/ws{{end}}
|
||||
{{if not .Relay }}
|
||||
storage:
|
||||
shard_pool_size: 15 # size of per-shard worker pools used for PUT operations
|
||||
|
||||
shard:
|
||||
default: # section with the default shard parameters
|
||||
metabase:
|
||||
perm: 0644 # permissions for metabase files(directories: +x for current user and group)
|
||||
|
||||
blobstor:
|
||||
perm: 0644 # permissions for blobstor files(directories: +x for current user and group)
|
||||
depth: 2 # max depth of object tree storage in FS
|
||||
small_object_size: 102400 # 100KiB, size threshold for "small" objects which are stored in key-value DB, not in FS, bytes
|
||||
compress: true # turn on/off Zstandard compression (level 3) of stored objects
|
||||
compression_exclude_content_types:
|
||||
- audio/*
|
||||
- video/*
|
||||
|
||||
blobovnicza:
|
||||
size: 1073741824 # approximate size limit of single blobovnicza instance, total size will be: size*width^(depth+1), bytes
|
||||
depth: 1 # max depth of object tree storage in key-value DB
|
||||
width: 4 # max width of object tree storage in key-value DB
|
||||
opened_cache_capacity: 50 # maximum number of opened database files
|
||||
opened_cache_ttl: 5m # ttl for opened database file
|
||||
opened_cache_exp_interval: 15s # cache cleanup interval for expired blobovnicza's
|
||||
|
||||
gc:
|
||||
remover_batch_size: 200 # number of objects to be removed by the garbage collector
|
||||
remover_sleep_interval: 5m # frequency of the garbage collector invocation
|
||||
0:
|
||||
mode: "read-write" # mode of the shard, must be one of the: "read-write" (default), "read-only"
|
||||
|
||||
metabase:
|
||||
path: {{ .MetabasePath }} # path to the metabase
|
||||
|
||||
blobstor:
|
||||
path: {{ .BlobstorPath }} # path to the blobstor
|
||||
{{end}}`
|
||||
|
||||
const (
|
||||
neofsMainnetAddress = "2cafa46838e8b564468ebd868dcafdd99dce6221"
|
||||
balanceMainnetAddress = "dc1ec98d9d0c5f9dfade16144defe08cffc5ca55"
|
||||
neofsTestnetAddress = "b65d8243ac63983206d17e5221af0653a7266fa1"
|
||||
balanceTestnetAddress = "e0420c216003747626670d1424569c17c79015bf"
|
||||
)
|
||||
|
||||
var n3config = map[string]struct {
|
||||
MorphRPC []string
|
||||
RPC []string
|
||||
NeoFSContract string
|
||||
BalanceContract string
|
||||
}{
|
||||
"testnet": {
|
||||
MorphRPC: []string{
|
||||
"rpc01.morph.testnet.fs.neo.org:51331",
|
||||
"rpc02.morph.testnet.fs.neo.org:51331",
|
||||
"rpc03.morph.testnet.fs.neo.org:51331",
|
||||
"rpc04.morph.testnet.fs.neo.org:51331",
|
||||
"rpc05.morph.testnet.fs.neo.org:51331",
|
||||
"rpc06.morph.testnet.fs.neo.org:51331",
|
||||
"rpc07.morph.testnet.fs.neo.org:51331",
|
||||
},
|
||||
RPC: []string{
|
||||
"rpc01.testnet.n3.nspcc.ru:21331",
|
||||
"rpc02.testnet.n3.nspcc.ru:21331",
|
||||
"rpc03.testnet.n3.nspcc.ru:21331",
|
||||
"rpc04.testnet.n3.nspcc.ru:21331",
|
||||
"rpc05.testnet.n3.nspcc.ru:21331",
|
||||
"rpc06.testnet.n3.nspcc.ru:21331",
|
||||
"rpc07.testnet.n3.nspcc.ru:21331",
|
||||
},
|
||||
NeoFSContract: neofsTestnetAddress,
|
||||
BalanceContract: balanceTestnetAddress,
|
||||
},
|
||||
"mainnet": {
|
||||
MorphRPC: []string{
|
||||
"rpc1.morph.fs.neo.org:40341",
|
||||
"rpc2.morph.fs.neo.org:40341",
|
||||
"rpc3.morph.fs.neo.org:40341",
|
||||
"rpc4.morph.fs.neo.org:40341",
|
||||
"rpc5.morph.fs.neo.org:40341",
|
||||
"rpc6.morph.fs.neo.org:40341",
|
||||
"rpc7.morph.fs.neo.org:40341",
|
||||
},
|
||||
RPC: []string{
|
||||
"rpc1.n3.nspcc.ru:10331",
|
||||
"rpc2.n3.nspcc.ru:10331",
|
||||
"rpc3.n3.nspcc.ru:10331",
|
||||
"rpc4.n3.nspcc.ru:10331",
|
||||
"rpc5.n3.nspcc.ru:10331",
|
||||
"rpc6.n3.nspcc.ru:10331",
|
||||
"rpc7.n3.nspcc.ru:10331",
|
||||
},
|
||||
NeoFSContract: neofsMainnetAddress,
|
||||
BalanceContract: balanceMainnetAddress,
|
||||
},
|
||||
}
|
|
@ -1,433 +0,0 @@
|
|||
package storagecfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
netutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
walletFlag = "wallet"
|
||||
accountFlag = "account"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultControlEndpoint = "localhost:8090"
|
||||
defaultDataEndpoint = "localhost"
|
||||
)
|
||||
|
||||
// RootCmd is a root command of config section.
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "storage-config [-w wallet] [-a acccount] [<path-to-config>]",
|
||||
Short: "Section for storage node configuration commands",
|
||||
Run: storageConfig,
|
||||
}
|
||||
|
||||
func init() {
|
||||
fs := RootCmd.Flags()
|
||||
|
||||
fs.StringP(walletFlag, "w", "", "Path to wallet")
|
||||
fs.StringP(accountFlag, "a", "", "Wallet account")
|
||||
}
|
||||
|
||||
type config struct {
|
||||
AnnouncedAddress string
|
||||
AuthorizedKeys []string
|
||||
ControlEndpoint string
|
||||
Endpoint string
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
MorphRPC []string
|
||||
Attribute struct {
|
||||
Locode string
|
||||
}
|
||||
Wallet struct {
|
||||
Path string
|
||||
Account string
|
||||
Password string
|
||||
}
|
||||
Relay bool
|
||||
BlobstorPath string
|
||||
MetabasePath string
|
||||
}
|
||||
|
||||
func storageConfig(cmd *cobra.Command, args []string) {
|
||||
outPath := getOutputPath(args)
|
||||
|
||||
historyPath := filepath.Join(os.TempDir(), "frostfs-adm.history")
|
||||
readline.SetHistoryPath(historyPath)
|
||||
|
||||
var c config
|
||||
|
||||
c.Wallet.Path, _ = cmd.Flags().GetString(walletFlag)
|
||||
if c.Wallet.Path == "" {
|
||||
c.Wallet.Path = getPath("Path to the storage node wallet: ")
|
||||
}
|
||||
|
||||
w, err := wallet.NewWalletFromFile(c.Wallet.Path)
|
||||
fatalOnErr(err)
|
||||
|
||||
fillWalletAccount(cmd, &c, w)
|
||||
|
||||
accH, err := flags.ParseAddress(c.Wallet.Account)
|
||||
fatalOnErr(err)
|
||||
|
||||
acc := w.GetAccount(accH)
|
||||
if acc == nil {
|
||||
fatalOnErr(errors.New("can't find account in wallet"))
|
||||
}
|
||||
|
||||
c.Wallet.Password, err = input.ReadPassword(fmt.Sprintf("Account password for %s: ", c.Wallet.Account))
|
||||
fatalOnErr(err)
|
||||
|
||||
err = acc.Decrypt(c.Wallet.Password, keys.NEP2ScryptParams())
|
||||
fatalOnErr(err)
|
||||
|
||||
c.AuthorizedKeys = append(c.AuthorizedKeys, hex.EncodeToString(acc.PrivateKey().PublicKey().Bytes()))
|
||||
|
||||
network := readNetwork(cmd)
|
||||
|
||||
c.MorphRPC = n3config[network].MorphRPC
|
||||
|
||||
depositGas(cmd, acc, network)
|
||||
|
||||
c.Attribute.Locode = getString("UN-LOCODE attribute in [XX YYY] format: ")
|
||||
|
||||
endpoint := getDefaultEndpoint(cmd, &c)
|
||||
c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", endpoint))
|
||||
if c.Endpoint == "" {
|
||||
c.Endpoint = endpoint
|
||||
}
|
||||
|
||||
c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint))
|
||||
if c.ControlEndpoint == "" {
|
||||
c.ControlEndpoint = defaultControlEndpoint
|
||||
}
|
||||
|
||||
c.TLSCert = getPath("TLS Certificate (optional): ")
|
||||
if c.TLSCert != "" {
|
||||
c.TLSKey = getPath("TLS Key: ")
|
||||
}
|
||||
|
||||
c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ")
|
||||
if !c.Relay {
|
||||
p := getPath("Path to the storage directory (all available storage will be used): ")
|
||||
c.BlobstorPath = filepath.Join(p, "blob")
|
||||
c.MetabasePath = filepath.Join(p, "meta")
|
||||
}
|
||||
|
||||
out := applyTemplate(c)
|
||||
fatalOnErr(os.WriteFile(outPath, out, 0o644))
|
||||
|
||||
cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`")
|
||||
}
|
||||
|
||||
func getDefaultEndpoint(cmd *cobra.Command, c *config) string {
|
||||
var addr, port string
|
||||
for {
|
||||
c.AnnouncedAddress = getString("Publicly announced address: ")
|
||||
validator := netutil.Address{}
|
||||
err := validator.FromString(c.AnnouncedAddress)
|
||||
if err != nil {
|
||||
cmd.Println("Incorrect address format. See https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/pkg/network/address.go for details.")
|
||||
continue
|
||||
}
|
||||
uriAddr, err := url.Parse(validator.URIAddr())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unexpected error: %w", err))
|
||||
}
|
||||
addr = uriAddr.Hostname()
|
||||
port = uriAddr.Port()
|
||||
ip, err := net.ResolveIPAddr("ip", addr)
|
||||
if err != nil {
|
||||
cmd.Printf("Can't resolve IP address %s: %v\n", addr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !ip.IP.IsGlobalUnicast() {
|
||||
cmd.Println("IP must be global unicast.")
|
||||
continue
|
||||
}
|
||||
cmd.Printf("Resolved IP address: %s\n", ip.String())
|
||||
|
||||
_, err = strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
cmd.Println("Port must be an integer.")
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return net.JoinHostPort(defaultDataEndpoint, port)
|
||||
}
|
||||
|
||||
func fillWalletAccount(cmd *cobra.Command, c *config, w *wallet.Wallet) {
|
||||
c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag)
|
||||
if c.Wallet.Account == "" {
|
||||
addr := address.Uint160ToString(w.GetChangeAddress())
|
||||
c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr))
|
||||
if c.Wallet.Account == "" {
|
||||
c.Wallet.Account = addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readNetwork(cmd *cobra.Command) string {
|
||||
var network string
|
||||
for {
|
||||
network = getString("Choose network [mainnet]/testnet: ")
|
||||
switch network {
|
||||
case "":
|
||||
network = "mainnet"
|
||||
case "testnet", "mainnet":
|
||||
default:
|
||||
cmd.Println(`Network must be either "mainnet" or "testnet"`)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return network
|
||||
}
|
||||
|
||||
func getOutputPath(args []string) string {
|
||||
if len(args) != 0 {
|
||||
return args[0]
|
||||
}
|
||||
outPath := getPath("File to write config at [./config.yml]: ")
|
||||
if outPath == "" {
|
||||
outPath = "./config.yml"
|
||||
}
|
||||
return outPath
|
||||
}
|
||||
|
||||
func getWalletAccount(w *wallet.Wallet, prompt string) string {
|
||||
addrs := make([]readline.PrefixCompleterInterface, len(w.Accounts))
|
||||
for i := range w.Accounts {
|
||||
addrs[i] = readline.PcItem(w.Accounts[i].Address)
|
||||
}
|
||||
|
||||
readline.SetAutoComplete(readline.NewPrefixCompleter(addrs...))
|
||||
defer readline.SetAutoComplete(nil)
|
||||
|
||||
s, err := readline.Line(prompt)
|
||||
fatalOnErr(err)
|
||||
return strings.TrimSpace(s) // autocompleter can return a string with a trailing space
|
||||
}
|
||||
|
||||
func getString(prompt string) string {
|
||||
s, err := readline.Line(prompt)
|
||||
fatalOnErr(err)
|
||||
if s != "" {
|
||||
_ = readline.AddHistory(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type filenameCompleter struct{}
|
||||
|
||||
func (filenameCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
|
||||
prefix := string(line[:pos])
|
||||
dir := filepath.Dir(prefix)
|
||||
de, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
for i := range de {
|
||||
name := filepath.Join(dir, de[i].Name())
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
tail := []rune(strings.TrimPrefix(name, prefix))
|
||||
if de[i].IsDir() {
|
||||
tail = append(tail, filepath.Separator)
|
||||
}
|
||||
newLine = append(newLine, tail)
|
||||
}
|
||||
}
|
||||
if pos != 0 {
|
||||
return newLine, pos - len([]rune(dir))
|
||||
}
|
||||
return newLine, 0
|
||||
}
|
||||
|
||||
func getPath(prompt string) string {
|
||||
readline.SetAutoComplete(filenameCompleter{})
|
||||
defer readline.SetAutoComplete(nil)
|
||||
|
||||
p, err := readline.Line(prompt)
|
||||
fatalOnErr(err)
|
||||
|
||||
if p == "" {
|
||||
return p
|
||||
}
|
||||
|
||||
_ = readline.AddHistory(p)
|
||||
|
||||
abs, err := filepath.Abs(p)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("can't create an absolute path: %w", err))
|
||||
}
|
||||
|
||||
return abs
|
||||
}
|
||||
|
||||
func getConfirmation(def bool, prompt string) bool {
|
||||
for {
|
||||
s, err := readline.Line(prompt)
|
||||
fatalOnErr(err)
|
||||
|
||||
switch strings.ToLower(s) {
|
||||
case "y", "yes":
|
||||
return true
|
||||
case "n", "no":
|
||||
return false
|
||||
default:
|
||||
if len(s) == 0 {
|
||||
return def
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyTemplate(c config) []byte {
|
||||
tmpl, err := template.New("config").Parse(configTemplate)
|
||||
fatalOnErr(err)
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
fatalOnErr(tmpl.Execute(b, c))
|
||||
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func fatalOnErr(err error) {
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func depositGas(cmd *cobra.Command, acc *wallet.Account, network string) {
|
||||
sideClient := initClient(n3config[network].MorphRPC)
|
||||
balanceHash, _ := util.Uint160DecodeStringLE(n3config[network].BalanceContract)
|
||||
|
||||
sideActor, err := actor.NewSimple(sideClient, acc)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("creating actor over side chain client: %w", err))
|
||||
}
|
||||
|
||||
sideGas := nep17.NewReader(sideActor, balanceHash)
|
||||
accSH := acc.Contract.ScriptHash()
|
||||
|
||||
balance, err := sideGas.BalanceOf(accSH)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("side chain balance: %w", err))
|
||||
}
|
||||
|
||||
ok := getConfirmation(false, fmt.Sprintf("Current NeoFS balance is %s, make a deposit? y/[n]: ",
|
||||
fixedn.ToString(balance, 12)))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
amountStr := getString("Enter amount in GAS: ")
|
||||
amount, err := fixedn.FromString(amountStr, 8)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("invalid amount: %w", err))
|
||||
}
|
||||
|
||||
mainClient := initClient(n3config[network].RPC)
|
||||
neofsHash, _ := util.Uint160DecodeStringLE(n3config[network].NeoFSContract)
|
||||
|
||||
mainActor, err := actor.NewSimple(mainClient, acc)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("creating actor over main chain client: %w", err))
|
||||
}
|
||||
|
||||
mainGas := nep17.New(mainActor, gas.Hash)
|
||||
|
||||
txHash, _, err := mainGas.Transfer(accSH, neofsHash, amount, nil)
|
||||
if err != nil {
|
||||
fatalOnErr(fmt.Errorf("sending TX to the NeoFS contract: %w", err))
|
||||
}
|
||||
|
||||
cmd.Print("Waiting for transactions to persist.")
|
||||
tick := time.NewTicker(time.Second / 2)
|
||||
defer tick.Stop()
|
||||
|
||||
timer := time.NewTimer(time.Second * 20)
|
||||
defer timer.Stop()
|
||||
|
||||
at := trigger.Application
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
_, err := mainClient.GetApplicationLog(txHash, &at)
|
||||
if err == nil {
|
||||
cmd.Print("\n")
|
||||
break loop
|
||||
}
|
||||
cmd.Print(".")
|
||||
case <-timer.C:
|
||||
cmd.Printf("\nTimeout while waiting for transaction to persist.\n")
|
||||
if getConfirmation(false, "Continue configuration? yes/[no]: ") {
|
||||
return
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initClient(rpc []string) *rpcclient.Client {
|
||||
var c *rpcclient.Client
|
||||
var err error
|
||||
|
||||
shuffled := make([]string, len(rpc))
|
||||
copy(shuffled, rpc)
|
||||
rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] })
|
||||
|
||||
for _, endpoint := range shuffled {
|
||||
c, err = rpcclient.New(context.Background(), "https://"+endpoint, rpcclient.Options{
|
||||
DialTimeout: time.Second * 2,
|
||||
RequestTimeout: time.Second * 5,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err = c.Init(); err != nil {
|
||||
continue
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
fatalOnErr(fmt.Errorf("can't create N3 client: %w", err))
|
||||
panic("unreachable")
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
|
@ -77,9 +76,7 @@ func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContain
|
|||
// SortedIDList returns sorted list of identifiers of user's containers.
|
||||
func (x ListContainersRes) SortedIDList() []cid.ID {
|
||||
list := x.cliRes.Containers()
|
||||
slices.SortFunc(list, func(lhs, rhs cid.ID) int {
|
||||
return strings.Compare(lhs.EncodeToString(), rhs.EncodeToString())
|
||||
})
|
||||
slices.SortFunc(list, cid.ID.Cmp)
|
||||
return list
|
||||
}
|
||||
|
||||
|
@ -687,9 +684,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes
|
|||
return nil, fmt.Errorf("read object list: %w", err)
|
||||
}
|
||||
|
||||
slices.SortFunc(list, func(a, b oid.ID) int {
|
||||
return strings.Compare(a.EncodeToString(), b.EncodeToString())
|
||||
})
|
||||
slices.SortFunc(list, oid.ID.Cmp)
|
||||
|
||||
return &SearchObjectsRes{
|
||||
ids: list,
|
||||
|
@ -863,6 +858,8 @@ type PatchObjectPrm struct {
|
|||
|
||||
ReplaceAttribute bool
|
||||
|
||||
NewSplitHeader *objectSDK.SplitHeader
|
||||
|
||||
PayloadPatches []PayloadPatch
|
||||
}
|
||||
|
||||
|
@ -893,7 +890,11 @@ func Patch(ctx context.Context, prm PatchObjectPrm) (*PatchRes, error) {
|
|||
return nil, fmt.Errorf("init payload reading: %w", err)
|
||||
}
|
||||
|
||||
if patcher.PatchAttributes(ctx, prm.NewAttributes, prm.ReplaceAttribute) {
|
||||
if patcher.PatchHeader(ctx, client.PatchHeaderPrm{
|
||||
NewSplitHeader: prm.NewSplitHeader,
|
||||
NewAttributes: prm.NewAttributes,
|
||||
ReplaceAttributes: prm.ReplaceAttribute,
|
||||
}) {
|
||||
for _, pp := range prm.PayloadPatches {
|
||||
payloadFile, err := os.OpenFile(pp.PayloadPath, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
|
|
|
@ -56,7 +56,7 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
|
|||
prmDial := client.PrmDial{
|
||||
Endpoint: addr.URIAddr(),
|
||||
GRPCDialOptions: []grpc.DialOption{
|
||||
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
||||
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInterceptor()),
|
||||
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
||||
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
||||
},
|
||||
|
|
|
@ -28,7 +28,7 @@ const (
|
|||
RPC = "rpc-endpoint"
|
||||
RPCShorthand = "r"
|
||||
RPCDefault = ""
|
||||
RPCUsage = "Remote node address (as 'multiaddr' or '<host>:<port>')"
|
||||
RPCUsage = "Remote node address ('<host>:<port>' or 'grpcs://<host>:<port>')"
|
||||
|
||||
Timeout = "timeout"
|
||||
TimeoutShorthand = "t"
|
||||
|
|
|
@ -52,7 +52,7 @@ func genereateAPEOverride(cmd *cobra.Command, _ []string) {
|
|||
|
||||
outputPath, _ := cmd.Flags().GetString(outputFlag)
|
||||
if outputPath != "" {
|
||||
err := os.WriteFile(outputPath, []byte(overrideMarshalled), 0o644)
|
||||
err := os.WriteFile(outputPath, overrideMarshalled, 0o644)
|
||||
commonCmd.ExitOnErr(cmd, "dump error: %w", err)
|
||||
} else {
|
||||
fmt.Print("\n")
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
|
@ -19,15 +21,16 @@ import (
|
|||
)
|
||||
|
||||
type policyPlaygroundREPL struct {
|
||||
cmd *cobra.Command
|
||||
nodes map[string]netmap.NodeInfo
|
||||
cmd *cobra.Command
|
||||
nodes map[string]netmap.NodeInfo
|
||||
console *readline.Instance
|
||||
}
|
||||
|
||||
func newPolicyPlaygroundREPL(cmd *cobra.Command) (*policyPlaygroundREPL, error) {
|
||||
func newPolicyPlaygroundREPL(cmd *cobra.Command) *policyPlaygroundREPL {
|
||||
return &policyPlaygroundREPL{
|
||||
cmd: cmd,
|
||||
nodes: map[string]netmap.NodeInfo{},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleLs(args []string) error {
|
||||
|
@ -40,7 +43,7 @@ func (repl *policyPlaygroundREPL) handleLs(args []string) error {
|
|||
node.IterateAttributes(func(k, v string) {
|
||||
attrs = append(attrs, fmt.Sprintf("%s:%q", k, v))
|
||||
})
|
||||
fmt.Printf("\t%2d: id=%s attrs={%v}\n", i, id, strings.Join(attrs, " "))
|
||||
fmt.Fprintf(repl.console, "\t%2d: id=%s attrs={%v}\n", i, id, strings.Join(attrs, " "))
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
|
@ -147,12 +150,29 @@ func (repl *policyPlaygroundREPL) handleEval(args []string) error {
|
|||
for _, node := range ns {
|
||||
ids = append(ids, hex.EncodeToString(node.PublicKey()))
|
||||
}
|
||||
fmt.Printf("\t%2d: %v\n", i+1, ids)
|
||||
fmt.Fprintf(repl.console, "\t%2d: %v\n", i+1, ids)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleHelp(args []string) error {
|
||||
if len(args) != 0 {
|
||||
if _, ok := commands[args[0]]; !ok {
|
||||
return fmt.Errorf("unknown command: %q", args[0])
|
||||
}
|
||||
fmt.Fprintln(repl.console, commands[args[0]].usage)
|
||||
return nil
|
||||
}
|
||||
|
||||
commandList := slices.Collect(maps.Keys(commands))
|
||||
slices.Sort(commandList)
|
||||
for _, command := range commandList {
|
||||
fmt.Fprintf(repl.console, "%s: %s\n", command, commands[command].descriprion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) netMap() netmap.NetMap {
|
||||
var nm netmap.NetMap
|
||||
var nodes []netmap.NodeInfo
|
||||
|
@ -163,15 +183,82 @@ func (repl *policyPlaygroundREPL) netMap() netmap.NetMap {
|
|||
return nm
|
||||
}
|
||||
|
||||
var policyPlaygroundCompleter = readline.NewPrefixCompleter(
|
||||
readline.PcItem("list"),
|
||||
readline.PcItem("ls"),
|
||||
readline.PcItem("add"),
|
||||
readline.PcItem("load"),
|
||||
readline.PcItem("remove"),
|
||||
readline.PcItem("rm"),
|
||||
readline.PcItem("eval"),
|
||||
)
|
||||
type commandDescription struct {
|
||||
descriprion string
|
||||
usage string
|
||||
}
|
||||
|
||||
var commands = map[string]commandDescription{
|
||||
"list": {
|
||||
descriprion: "Display all nodes in the netmap",
|
||||
usage: `Display all nodes in the netmap
|
||||
Example of usage:
|
||||
list
|
||||
1: id=03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae attrs={Continent:"Europe" Country:"Poland"}
|
||||
2: id=02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3 attrs={Continent:"Antarctica" Country:"Heard Island"}
|
||||
`,
|
||||
},
|
||||
|
||||
"ls": {
|
||||
descriprion: "Display all nodes in the netmap",
|
||||
usage: `Display all nodes in the netmap
|
||||
Example of usage:
|
||||
ls
|
||||
1: id=03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae attrs={Continent:"Europe" Country:"Poland"}
|
||||
2: id=02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3 attrs={Continent:"Antarctica" Country:"Heard Island"}
|
||||
`,
|
||||
},
|
||||
|
||||
"add": {
|
||||
descriprion: "Add a new node: add <node-hash> attr=value",
|
||||
usage: `Add a new node
|
||||
Example of usage:
|
||||
add 03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae continent:Europe country:Poland`,
|
||||
},
|
||||
|
||||
"load": {
|
||||
descriprion: "Load netmap from file: load <path>",
|
||||
usage: `Load netmap from file
|
||||
Example of usage:
|
||||
load "netmap.json"
|
||||
File format (netmap.json):
|
||||
{
|
||||
"03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae": {
|
||||
"continent": "Europe",
|
||||
"country": "Poland"
|
||||
},
|
||||
"02ac920cd7df0b61b289072e6b946e2da4e1a31b9ab1c621bb475e30fa4ab102c3": {
|
||||
"continent": "Antarctica",
|
||||
"country": "Heard Island"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
|
||||
"remove": {
|
||||
descriprion: "Remove a node: remove <node-hash>",
|
||||
usage: `Remove a node
|
||||
Example of usage:
|
||||
remove 03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae`,
|
||||
},
|
||||
|
||||
"rm": {
|
||||
descriprion: "Remove a node: rm <node-hash>",
|
||||
usage: `Remove a node
|
||||
Example of usage:
|
||||
rm 03ff65b6ae79134a4dce9d0d39d3851e9bab4ee97abf86e81e1c5bbc50cd2826ae`,
|
||||
},
|
||||
|
||||
"eval": {
|
||||
descriprion: "Evaluate a policy: eval <policy>",
|
||||
usage: `Evaluate a policy
|
||||
Example of usage:
|
||||
eval REP 2`,
|
||||
},
|
||||
|
||||
"help": {
|
||||
descriprion: "Show available commands",
|
||||
},
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) run() error {
|
||||
if len(viper.GetString(commonflags.RPC)) > 0 {
|
||||
|
@ -198,16 +285,30 @@ func (repl *policyPlaygroundREPL) run() error {
|
|||
"remove": repl.handleRemove,
|
||||
"rm": repl.handleRemove,
|
||||
"eval": repl.handleEval,
|
||||
"help": repl.handleHelp,
|
||||
}
|
||||
|
||||
var cfgCompleter []readline.PrefixCompleterInterface
|
||||
var helpSubItems []readline.PrefixCompleterInterface
|
||||
|
||||
for name := range commands {
|
||||
if name != "help" {
|
||||
cfgCompleter = append(cfgCompleter, readline.PcItem(name))
|
||||
helpSubItems = append(helpSubItems, readline.PcItem(name))
|
||||
}
|
||||
}
|
||||
|
||||
cfgCompleter = append(cfgCompleter, readline.PcItem("help", helpSubItems...))
|
||||
completer := readline.NewPrefixCompleter(cfgCompleter...)
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "> ",
|
||||
InterruptPrompt: "^C",
|
||||
AutoComplete: policyPlaygroundCompleter,
|
||||
AutoComplete: completer,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing readline: %w", err)
|
||||
}
|
||||
repl.console = rl
|
||||
defer rl.Close()
|
||||
|
||||
var exit bool
|
||||
|
@ -232,10 +333,10 @@ func (repl *policyPlaygroundREPL) run() error {
|
|||
cmd := parts[0]
|
||||
if handler, exists := cmdHandlers[cmd]; exists {
|
||||
if err := handler(parts[1:]); err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
fmt.Fprintf(repl.console, "error: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("error: unknown command %q\n", cmd)
|
||||
fmt.Fprintf(repl.console, "error: unknown command %q\n", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,8 +347,7 @@ var policyPlaygroundCmd = &cobra.Command{
|
|||
Long: `A REPL for testing placement policies.
|
||||
If a wallet and endpoint is provided, the initial netmap data will be loaded from the snapshot of the node. Otherwise, an empty playground is created.`,
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
repl, err := newPolicyPlaygroundREPL(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "could not create policy playground: %w", err)
|
||||
repl := newPolicyPlaygroundREPL(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run())
|
||||
},
|
||||
}
|
||||
|
|
|
@ -296,7 +296,7 @@ func appendEstimation(sb *strings.Builder, resp *control.GetShardEvacuationStatu
|
|||
leftSeconds := avgObjEvacuationTimeSeconds * objectsLeft
|
||||
leftMinutes := int(leftSeconds / 60)
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" Estimated time left: %d minutes.", leftMinutes))
|
||||
fmt.Fprintf(sb, " Estimated time left: %d minutes.", leftMinutes)
|
||||
}
|
||||
|
||||
func appendDuration(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
|
@ -305,20 +305,20 @@ func appendDuration(sb *strings.Builder, resp *control.GetShardEvacuationStatusR
|
|||
hour := int(duration.Seconds() / 3600)
|
||||
minute := int(duration.Seconds()/60) % 60
|
||||
second := int(duration.Seconds()) % 60
|
||||
sb.WriteString(fmt.Sprintf(" Duration: %02d:%02d:%02d.", hour, minute, second))
|
||||
fmt.Fprintf(sb, " Duration: %02d:%02d:%02d.", hour, minute, second)
|
||||
}
|
||||
}
|
||||
|
||||
func appendStartedAt(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetStartedAt() != nil {
|
||||
startedAt := time.Unix(resp.GetBody().GetStartedAt().GetValue(), 0).UTC()
|
||||
sb.WriteString(fmt.Sprintf(" Started at: %s UTC.", startedAt.Format(time.RFC3339)))
|
||||
fmt.Fprintf(sb, " Started at: %s UTC.", startedAt.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
func appendError(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if len(resp.GetBody().GetErrorMessage()) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" Error: %s.", resp.GetBody().GetErrorMessage()))
|
||||
fmt.Fprintf(sb, " Error: %s.", resp.GetBody().GetErrorMessage())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,7 +332,7 @@ func appendStatus(sb *strings.Builder, resp *control.GetShardEvacuationStatusRes
|
|||
default:
|
||||
status = "undefined"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" Status: %s.", status))
|
||||
fmt.Fprintf(sb, " Status: %s.", status)
|
||||
}
|
||||
|
||||
func appendShardIDs(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
|
@ -350,14 +350,14 @@ func appendShardIDs(sb *strings.Builder, resp *control.GetShardEvacuationStatusR
|
|||
}
|
||||
|
||||
func appendCounts(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
sb.WriteString(fmt.Sprintf(" Evacuated %d objects out of %d, failed to evacuate: %d, skipped: %d; evacuated %d trees out of %d, failed to evacuate: %d.",
|
||||
fmt.Fprintf(sb, " Evacuated %d objects out of %d, failed to evacuate: %d, skipped: %d; evacuated %d trees out of %d, failed to evacuate: %d.",
|
||||
resp.GetBody().GetEvacuatedObjects(),
|
||||
resp.GetBody().GetTotalObjects(),
|
||||
resp.GetBody().GetFailedObjects(),
|
||||
resp.GetBody().GetSkippedObjects(),
|
||||
resp.GetBody().GetEvacuatedTrees(),
|
||||
resp.GetBody().GetTotalTrees(),
|
||||
resp.GetBody().GetFailedTrees()))
|
||||
resp.GetBody().GetFailedTrees())
|
||||
}
|
||||
|
||||
func initControlEvacuationShardCmd() {
|
||||
|
|
|
@ -62,7 +62,7 @@ func listTargets(cmd *cobra.Command, _ []string) {
|
|||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
_, _ = tw.Write([]byte("#\tName\tType\n"))
|
||||
for i, t := range targets {
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t%s\t%s\n", strconv.Itoa(i), t.GetName(), t.GetType())))
|
||||
_, _ = tw.Write(fmt.Appendf(nil, "%s\t%s\t%s\n", strconv.Itoa(i), t.GetName(), t.GetType()))
|
||||
}
|
||||
_ = tw.Flush()
|
||||
cmd.Print(buf.String())
|
||||
|
|
117
cmd/frostfs-cli/modules/control/locate.go
Normal file
117
cmd/frostfs-cli/modules/control/locate.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
object "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc/client"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
FullInfoFlag = "full"
|
||||
FullInfoFlagUsage = "Print full ShardInfo."
|
||||
)
|
||||
|
||||
var locateObjectCmd = &cobra.Command{
|
||||
Use: "locate-object",
|
||||
Short: "List shards storing the object",
|
||||
Long: "List shards storing the object",
|
||||
Run: locateObject,
|
||||
}
|
||||
|
||||
func initControlLocateObjectCmd() {
|
||||
initControlFlags(locateObjectCmd)
|
||||
|
||||
flags := locateObjectCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = locateObjectCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = locateObjectCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.Bool(commonflags.JSON, false, "Print shard info as a JSON array. Requires --full flag.")
|
||||
flags.Bool(FullInfoFlag, false, FullInfoFlagUsage)
|
||||
}
|
||||
|
||||
func locateObject(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
_ = object.ReadObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
pk := key.Get(cmd)
|
||||
|
||||
body := new(control.ListShardsForObjectRequest_Body)
|
||||
body.SetContainerId(cnr.EncodeToString())
|
||||
body.SetObjectId(obj.EncodeToString())
|
||||
req := new(control.ListShardsForObjectRequest)
|
||||
req.SetBody(body)
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var err error
|
||||
var resp *control.ListShardsForObjectResponse
|
||||
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = control.ListShardsForObject(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
shardIDs := resp.GetBody().GetShard_ID()
|
||||
|
||||
isFull, _ := cmd.Flags().GetBool(FullInfoFlag)
|
||||
if !isFull {
|
||||
for _, id := range shardIDs {
|
||||
cmd.Println(base58.Encode(id))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// get full shard info
|
||||
listShardsReq := new(control.ListShardsRequest)
|
||||
listShardsReq.SetBody(new(control.ListShardsRequest_Body))
|
||||
signRequest(cmd, pk, listShardsReq)
|
||||
var listShardsResp *control.ListShardsResponse
|
||||
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||
listShardsResp, err = control.ListShards(client, listShardsReq)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, listShardsResp.GetSignature(), listShardsResp.GetBody())
|
||||
|
||||
shards := listShardsResp.GetBody().GetShards()
|
||||
sortShardsByID(shards)
|
||||
shards = filterShards(shards, shardIDs)
|
||||
|
||||
isJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
if isJSON {
|
||||
prettyPrintShardsJSON(cmd, shards)
|
||||
} else {
|
||||
prettyPrintShards(cmd, shards)
|
||||
}
|
||||
}
|
||||
|
||||
func filterShards(info []control.ShardInfo, ids [][]byte) []control.ShardInfo {
|
||||
var res []control.ShardInfo
|
||||
for _, id := range ids {
|
||||
for _, inf := range info {
|
||||
if bytes.Equal(inf.Shard_ID, id) {
|
||||
res = append(res, inf)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
|
@ -39,6 +39,7 @@ func init() {
|
|||
listRulesCmd,
|
||||
getRuleCmd,
|
||||
listTargetsCmd,
|
||||
locateObjectCmd,
|
||||
)
|
||||
|
||||
initControlHealthCheckCmd()
|
||||
|
@ -52,4 +53,5 @@ func init() {
|
|||
initControlListRulesCmd()
|
||||
initControGetRuleCmd()
|
||||
initControlListTargetsCmd()
|
||||
initControlLocateObjectCmd()
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ func awaitSetNetmapStatus(cmd *cobra.Command, pk *ecdsa.PrivateKey, cli *client.
|
|||
var resp *control.GetNetmapStatusResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = control.GetNetmapStatus(client, req)
|
||||
resp, err = control.GetNetmapStatus(cmd.Context(), client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "failed to get current netmap status: %w", err)
|
||||
|
|
|
@ -24,7 +24,7 @@ var writecacheShardCmd = &cobra.Command{
|
|||
var sealWritecacheShardCmd = &cobra.Command{
|
||||
Use: "seal",
|
||||
Short: "Flush objects from write-cache and move write-cache to degraded read only mode.",
|
||||
Long: "Flush all the objects from the write-cache to the main storage and move the write-cache to the degraded read only mode: write-cache will be empty and no objects will be put in it.",
|
||||
Long: "Flush all the objects from the write-cache to the main storage and move the write-cache to the 'CLOSED' mode: write-cache will be empty and no objects will be put in it.",
|
||||
Run: sealWritecache,
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ func deleteObject(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.OIDFlag))
|
||||
}
|
||||
|
||||
objAddr = readObjectAddress(cmd, &cnr, &obj)
|
||||
objAddr = ReadObjectAddress(cmd, &cnr, &obj)
|
||||
}
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
|
|
@ -46,7 +46,7 @@ func getObject(cmd *cobra.Command, _ []string) {
|
|||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
filename := cmd.Flag(fileFlag).Value.String()
|
||||
out, closer := createOutWriter(cmd, filename)
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -42,7 +41,9 @@ func initObjectHashCmd() {
|
|||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectHashCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String("range", "", "Range to take hash from in the form offset1:length1,...")
|
||||
flags.StringSlice("range", nil, "Range to take hash from in the form offset1:length1,...")
|
||||
_ = objectHashCmd.MarkFlagRequired("range")
|
||||
|
||||
flags.String("type", hashSha256, "Hash type. Either 'sha256' or 'tz'")
|
||||
flags.String(getRangeHashSaltFlag, "", "Salt in hex format")
|
||||
}
|
||||
|
@ -51,7 +52,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
|||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
ranges, err := getRangeList(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
|
@ -66,36 +67,6 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
|||
pk := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
tz := typ == hashTz
|
||||
fullHash := len(ranges) == 0
|
||||
if fullHash {
|
||||
var headPrm internalclient.HeadObjectPrm
|
||||
headPrm.SetClient(cli)
|
||||
Prepare(cmd, &headPrm)
|
||||
headPrm.SetAddress(objAddr)
|
||||
|
||||
// get hash of full payload through HEAD (may be user can do it through dedicated command?)
|
||||
res, err := internalclient.HeadObject(cmd.Context(), headPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
var cs checksum.Checksum
|
||||
var csSet bool
|
||||
|
||||
if tz {
|
||||
cs, csSet = res.Header().PayloadHomomorphicHash()
|
||||
} else {
|
||||
cs, csSet = res.Header().PayloadChecksum()
|
||||
}
|
||||
|
||||
if csSet {
|
||||
cmd.Println(hex.EncodeToString(cs.Value()))
|
||||
} else {
|
||||
cmd.Println("Missing checksum in object header.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var hashPrm internalclient.HashPayloadRangesPrm
|
||||
hashPrm.SetClient(cli)
|
||||
Prepare(cmd, &hashPrm)
|
||||
|
@ -104,7 +75,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
|||
hashPrm.SetSalt(salt)
|
||||
hashPrm.SetRanges(ranges)
|
||||
|
||||
if tz {
|
||||
if typ == hashTz {
|
||||
hashPrm.TZ()
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
|||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
|
|
@ -48,6 +48,12 @@ type ecHeader struct {
|
|||
parent oid.ID
|
||||
}
|
||||
|
||||
type objectCounter struct {
|
||||
sync.Mutex
|
||||
total uint32
|
||||
isECcounted bool
|
||||
}
|
||||
|
||||
type objectPlacement struct {
|
||||
requiredNodes []netmapSDK.NodeInfo
|
||||
confirmedNodes []netmapSDK.NodeInfo
|
||||
|
@ -56,6 +62,7 @@ type objectPlacement struct {
|
|||
type objectNodesResult struct {
|
||||
errors []error
|
||||
placements map[oid.ID]objectPlacement
|
||||
total uint32
|
||||
}
|
||||
|
||||
type ObjNodesDataObject struct {
|
||||
|
@ -101,23 +108,23 @@ func initObjectNodesCmd() {
|
|||
func objectNodes(cmd *cobra.Command, _ []string) {
|
||||
var cnrID cid.ID
|
||||
var objID oid.ID
|
||||
readObjectAddress(cmd, &cnrID, &objID)
|
||||
ReadObjectAddress(cmd, &cnrID, &objID)
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
objects := getPhyObjects(cmd, cnrID, objID, cli, pk)
|
||||
objects, count := getPhyObjects(cmd, cnrID, objID, cli, pk)
|
||||
|
||||
placementPolicy, netmap := getPlacementPolicyAndNetmap(cmd, cnrID, cli)
|
||||
|
||||
result := getRequiredPlacement(cmd, objects, placementPolicy, netmap)
|
||||
|
||||
getActualPlacement(cmd, netmap, pk, objects, result)
|
||||
getActualPlacement(cmd, netmap, pk, objects, count, result)
|
||||
|
||||
printPlacement(cmd, objID, objects, result)
|
||||
}
|
||||
|
||||
func getPhyObjects(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) []phyObject {
|
||||
func getPhyObjects(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) ([]phyObject, int) {
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnrID)
|
||||
addrObj.SetObject(objID)
|
||||
|
@ -145,7 +152,7 @@ func getPhyObjects(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.C
|
|||
parent: res.Header().ECHeader().Parent(),
|
||||
}
|
||||
}
|
||||
return []phyObject{obj}
|
||||
return []phyObject{obj}, 1
|
||||
}
|
||||
|
||||
var errSplitInfo *objectSDK.SplitInfoError
|
||||
|
@ -155,29 +162,34 @@ func getPhyObjects(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.C
|
|||
|
||||
var ecInfoError *objectSDK.ECInfoError
|
||||
if errors.As(err, &ecInfoError) {
|
||||
return getECObjectChunks(cmd, cnrID, objID, ecInfoError)
|
||||
return getECObjectChunks(cmd, cnrID, objID, ecInfoError), 1
|
||||
}
|
||||
commonCmd.ExitOnErr(cmd, "failed to get object info: %w", err)
|
||||
return nil
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func getComplexObjectParts(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, prmHead internalclient.HeadObjectPrm, errSplitInfo *objectSDK.SplitInfoError) []phyObject {
|
||||
members := getCompexObjectMembers(cmd, cnrID, objID, cli, prmHead, errSplitInfo)
|
||||
return flattenComplexMembersIfECContainer(cmd, cnrID, members, prmHead)
|
||||
func getComplexObjectParts(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, prmHead internalclient.HeadObjectPrm, errSplitInfo *objectSDK.SplitInfoError) ([]phyObject, int) {
|
||||
members, total := getCompexObjectMembers(cmd, cnrID, objID, cli, prmHead, errSplitInfo)
|
||||
return flattenComplexMembersIfECContainer(cmd, cnrID, members, prmHead), total
|
||||
}
|
||||
|
||||
func getCompexObjectMembers(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, prmHead internalclient.HeadObjectPrm, errSplitInfo *objectSDK.SplitInfoError) []oid.ID {
|
||||
func getCompexObjectMembers(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, prmHead internalclient.HeadObjectPrm, errSplitInfo *objectSDK.SplitInfoError) ([]oid.ID, int) {
|
||||
var total int
|
||||
splitInfo := errSplitInfo.SplitInfo()
|
||||
|
||||
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnrID); ok {
|
||||
return members
|
||||
if total = len(members); total > 0 {
|
||||
total-- // linking object is not data object
|
||||
}
|
||||
return members, total
|
||||
}
|
||||
|
||||
if members, ok := tryGetSplitMembersBySplitID(cmd, splitInfo, cli, cnrID); ok {
|
||||
return members
|
||||
return members, len(members)
|
||||
}
|
||||
|
||||
return tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnrID, objID)
|
||||
members := tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnrID, objID)
|
||||
return members, len(members)
|
||||
}
|
||||
|
||||
func flattenComplexMembersIfECContainer(cmd *cobra.Command, cnrID cid.ID, members []oid.ID, prmHead internalclient.HeadObjectPrm) []phyObject {
|
||||
|
@ -320,7 +332,7 @@ func getReplicaRequiredPlacement(cmd *cobra.Command, objects []phyObject, placem
|
|||
}
|
||||
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
||||
for _, object := range objects {
|
||||
placement, err := placementBuilder.BuildPlacement(object.containerID, &object.objectID, placementPolicy)
|
||||
placement, err := placementBuilder.BuildPlacement(cmd.Context(), object.containerID, &object.objectID, placementPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get required placement for object: %w", err)
|
||||
for repIdx, rep := range placement {
|
||||
numOfReplicas := placementPolicy.ReplicaDescriptor(repIdx).NumberOfObjects()
|
||||
|
@ -358,7 +370,7 @@ func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placem
|
|||
placementObjectID = object.ecHeader.parent
|
||||
}
|
||||
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
||||
placement, err := placementBuilder.BuildPlacement(object.containerID, &placementObjectID, placementPolicy)
|
||||
placement, err := placementBuilder.BuildPlacement(cmd.Context(), object.containerID, &placementObjectID, placementPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get required placement: %w", err)
|
||||
|
||||
for _, vector := range placement {
|
||||
|
@ -383,8 +395,11 @@ func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placem
|
|||
}
|
||||
}
|
||||
|
||||
func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, pk *ecdsa.PrivateKey, objects []phyObject, result *objectNodesResult) {
|
||||
func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, pk *ecdsa.PrivateKey, objects []phyObject, count int, result *objectNodesResult) {
|
||||
resultMtx := &sync.Mutex{}
|
||||
counter := &objectCounter{
|
||||
total: uint32(count),
|
||||
}
|
||||
|
||||
candidates := getNodesToCheckObjectExistance(cmd, netmap, result)
|
||||
|
||||
|
@ -401,7 +416,7 @@ func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, pk *ecdsa.
|
|||
|
||||
for _, object := range objects {
|
||||
eg.Go(func() error {
|
||||
stored, err := isObjectStoredOnNode(egCtx, cmd, object.containerID, object.objectID, cli, pk)
|
||||
stored, err := isObjectStoredOnNode(egCtx, cmd, object.containerID, object.objectID, cli, pk, counter)
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
if err == nil && stored {
|
||||
|
@ -420,6 +435,7 @@ func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, pk *ecdsa.
|
|||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "failed to get actual placement: %w", eg.Wait())
|
||||
result.total = counter.total
|
||||
}
|
||||
|
||||
func getNodesToCheckObjectExistance(cmd *cobra.Command, netmap *netmapSDK.NetMap, result *objectNodesResult) []netmapSDK.NodeInfo {
|
||||
|
@ -478,7 +494,7 @@ func createClient(ctx context.Context, cmd *cobra.Command, candidate netmapSDK.N
|
|||
return cli, nil
|
||||
}
|
||||
|
||||
func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) (bool, error) {
|
||||
func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey, counter *objectCounter) (bool, error) {
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnrID)
|
||||
addrObj.SetObject(objID)
|
||||
|
@ -493,6 +509,14 @@ func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID,
|
|||
|
||||
res, err := internalclient.HeadObject(ctx, prmHead)
|
||||
if err == nil && res != nil {
|
||||
if res.Header().ECHeader() != nil {
|
||||
counter.Lock()
|
||||
defer counter.Unlock()
|
||||
if !counter.isECcounted {
|
||||
counter.total *= res.Header().ECHeader().Total()
|
||||
}
|
||||
counter.isECcounted = true
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
var notFound *apistatus.ObjectNotFound
|
||||
|
@ -512,7 +536,8 @@ func printPlacement(cmd *cobra.Command, objID oid.ID, objects []phyObject, resul
|
|||
}
|
||||
|
||||
func printObjectNodesAsText(cmd *cobra.Command, objID oid.ID, objects []phyObject, result *objectNodesResult) {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Object %s stores payload in %d data objects:\n", objID.EncodeToString(), len(objects))
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Object %s stores payload in %d data objects\n", objID.EncodeToString(), result.total)
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Found %d:\n", len(objects))
|
||||
|
||||
for _, object := range objects {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "- %s\n", object.objectID)
|
||||
|
|
|
@ -2,6 +2,7 @@ package object
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
|
@ -20,6 +22,7 @@ const (
|
|||
replaceAttrsFlagName = "replace-attrs"
|
||||
rangeFlagName = "range"
|
||||
payloadFlagName = "payload"
|
||||
splitHeaderFlagName = "split-header"
|
||||
)
|
||||
|
||||
var objectPatchCmd = &cobra.Command{
|
||||
|
@ -46,17 +49,18 @@ func initObjectPatchCmd() {
|
|||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String(newAttrsFlagName, "", "New object attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.StringSlice(newAttrsFlagName, nil, "New object attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.Bool(replaceAttrsFlagName, false, "Replace object attributes by new ones.")
|
||||
flags.StringSlice(rangeFlagName, []string{}, "Range to which patch payload is applied. Format: offset:length")
|
||||
flags.StringSlice(payloadFlagName, []string{}, "Path to file with patch payload.")
|
||||
flags.String(splitHeaderFlagName, "", "Path to binary or JSON-encoded split header")
|
||||
}
|
||||
|
||||
func patch(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
ranges, err := getRangeSlice(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
|
@ -84,6 +88,8 @@ func patch(cmd *cobra.Command, _ []string) {
|
|||
prm.NewAttributes = newAttrs
|
||||
prm.ReplaceAttribute = replaceAttrs
|
||||
|
||||
prm.NewSplitHeader = parseSplitHeaderBinaryOrJSON(cmd)
|
||||
|
||||
for i := range ranges {
|
||||
prm.PayloadPatches = append(prm.PayloadPatches, internalclient.PayloadPatch{
|
||||
Range: ranges[i],
|
||||
|
@ -99,11 +105,9 @@ func patch(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func parseNewObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||
var rawAttrs []string
|
||||
|
||||
raw := cmd.Flag(newAttrsFlagName).Value.String()
|
||||
if len(raw) != 0 {
|
||||
rawAttrs = strings.Split(raw, ",")
|
||||
rawAttrs, err := cmd.Flags().GetStringSlice(newAttrsFlagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||
|
@ -149,3 +153,22 @@ func patchPayloadPaths(cmd *cobra.Command) []string {
|
|||
v, _ := cmd.Flags().GetStringSlice(payloadFlagName)
|
||||
return v
|
||||
}
|
||||
|
||||
func parseSplitHeaderBinaryOrJSON(cmd *cobra.Command) *objectSDK.SplitHeader {
|
||||
path, _ := cmd.Flags().GetString(splitHeaderFlagName)
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
commonCmd.ExitOnErr(cmd, "read file error: %w", err)
|
||||
|
||||
splitHdrV2 := new(objectV2.SplitHeader)
|
||||
err = splitHdrV2.Unmarshal(data)
|
||||
if err != nil {
|
||||
err = splitHdrV2.UnmarshalJSON(data)
|
||||
commonCmd.ExitOnErr(cmd, "unmarshal error: %w", err)
|
||||
}
|
||||
|
||||
return objectSDK.NewSplitHeaderFromV2(splitHdrV2)
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func initObjectPutCmd() {
|
|||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
|
||||
flags.String("attributes", "", "User attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.StringSlice("attributes", nil, "User attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.Bool("disable-filename", false, "Do not set well-known filename attribute")
|
||||
flags.Bool("disable-timestamp", false, "Do not set well-known timestamp attribute")
|
||||
flags.Uint64VarP(&putExpiredOn, commonflags.ExpireAt, "e", 0, "The last active epoch in the life of the object")
|
||||
|
@ -214,11 +214,9 @@ func getAllObjectAttributes(cmd *cobra.Command) []objectSDK.Attribute {
|
|||
}
|
||||
|
||||
func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||
var rawAttrs []string
|
||||
|
||||
raw := cmd.Flag("attributes").Value.String()
|
||||
if len(raw) != 0 {
|
||||
rawAttrs = strings.Split(raw, ",")
|
||||
rawAttrs, err := cmd.Flags().GetStringSlice("attributes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||
|
|
|
@ -38,7 +38,7 @@ func initObjectRangeCmd() {
|
|||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String("range", "", "Range to take data from in the form offset:length")
|
||||
flags.StringSlice("range", nil, "Range to take data from in the form offset:length")
|
||||
flags.String(fileFlag, "", "File to write object payload to. Default: stdout.")
|
||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ func getObjectRange(cmd *cobra.Command, _ []string) {
|
|||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
ranges, err := getRangeList(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
|
@ -154,7 +154,7 @@ func printECInfoErr(cmd *cobra.Command, err error) bool {
|
|||
if ok {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
if !(toJSON || toProto) {
|
||||
if !toJSON && !toProto {
|
||||
cmd.PrintErrln("Object is erasure-encoded, ec information received.")
|
||||
}
|
||||
printECInfo(cmd, errECInfo.ECInfo())
|
||||
|
@ -195,11 +195,10 @@ func marshalECInfo(cmd *cobra.Command, info *objectSDK.ECInfo) ([]byte, error) {
|
|||
}
|
||||
|
||||
func getRangeList(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
||||
v := cmd.Flag("range").Value.String()
|
||||
if len(v) == 0 {
|
||||
return nil, nil
|
||||
vs, err := cmd.Flags().GetStringSlice("range")
|
||||
if len(vs) == 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs := strings.Split(v, ",")
|
||||
rs := make([]objectSDK.Range, len(vs))
|
||||
for i := range vs {
|
||||
before, after, found := strings.Cut(vs[i], rangeSep)
|
||||
|
|
|
@ -74,7 +74,7 @@ func parseXHeaders(cmd *cobra.Command) []string {
|
|||
return xs
|
||||
}
|
||||
|
||||
func readObjectAddress(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID) oid.Address {
|
||||
func ReadObjectAddress(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID) oid.Address {
|
||||
readCID(cmd, cnr)
|
||||
readOID(cmd, obj)
|
||||
|
||||
|
|
|
@ -2,18 +2,19 @@ package tree
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
|
||||
metrics "git.frostfs.info/TrueCloudLab/frostfs-observability/metrics/grpc"
|
||||
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
|
@ -32,23 +33,29 @@ func _client() (tree.TreeServiceClient, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
host, isTLS, err := client.ParseURI(netAddr.URIAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
creds := insecure.NewCredentials()
|
||||
if isTLS {
|
||||
creds = credentials.NewTLS(&tls.Config{})
|
||||
}
|
||||
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithChainUnaryInterceptor(
|
||||
metrics.NewUnaryClientInterceptor(),
|
||||
tracing.NewUnaryClientInteceptor(),
|
||||
tracing.NewUnaryClientInterceptor(),
|
||||
),
|
||||
grpc.WithChainStreamInterceptor(
|
||||
metrics.NewStreamClientInterceptor(),
|
||||
tracing.NewStreamClientInterceptor(),
|
||||
),
|
||||
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
||||
grpc.WithDisableServiceConfig(),
|
||||
grpc.WithTransportCredentials(creds),
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(netAddr.URIAddr(), "grpcs:") {
|
||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
cc, err := grpc.NewClient(netAddr.URIAddr(), opts...)
|
||||
cc, err := grpc.NewClient(host, opts...)
|
||||
return tree.NewTreeServiceClient(cc), err
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@ -38,13 +39,14 @@ func reloadConfig() error {
|
|||
}
|
||||
cmode.Store(cfg.GetBool("node.kludge_compatibility_mode"))
|
||||
audit.Store(cfg.GetBool("audit.enabled"))
|
||||
var logPrm logger.Prm
|
||||
err = logPrm.SetLevelString(cfg.GetString("logger.level"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logPrm.PrependTimestamp = cfg.GetBool("logger.timestamp")
|
||||
log.Reload(logPrm)
|
||||
|
||||
return logPrm.Reload()
|
||||
return nil
|
||||
}
|
||||
|
||||
func watchForSignal(ctx context.Context, cancel func()) {
|
||||
|
|
|
@ -31,7 +31,6 @@ const (
|
|||
var (
|
||||
wg = new(sync.WaitGroup)
|
||||
intErr = make(chan error) // internal inner ring errors
|
||||
logPrm = new(logger.Prm)
|
||||
innerRing *innerring.Server
|
||||
pprofCmp *pprofComponent
|
||||
metricsCmp *httpComponent
|
||||
|
@ -70,6 +69,7 @@ func main() {
|
|||
|
||||
metrics := irMetrics.NewInnerRingMetrics()
|
||||
|
||||
var logPrm logger.Prm
|
||||
err = logPrm.SetLevelString(
|
||||
cfg.GetString("logger.level"),
|
||||
)
|
||||
|
|
|
@ -2,13 +2,17 @@ package meta
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
||||
schemaCommon "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||
schema "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/spf13/cobra"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var tuiCMD = &cobra.Command{
|
||||
|
@ -27,6 +31,11 @@ Available search filters:
|
|||
|
||||
var initialPrompt string
|
||||
|
||||
var parserPerSchemaVersion = map[uint64]schemaCommon.Parser{
|
||||
2: schema.MetabaseParserV2,
|
||||
3: schema.MetabaseParserV3,
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.AddComponentPathFlag(tuiCMD, &vPath)
|
||||
|
||||
|
@ -49,12 +58,22 @@ func runTUI(cmd *cobra.Command) error {
|
|||
}
|
||||
defer db.Close()
|
||||
|
||||
schemaVersion, hasVersion := lookupSchemaVersion(cmd, db)
|
||||
if !hasVersion {
|
||||
return errors.New("couldn't detect schema version")
|
||||
}
|
||||
|
||||
metabaseParser, ok := parserPerSchemaVersion[schemaVersion]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown schema version %d", schemaVersion)
|
||||
}
|
||||
|
||||
// Need if app was stopped with Ctrl-C.
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
defer cancel()
|
||||
|
||||
app := tview.NewApplication()
|
||||
ui := tui.NewUI(ctx, app, db, schema.MetabaseParser, nil)
|
||||
ui := tui.NewUI(ctx, app, db, metabaseParser, nil)
|
||||
|
||||
_ = ui.AddFilter("cid", tui.CIDParser, "CID")
|
||||
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
||||
|
@ -69,3 +88,31 @@ func runTUI(cmd *cobra.Command) error {
|
|||
app.SetRoot(ui, true).SetFocus(ui)
|
||||
return app.Run()
|
||||
}
|
||||
|
||||
var (
|
||||
shardInfoBucket = []byte{5}
|
||||
versionRecord = []byte("version")
|
||||
)
|
||||
|
||||
func lookupSchemaVersion(cmd *cobra.Command, db *bbolt.DB) (version uint64, ok bool) {
|
||||
err := db.View(func(tx *bbolt.Tx) error {
|
||||
bkt := tx.Bucket(shardInfoBucket)
|
||||
if bkt == nil {
|
||||
return nil
|
||||
}
|
||||
rec := bkt.Get(versionRecord)
|
||||
if rec == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
version = binary.LittleEndian.Uint64(rec)
|
||||
ok = true
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
common.ExitOnErr(cmd, fmt.Errorf("couldn't lookup version: %w", err))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package common
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
)
|
||||
|
||||
type FilterResult byte
|
||||
|
@ -71,11 +73,7 @@ func (fp FallbackParser) ToParser() Parser {
|
|||
func (p Parser) ToFallbackParser() FallbackParser {
|
||||
return func(key, value []byte) (SchemaEntry, Parser) {
|
||||
entry, next, err := p(key, value)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf(
|
||||
"couldn't use that parser as a fallback parser, it returned an error: %w", err,
|
||||
))
|
||||
}
|
||||
assert.NoError(err, "couldn't use that parser as a fallback parser")
|
||||
return entry, next
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,10 +80,15 @@ var (
|
|||
},
|
||||
)
|
||||
|
||||
UserAttributeParser = NewUserAttributeKeyBucketParser(
|
||||
UserAttributeParserV2 = NewUserAttributeKeyBucketParser(
|
||||
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||
)
|
||||
|
||||
UserAttributeParserV3 = NewUserAttributeKeyBucketParserWithSpecificKeys(
|
||||
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||
[]string{"FilePath", "S3-Access-Box-CRDT-Name"},
|
||||
)
|
||||
|
||||
PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{
|
||||
cidResolver: StrictResolver,
|
||||
oidResolver: StrictResolver,
|
||||
|
@ -108,4 +113,14 @@ var (
|
|||
cidResolver: StrictResolver,
|
||||
oidResolver: LenientResolver,
|
||||
})
|
||||
|
||||
ExpirationEpochToObjectParser = NewPrefixBucketParser(ExpirationEpochToObject, records.ExpirationEpochToObjectRecordParser, Resolvers{
|
||||
cidResolver: LenientResolver,
|
||||
oidResolver: LenientResolver,
|
||||
})
|
||||
|
||||
ObjectToExpirationEpochParser = NewPrefixContainerBucketParser(ObjectToExpirationEpoch, records.ObjectToExpirationEpochRecordParser, Resolvers{
|
||||
cidResolver: StrictResolver,
|
||||
oidResolver: LenientResolver,
|
||||
})
|
||||
)
|
||||
|
|
|
@ -22,27 +22,31 @@ const (
|
|||
Split
|
||||
ContainerCounters
|
||||
ECInfo
|
||||
ExpirationEpochToObject
|
||||
ObjectToExpirationEpoch
|
||||
)
|
||||
|
||||
var x = map[Prefix]string{
|
||||
Graveyard: "Graveyard",
|
||||
Garbage: "Garbage",
|
||||
ToMoveIt: "To Move It",
|
||||
ContainerVolume: "Container Volume",
|
||||
Locked: "Locked",
|
||||
ShardInfo: "Shard Info",
|
||||
Primary: "Primary",
|
||||
Lockers: "Lockers",
|
||||
Tombstone: "Tombstone",
|
||||
Small: "Small",
|
||||
Root: "Root",
|
||||
Owner: "Owner",
|
||||
UserAttribute: "User Attribute",
|
||||
PayloadHash: "Payload Hash",
|
||||
Parent: "Parent",
|
||||
Split: "Split",
|
||||
ContainerCounters: "Container Counters",
|
||||
ECInfo: "EC Info",
|
||||
Graveyard: "Graveyard",
|
||||
Garbage: "Garbage",
|
||||
ToMoveIt: "To Move It",
|
||||
ContainerVolume: "Container Volume",
|
||||
Locked: "Locked",
|
||||
ShardInfo: "Shard Info",
|
||||
Primary: "Primary",
|
||||
Lockers: "Lockers",
|
||||
Tombstone: "Tombstone",
|
||||
Small: "Small",
|
||||
Root: "Root",
|
||||
Owner: "Owner",
|
||||
UserAttribute: "User Attribute",
|
||||
PayloadHash: "Payload Hash",
|
||||
Parent: "Parent",
|
||||
Split: "Split",
|
||||
ContainerCounters: "Container Counters",
|
||||
ECInfo: "EC Info",
|
||||
ExpirationEpochToObject: "Exp. Epoch to Object",
|
||||
ObjectToExpirationEpoch: "Object to Exp. Epoch",
|
||||
}
|
||||
|
||||
func (p Prefix) String() string {
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func (b *PrefixBucket) String() string {
|
||||
return common.FormatSimple(
|
||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ func (b *PrefixContainerBucket) String() string {
|
|||
return fmt.Sprintf(
|
||||
"%s CID %s",
|
||||
common.FormatSimple(
|
||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||
),
|
||||
common.FormatSimple(b.id.String(), tcell.ColorAqua),
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ func (b *ContainerBucket) String() string {
|
|||
func (b *UserAttributeKeyBucket) String() string {
|
||||
return fmt.Sprintf("%s CID %s ATTR-KEY %s",
|
||||
common.FormatSimple(
|
||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||
),
|
||||
common.FormatSimple(
|
||||
fmt.Sprintf("%-44s", b.id), tcell.ColorAqua,
|
||||
|
|
|
@ -2,6 +2,7 @@ package buckets
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
|
@ -57,10 +58,11 @@ var (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrNotBucket = errors.New("not a bucket")
|
||||
ErrInvalidKeyLength = errors.New("invalid key length")
|
||||
ErrInvalidValueLength = errors.New("invalid value length")
|
||||
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||
ErrNotBucket = errors.New("not a bucket")
|
||||
ErrInvalidKeyLength = errors.New("invalid key length")
|
||||
ErrInvalidValueLength = errors.New("invalid value length")
|
||||
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||
ErrUnexpectedAttributeKey = errors.New("unexpected attribute key")
|
||||
)
|
||||
|
||||
func NewPrefixBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser {
|
||||
|
@ -132,6 +134,10 @@ func NewContainerBucketParser(next common.Parser, resolvers Resolvers) common.Pa
|
|||
}
|
||||
|
||||
func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
||||
return NewUserAttributeKeyBucketParserWithSpecificKeys(next, nil)
|
||||
}
|
||||
|
||||
func NewUserAttributeKeyBucketParserWithSpecificKeys(next common.Parser, keys []string) common.Parser {
|
||||
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||
if value != nil {
|
||||
return nil, nil, ErrNotBucket
|
||||
|
@ -147,6 +153,11 @@ func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
|||
return nil, nil, err
|
||||
}
|
||||
b.key = string(key[33:])
|
||||
|
||||
if len(keys) != 0 && !slices.Contains(keys, b.key) {
|
||||
return nil, nil, ErrUnexpectedAttributeKey
|
||||
}
|
||||
|
||||
return &b, next, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,30 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/buckets"
|
||||
)
|
||||
|
||||
var MetabaseParser = common.WithFallback(
|
||||
var MetabaseParserV3 = common.WithFallback(
|
||||
common.Any(
|
||||
buckets.GraveyardParser,
|
||||
buckets.GarbageParser,
|
||||
buckets.ContainerVolumeParser,
|
||||
buckets.LockedParser,
|
||||
buckets.ShardInfoParser,
|
||||
buckets.PrimaryParser,
|
||||
buckets.LockersParser,
|
||||
buckets.TombstoneParser,
|
||||
buckets.SmallParser,
|
||||
buckets.RootParser,
|
||||
buckets.UserAttributeParserV3,
|
||||
buckets.ParentParser,
|
||||
buckets.SplitParser,
|
||||
buckets.ContainerCountersParser,
|
||||
buckets.ECInfoParser,
|
||||
buckets.ExpirationEpochToObjectParser,
|
||||
buckets.ObjectToExpirationEpochParser,
|
||||
),
|
||||
common.RawParser.ToFallbackParser(),
|
||||
)
|
||||
|
||||
var MetabaseParserV2 = common.WithFallback(
|
||||
common.Any(
|
||||
buckets.GraveyardParser,
|
||||
buckets.GarbageParser,
|
||||
|
@ -18,7 +41,7 @@ var MetabaseParser = common.WithFallback(
|
|||
buckets.SmallParser,
|
||||
buckets.RootParser,
|
||||
buckets.OwnerParser,
|
||||
buckets.UserAttributeParser,
|
||||
buckets.UserAttributeParserV2,
|
||||
buckets.PayloadHashParser,
|
||||
buckets.ParentParser,
|
||||
buckets.SplitParser,
|
||||
|
|
|
@ -63,3 +63,11 @@ func (r *ContainerCountersRecord) DetailedString() string {
|
|||
func (r *ECInfoRecord) DetailedString() string {
|
||||
return spew.Sdump(*r)
|
||||
}
|
||||
|
||||
func (r *ExpirationEpochToObjectRecord) DetailedString() string {
|
||||
return spew.Sdump(*r)
|
||||
}
|
||||
|
||||
func (r *ObjectToExpirationEpochRecord) DetailedString() string {
|
||||
return spew.Sdump(*r)
|
||||
}
|
||||
|
|
|
@ -143,3 +143,26 @@ func (r *ECInfoRecord) Filter(typ string, val any) common.FilterResult {
|
|||
return common.No
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ExpirationEpochToObjectRecord) Filter(typ string, val any) common.FilterResult {
|
||||
switch typ {
|
||||
case "cid":
|
||||
id := val.(cid.ID)
|
||||
return common.IfThenElse(r.cnt.Equals(id), common.Yes, common.No)
|
||||
case "oid":
|
||||
id := val.(oid.ID)
|
||||
return common.IfThenElse(r.obj.Equals(id), common.Yes, common.No)
|
||||
default:
|
||||
return common.No
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ObjectToExpirationEpochRecord) Filter(typ string, val any) common.FilterResult {
|
||||
switch typ {
|
||||
case "oid":
|
||||
id := val.(oid.ID)
|
||||
return common.IfThenElse(r.obj.Equals(id), common.Yes, common.No)
|
||||
default:
|
||||
return common.No
|
||||
}
|
||||
}
|
||||
|
|
|
@ -249,3 +249,45 @@ func ECInfoRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, e
|
|||
}
|
||||
return &r, nil, nil
|
||||
}
|
||||
|
||||
func ExpirationEpochToObjectRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) {
|
||||
if len(key) != 72 {
|
||||
return nil, nil, ErrInvalidKeyLength
|
||||
}
|
||||
|
||||
var (
|
||||
r ExpirationEpochToObjectRecord
|
||||
err error
|
||||
)
|
||||
|
||||
r.epoch = binary.BigEndian.Uint64(key[:8])
|
||||
if err = r.cnt.Decode(key[8:40]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = r.obj.Decode(key[40:]); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &r, nil, nil
|
||||
}
|
||||
|
||||
func ObjectToExpirationEpochRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||
if len(key) != 32 {
|
||||
return nil, nil, ErrInvalidKeyLength
|
||||
}
|
||||
if len(value) != 8 {
|
||||
return nil, nil, ErrInvalidValueLength
|
||||
}
|
||||
|
||||
var (
|
||||
r ObjectToExpirationEpochRecord
|
||||
err error
|
||||
)
|
||||
|
||||
if err = r.obj.Decode(key); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r.epoch = binary.LittleEndian.Uint64(value)
|
||||
|
||||
return &r, nil, nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package records
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
@ -133,3 +134,22 @@ func (r *ECInfoRecord) String() string {
|
|||
len(r.ids),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *ExpirationEpochToObjectRecord) String() string {
|
||||
return fmt.Sprintf(
|
||||
"exp. epoch %s %c CID %s OID %s",
|
||||
common.FormatSimple(fmt.Sprintf("%-20d", r.epoch), tcell.ColorAqua),
|
||||
tview.Borders.Vertical,
|
||||
common.FormatSimple(fmt.Sprintf("%-44s", r.cnt), tcell.ColorAqua),
|
||||
common.FormatSimple(fmt.Sprintf("%-44s", r.obj), tcell.ColorAqua),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *ObjectToExpirationEpochRecord) String() string {
|
||||
return fmt.Sprintf(
|
||||
"OID %s %c exp. epoch %s",
|
||||
common.FormatSimple(fmt.Sprintf("%-44s", r.obj), tcell.ColorAqua),
|
||||
tview.Borders.Vertical,
|
||||
common.FormatSimple(strconv.FormatUint(r.epoch, 10), tcell.ColorAqua),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -79,4 +79,15 @@ type (
|
|||
id oid.ID
|
||||
ids []oid.ID
|
||||
}
|
||||
|
||||
ExpirationEpochToObjectRecord struct {
|
||||
epoch uint64
|
||||
cnt cid.ID
|
||||
obj oid.ID
|
||||
}
|
||||
|
||||
ObjectToExpirationEpochRecord struct {
|
||||
obj oid.ID
|
||||
epoch uint64
|
||||
}
|
||||
)
|
||||
|
|
|
@ -124,10 +124,7 @@ func (v *BucketsView) loadNodeChildren(
|
|||
path := parentBucket.Path
|
||||
parser := parentBucket.NextParser
|
||||
|
||||
buffer, err := LoadBuckets(ctx, v.ui.db, path, v.ui.loadBufferSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := LoadBuckets(ctx, v.ui.db, path, v.ui.loadBufferSize)
|
||||
|
||||
for item := range buffer {
|
||||
if item.err != nil {
|
||||
|
@ -135,6 +132,7 @@ func (v *BucketsView) loadNodeChildren(
|
|||
}
|
||||
bucket := item.val
|
||||
|
||||
var err error
|
||||
bucket.Entry, bucket.NextParser, err = parser(bucket.Name, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -180,10 +178,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
|||
defer cancel()
|
||||
|
||||
// Check the current bucket's nested buckets if exist
|
||||
bucketsBuffer, err := LoadBuckets(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bucketsBuffer := LoadBuckets(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||||
|
||||
for item := range bucketsBuffer {
|
||||
if item.err != nil {
|
||||
|
@ -191,6 +186,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
|||
}
|
||||
b := item.val
|
||||
|
||||
var err error
|
||||
b.Entry, b.NextParser, err = bucket.NextParser(b.Name, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -206,10 +202,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
|||
}
|
||||
|
||||
// Check the current bucket's nested records if exist
|
||||
recordsBuffer, err := LoadRecords(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
recordsBuffer := LoadRecords(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||||
|
||||
for item := range recordsBuffer {
|
||||
if item.err != nil {
|
||||
|
@ -217,6 +210,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
|||
}
|
||||
r := item.val
|
||||
|
||||
var err error
|
||||
r.Entry, _, err = bucket.NextParser(r.Key, r.Value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
|
@ -35,7 +35,7 @@ func resolvePath(tx *bbolt.Tx, path [][]byte) (*bbolt.Bucket, error) {
|
|||
func load[T any](
|
||||
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
||||
filter func(key, value []byte) bool, transform func(key, value []byte) T,
|
||||
) (<-chan Item[T], error) {
|
||||
) <-chan Item[T] {
|
||||
buffer := make(chan Item[T], bufferSize)
|
||||
|
||||
go func() {
|
||||
|
@ -77,13 +77,13 @@ func load[T any](
|
|||
}
|
||||
}()
|
||||
|
||||
return buffer, nil
|
||||
return buffer
|
||||
}
|
||||
|
||||
func LoadBuckets(
|
||||
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
||||
) (<-chan Item[*Bucket], error) {
|
||||
buffer, err := load(
|
||||
) <-chan Item[*Bucket] {
|
||||
buffer := load(
|
||||
ctx, db, path, bufferSize,
|
||||
func(_, value []byte) bool {
|
||||
return value == nil
|
||||
|
@ -98,17 +98,14 @@ func LoadBuckets(
|
|||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't start iterating bucket: %w", err)
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
return buffer
|
||||
}
|
||||
|
||||
func LoadRecords(
|
||||
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
||||
) (<-chan Item[*Record], error) {
|
||||
buffer, err := load(
|
||||
) <-chan Item[*Record] {
|
||||
buffer := load(
|
||||
ctx, db, path, bufferSize,
|
||||
func(_, value []byte) bool {
|
||||
return value != nil
|
||||
|
@ -124,11 +121,8 @@ func LoadRecords(
|
|||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't start iterating bucket: %w", err)
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
return buffer
|
||||
}
|
||||
|
||||
// HasBuckets checks if a bucket has nested buckets. It relies on assumption
|
||||
|
@ -137,24 +131,21 @@ func HasBuckets(ctx context.Context, db *bbolt.DB, path [][]byte) (bool, error)
|
|||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
buffer, err := load(
|
||||
buffer := load(
|
||||
ctx, db, path, 1,
|
||||
nil,
|
||||
func(_, value []byte) []byte { return value },
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
x, ok := <-buffer
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
if x.err != nil {
|
||||
return false, err
|
||||
return false, x.err
|
||||
}
|
||||
if x.val != nil {
|
||||
return false, err
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
@ -26,7 +28,7 @@ func (f *InputFieldWithHistory) AddToHistory(s string) {
|
|||
|
||||
// Used history data for search prompt, so just make that data recent.
|
||||
if f.historyPointer != len(f.history) && s == f.history[f.historyPointer] {
|
||||
f.history = append(f.history[:f.historyPointer], f.history[f.historyPointer+1:]...)
|
||||
f.history = slices.Delete(f.history, f.historyPointer, f.historyPointer+1)
|
||||
f.history = append(f.history, s)
|
||||
}
|
||||
|
||||
|
@ -51,17 +53,17 @@ func (f *InputFieldWithHistory) InputHandler() func(event *tcell.EventKey, setFo
|
|||
f.historyPointer++
|
||||
// Stop iterating over history.
|
||||
if f.historyPointer == len(f.history) {
|
||||
f.InputField.SetText(f.currentContent)
|
||||
f.SetText(f.currentContent)
|
||||
return
|
||||
}
|
||||
f.InputField.SetText(f.history[f.historyPointer])
|
||||
f.SetText(f.history[f.historyPointer])
|
||||
case tcell.KeyUp:
|
||||
if len(f.history) == 0 {
|
||||
return
|
||||
}
|
||||
// Start iterating over history.
|
||||
if f.historyPointer == len(f.history) {
|
||||
f.currentContent = f.InputField.GetText()
|
||||
f.currentContent = f.GetText()
|
||||
}
|
||||
// End of history.
|
||||
if f.historyPointer == 0 {
|
||||
|
@ -69,7 +71,7 @@ func (f *InputFieldWithHistory) InputHandler() func(event *tcell.EventKey, setFo
|
|||
}
|
||||
// Iterate to least recent prompts.
|
||||
f.historyPointer--
|
||||
f.InputField.SetText(f.history[f.historyPointer])
|
||||
f.SetText(f.history[f.historyPointer])
|
||||
default:
|
||||
f.InputField.InputHandler()(event, func(tview.Primitive) {})
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/assert"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
@ -62,10 +63,7 @@ func (v *RecordsView) Mount(ctx context.Context) error {
|
|||
|
||||
ctx, v.onUnmount = context.WithCancel(ctx)
|
||||
|
||||
tempBuffer, err := LoadRecords(ctx, v.ui.db, v.bucket.Path, v.ui.loadBufferSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempBuffer := LoadRecords(ctx, v.ui.db, v.bucket.Path, v.ui.loadBufferSize)
|
||||
|
||||
v.buffer = make(chan *Record, v.ui.loadBufferSize)
|
||||
go func() {
|
||||
|
@ -73,11 +71,12 @@ func (v *RecordsView) Mount(ctx context.Context) error {
|
|||
|
||||
for item := range tempBuffer {
|
||||
if item.err != nil {
|
||||
v.ui.stopOnError(err)
|
||||
v.ui.stopOnError(item.err)
|
||||
break
|
||||
}
|
||||
record := item.val
|
||||
|
||||
var err error
|
||||
record.Entry, _, err = v.bucket.NextParser(record.Key, record.Value)
|
||||
if err != nil {
|
||||
v.ui.stopOnError(err)
|
||||
|
@ -96,9 +95,7 @@ func (v *RecordsView) Mount(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (v *RecordsView) Unmount() {
|
||||
if v.onUnmount == nil {
|
||||
panic("try to unmount not mounted component")
|
||||
}
|
||||
assert.False(v.onUnmount == nil, "try to unmount not mounted component")
|
||||
v.onUnmount()
|
||||
v.onUnmount = nil
|
||||
}
|
||||
|
|
|
@ -482,7 +482,7 @@ func (ui *UI) handleInputOnSearching(event *tcell.EventKey) {
|
|||
ui.searchBar.InputHandler()(event, func(tview.Primitive) {})
|
||||
}
|
||||
|
||||
ui.Box.MouseHandler()
|
||||
ui.MouseHandler()
|
||||
}
|
||||
|
||||
func (ui *UI) WithPrompt(prompt string) error {
|
||||
|
|
|
@ -14,11 +14,12 @@ import (
|
|||
func initAPEManagerService(c *cfg) {
|
||||
contractStorage := ape_contract.NewProxyVerificationContractStorage(
|
||||
morph.NewSwitchRPCGuardedActor(c.cfgMorph.client),
|
||||
c.shared.key,
|
||||
c.key,
|
||||
c.cfgMorph.proxyScriptHash,
|
||||
c.cfgObject.cfgAccessPolicyEngine.policyContractHash)
|
||||
|
||||
execsvc := apemanager.New(c.cfgObject.cnrSource, contractStorage,
|
||||
c.cfgMorph.client,
|
||||
apemanager.WithLogger(c.log))
|
||||
sigsvc := apemanager.NewSignService(&c.key.PrivateKey, execsvc)
|
||||
auditSvc := apemanager.NewAuditService(sigsvc, c.log, c.audit)
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type netValueReader[K any, V any] func(K) (V, error)
|
||||
type netValueReader[K any, V any] func(ctx context.Context, cid K) (V, error)
|
||||
|
||||
type valueWithError[V any] struct {
|
||||
v V
|
||||
|
@ -49,7 +57,7 @@ func newNetworkTTLCache[K comparable, V any](sz int, ttl time.Duration, netRdr n
|
|||
// updates the value from the network on cache miss or by TTL.
|
||||
//
|
||||
// returned value should not be modified.
|
||||
func (c *ttlNetCache[K, V]) get(key K) (V, error) {
|
||||
func (c *ttlNetCache[K, V]) get(ctx context.Context, key K) (V, error) {
|
||||
hit := false
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
|
@ -71,7 +79,7 @@ func (c *ttlNetCache[K, V]) get(key K) (V, error) {
|
|||
return val.v, val.e
|
||||
}
|
||||
|
||||
v, err := c.netRdr(key)
|
||||
v, err := c.netRdr(ctx, key)
|
||||
|
||||
c.cache.Add(key, &valueWithError[V]{
|
||||
v: v,
|
||||
|
@ -109,55 +117,6 @@ func (c *ttlNetCache[K, V]) remove(key K) {
|
|||
hit = c.cache.Remove(key)
|
||||
}
|
||||
|
||||
// entity that provides LRU cache interface.
|
||||
type lruNetCache struct {
|
||||
cache *lru.Cache[uint64, *netmapSDK.NetMap]
|
||||
|
||||
netRdr netValueReader[uint64, *netmapSDK.NetMap]
|
||||
|
||||
metrics cacheMetrics
|
||||
}
|
||||
|
||||
// newNetworkLRUCache returns wrapper over netValueReader with LRU cache.
|
||||
func newNetworkLRUCache(sz int, netRdr netValueReader[uint64, *netmapSDK.NetMap], metrics cacheMetrics) *lruNetCache {
|
||||
cache, err := lru.New[uint64, *netmapSDK.NetMap](sz)
|
||||
fatalOnErr(err)
|
||||
|
||||
return &lruNetCache{
|
||||
cache: cache,
|
||||
netRdr: netRdr,
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
// reads value by the key.
|
||||
//
|
||||
// updates the value from the network on cache miss.
|
||||
//
|
||||
// returned value should not be modified.
|
||||
func (c *lruNetCache) get(key uint64) (*netmapSDK.NetMap, error) {
|
||||
hit := false
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
c.metrics.AddMethodDuration("Get", time.Since(startedAt), hit)
|
||||
}()
|
||||
|
||||
val, ok := c.cache.Get(key)
|
||||
if ok {
|
||||
hit = true
|
||||
return val, nil
|
||||
}
|
||||
|
||||
val, err := c.netRdr(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.cache.Add(key, val)
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// wrapper over TTL cache of values read from the network
|
||||
// that implements container storage.
|
||||
type ttlContainerStorage struct {
|
||||
|
@ -166,11 +125,11 @@ type ttlContainerStorage struct {
|
|||
}
|
||||
|
||||
func newCachedContainerStorage(v container.Source, ttl time.Duration, containerCacheSize uint32) ttlContainerStorage {
|
||||
lruCnrCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(id cid.ID) (*container.Container, error) {
|
||||
return v.Get(id)
|
||||
lruCnrCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(ctx context.Context, id cid.ID) (*container.Container, error) {
|
||||
return v.Get(ctx, id)
|
||||
}, metrics.NewCacheMetrics("container"))
|
||||
lruDelInfoCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(id cid.ID) (*container.DelInfo, error) {
|
||||
return v.DeletionInfo(id)
|
||||
lruDelInfoCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(ctx context.Context, id cid.ID) (*container.DelInfo, error) {
|
||||
return v.DeletionInfo(ctx, id)
|
||||
}, metrics.NewCacheMetrics("container_deletion_info"))
|
||||
|
||||
return ttlContainerStorage{
|
||||
|
@ -188,43 +147,259 @@ func (s ttlContainerStorage) handleRemoval(cnr cid.ID) {
|
|||
|
||||
// Get returns container value from the cache. If value is missing in the cache
|
||||
// or expired, then it returns value from side chain and updates the cache.
|
||||
func (s ttlContainerStorage) Get(cnr cid.ID) (*container.Container, error) {
|
||||
return s.containerCache.get(cnr)
|
||||
func (s ttlContainerStorage) Get(ctx context.Context, cnr cid.ID) (*container.Container, error) {
|
||||
return s.containerCache.get(ctx, cnr)
|
||||
}
|
||||
|
||||
func (s ttlContainerStorage) DeletionInfo(cnr cid.ID) (*container.DelInfo, error) {
|
||||
return s.delInfoCache.get(cnr)
|
||||
func (s ttlContainerStorage) DeletionInfo(ctx context.Context, cnr cid.ID) (*container.DelInfo, error) {
|
||||
return s.delInfoCache.get(ctx, cnr)
|
||||
}
|
||||
|
||||
type lruNetmapSource struct {
|
||||
netState netmap.State
|
||||
|
||||
cache *lruNetCache
|
||||
client rawSource
|
||||
cache *simplelru.LRU[uint64, *atomic.Pointer[netmapSDK.NetMap]]
|
||||
mtx sync.RWMutex
|
||||
metrics cacheMetrics
|
||||
log *logger.Logger
|
||||
candidates atomic.Pointer[[]netmapSDK.NodeInfo]
|
||||
}
|
||||
|
||||
func newCachedNetmapStorage(s netmap.State, v netmap.Source) netmap.Source {
|
||||
type rawSource interface {
|
||||
GetCandidates(ctx context.Context) ([]netmapSDK.NodeInfo, error)
|
||||
GetNetMapByEpoch(ctx context.Context, epoch uint64) (*netmapSDK.NetMap, error)
|
||||
}
|
||||
|
||||
func newCachedNetmapStorage(ctx context.Context, log *logger.Logger,
|
||||
netState netmap.State, client rawSource, wg *sync.WaitGroup, d time.Duration,
|
||||
) netmap.Source {
|
||||
const netmapCacheSize = 10
|
||||
|
||||
lruNetmapCache := newNetworkLRUCache(netmapCacheSize, func(key uint64) (*netmapSDK.NetMap, error) {
|
||||
return v.GetNetMapByEpoch(key)
|
||||
}, metrics.NewCacheMetrics("netmap"))
|
||||
cache, err := simplelru.NewLRU[uint64, *atomic.Pointer[netmapSDK.NetMap]](netmapCacheSize, nil)
|
||||
fatalOnErr(err)
|
||||
|
||||
return &lruNetmapSource{
|
||||
netState: s,
|
||||
cache: lruNetmapCache,
|
||||
src := &lruNetmapSource{
|
||||
netState: netState,
|
||||
client: client,
|
||||
cache: cache,
|
||||
log: log,
|
||||
metrics: metrics.NewCacheMetrics("netmap"),
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
src.updateCandidates(ctx, d)
|
||||
}()
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
// updateCandidates routine to merge netmap in cache with candidates list.
|
||||
func (s *lruNetmapSource) updateCandidates(ctx context.Context, d time.Duration) {
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-timer.C:
|
||||
newCandidates, err := s.client.GetCandidates(ctx)
|
||||
if err != nil {
|
||||
s.log.Debug(ctx, logs.FailedToUpdateNetmapCandidates, zap.Error(err))
|
||||
timer.Reset(d)
|
||||
break
|
||||
}
|
||||
if len(newCandidates) == 0 {
|
||||
s.candidates.Store(&newCandidates)
|
||||
timer.Reset(d)
|
||||
break
|
||||
}
|
||||
slices.SortFunc(newCandidates, func(n1 netmapSDK.NodeInfo, n2 netmapSDK.NodeInfo) int {
|
||||
return cmp.Compare(n1.Hash(), n2.Hash())
|
||||
})
|
||||
|
||||
// Check once state changed
|
||||
v := s.candidates.Load()
|
||||
if v == nil {
|
||||
s.candidates.Store(&newCandidates)
|
||||
s.mergeCacheWithCandidates(newCandidates)
|
||||
timer.Reset(d)
|
||||
break
|
||||
}
|
||||
ret := slices.CompareFunc(*v, newCandidates, func(n1 netmapSDK.NodeInfo, n2 netmapSDK.NodeInfo) int {
|
||||
if !bytes.Equal(n1.PublicKey(), n2.PublicKey()) ||
|
||||
uint32(n1.Status()) != uint32(n2.Status()) ||
|
||||
slices.Compare(n1.ExternalAddresses(), n2.ExternalAddresses()) != 0 {
|
||||
return 1
|
||||
}
|
||||
var ne1 []string
|
||||
n1.IterateNetworkEndpoints(func(s string) bool {
|
||||
ne1 = append(ne1, s)
|
||||
return false
|
||||
})
|
||||
var ne2 []string
|
||||
n2.IterateNetworkEndpoints(func(s string) bool {
|
||||
ne2 = append(ne2, s)
|
||||
return false
|
||||
})
|
||||
return slices.Compare(ne1, ne2)
|
||||
})
|
||||
if ret != 0 {
|
||||
s.candidates.Store(&newCandidates)
|
||||
s.mergeCacheWithCandidates(newCandidates)
|
||||
}
|
||||
timer.Reset(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) GetNetMap(diff uint64) (*netmapSDK.NetMap, error) {
|
||||
return s.getNetMapByEpoch(s.netState.CurrentEpoch() - diff)
|
||||
func (s *lruNetmapSource) mergeCacheWithCandidates(candidates []netmapSDK.NodeInfo) {
|
||||
s.mtx.Lock()
|
||||
tmp := s.cache.Values()
|
||||
s.mtx.Unlock()
|
||||
for _, pointer := range tmp {
|
||||
nm := pointer.Load()
|
||||
updates := getNetMapNodesToUpdate(nm, candidates)
|
||||
if len(updates) > 0 {
|
||||
nm = nm.Clone()
|
||||
mergeNetmapWithCandidates(updates, nm)
|
||||
pointer.Store(nm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) GetNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
return s.getNetMapByEpoch(epoch)
|
||||
// reads value by the key.
|
||||
//
|
||||
// updates the value from the network on cache miss.
|
||||
//
|
||||
// returned value should not be modified.
|
||||
func (s *lruNetmapSource) get(ctx context.Context, key uint64) (*netmapSDK.NetMap, error) {
|
||||
hit := false
|
||||
startedAt := time.Now()
|
||||
defer func() {
|
||||
s.metrics.AddMethodDuration("Get", time.Since(startedAt), hit)
|
||||
}()
|
||||
|
||||
s.mtx.RLock()
|
||||
val, ok := s.cache.Get(key)
|
||||
s.mtx.RUnlock()
|
||||
if ok {
|
||||
hit = true
|
||||
return val.Load(), nil
|
||||
}
|
||||
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
val, ok = s.cache.Get(key)
|
||||
if ok {
|
||||
hit = true
|
||||
return val.Load(), nil
|
||||
}
|
||||
|
||||
nm, err := s.client.GetNetMapByEpoch(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := s.candidates.Load()
|
||||
if v != nil {
|
||||
updates := getNetMapNodesToUpdate(nm, *v)
|
||||
if len(updates) > 0 {
|
||||
mergeNetmapWithCandidates(updates, nm)
|
||||
}
|
||||
}
|
||||
|
||||
p := atomic.Pointer[netmapSDK.NetMap]{}
|
||||
p.Store(nm)
|
||||
s.cache.Add(key, &p)
|
||||
|
||||
return nm, nil
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) getNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
val, err := s.cache.get(epoch)
|
||||
// mergeNetmapWithCandidates updates nodes state in the provided netmap with state in the list of candidates.
|
||||
func mergeNetmapWithCandidates(updates []nodeToUpdate, nm *netmapSDK.NetMap) {
|
||||
for _, v := range updates {
|
||||
if v.status != netmapSDK.UnspecifiedState {
|
||||
nm.Nodes()[v.netmapIndex].SetStatus(v.status)
|
||||
}
|
||||
if v.externalAddresses != nil {
|
||||
nm.Nodes()[v.netmapIndex].SetExternalAddresses(v.externalAddresses...)
|
||||
}
|
||||
if v.endpoints != nil {
|
||||
nm.Nodes()[v.netmapIndex].SetNetworkEndpoints(v.endpoints...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type nodeToUpdate struct {
|
||||
netmapIndex int
|
||||
status netmapSDK.NodeState
|
||||
externalAddresses []string
|
||||
endpoints []string
|
||||
}
|
||||
|
||||
// getNetMapNodesToUpdate checks for the changes between provided netmap and the list of candidates.
|
||||
func getNetMapNodesToUpdate(nm *netmapSDK.NetMap, candidates []netmapSDK.NodeInfo) []nodeToUpdate {
|
||||
var res []nodeToUpdate
|
||||
for i := range nm.Nodes() {
|
||||
for _, cnd := range candidates {
|
||||
if bytes.Equal(nm.Nodes()[i].PublicKey(), cnd.PublicKey()) {
|
||||
var tmp nodeToUpdate
|
||||
var update bool
|
||||
|
||||
if cnd.Status() != nm.Nodes()[i].Status() &&
|
||||
(cnd.Status() == netmapSDK.Online || cnd.Status() == netmapSDK.Maintenance) {
|
||||
update = true
|
||||
tmp.status = cnd.Status()
|
||||
}
|
||||
|
||||
externalAddresses := cnd.ExternalAddresses()
|
||||
if externalAddresses != nil &&
|
||||
slices.Compare(externalAddresses, nm.Nodes()[i].ExternalAddresses()) != 0 {
|
||||
update = true
|
||||
tmp.externalAddresses = externalAddresses
|
||||
}
|
||||
|
||||
nodeEndpoints := make([]string, 0, nm.Nodes()[i].NumberOfNetworkEndpoints())
|
||||
nm.Nodes()[i].IterateNetworkEndpoints(func(s string) bool {
|
||||
nodeEndpoints = append(nodeEndpoints, s)
|
||||
return false
|
||||
})
|
||||
candidateEndpoints := make([]string, 0, cnd.NumberOfNetworkEndpoints())
|
||||
cnd.IterateNetworkEndpoints(func(s string) bool {
|
||||
candidateEndpoints = append(candidateEndpoints, s)
|
||||
return false
|
||||
})
|
||||
if slices.Compare(nodeEndpoints, candidateEndpoints) != 0 {
|
||||
update = true
|
||||
tmp.endpoints = candidateEndpoints
|
||||
}
|
||||
|
||||
if update {
|
||||
tmp.netmapIndex = i
|
||||
res = append(res, tmp)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) GetNetMap(ctx context.Context, diff uint64) (*netmapSDK.NetMap, error) {
|
||||
return s.getNetMapByEpoch(ctx, s.netState.CurrentEpoch()-diff)
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) GetNetMapByEpoch(ctx context.Context, epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
return s.getNetMapByEpoch(ctx, epoch)
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) getNetMapByEpoch(ctx context.Context, epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
val, err := s.get(ctx, epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -232,7 +407,7 @@ func (s *lruNetmapSource) getNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, err
|
|||
return val, nil
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) Epoch() (uint64, error) {
|
||||
func (s *lruNetmapSource) Epoch(_ context.Context) (uint64, error) {
|
||||
return s.netState.CurrentEpoch(), nil
|
||||
}
|
||||
|
||||
|
@ -240,7 +415,10 @@ type cachedIRFetcher struct {
|
|||
*ttlNetCache[struct{}, [][]byte]
|
||||
}
|
||||
|
||||
func newCachedIRFetcher(f interface{ InnerRingKeys() ([][]byte, error) }) cachedIRFetcher {
|
||||
func newCachedIRFetcher(f interface {
|
||||
InnerRingKeys(ctx context.Context) ([][]byte, error)
|
||||
},
|
||||
) cachedIRFetcher {
|
||||
const (
|
||||
irFetcherCacheSize = 1 // we intend to store only one value
|
||||
|
||||
|
@ -254,8 +432,8 @@ func newCachedIRFetcher(f interface{ InnerRingKeys() ([][]byte, error) }) cached
|
|||
)
|
||||
|
||||
irFetcherCache := newNetworkTTLCache(irFetcherCacheSize, irFetcherCacheTTL,
|
||||
func(_ struct{}) ([][]byte, error) {
|
||||
return f.InnerRingKeys()
|
||||
func(ctx context.Context, _ struct{}) ([][]byte, error) {
|
||||
return f.InnerRingKeys(ctx)
|
||||
}, metrics.NewCacheMetrics("ir_keys"),
|
||||
)
|
||||
|
||||
|
@ -265,8 +443,8 @@ func newCachedIRFetcher(f interface{ InnerRingKeys() ([][]byte, error) }) cached
|
|||
// InnerRingKeys returns cached list of Inner Ring keys. If keys are missing in
|
||||
// the cache or expired, then it returns keys from side chain and updates
|
||||
// the cache.
|
||||
func (f cachedIRFetcher) InnerRingKeys() ([][]byte, error) {
|
||||
val, err := f.get(struct{}{})
|
||||
func (f cachedIRFetcher) InnerRingKeys(ctx context.Context) ([][]byte, error) {
|
||||
val, err := f.get(ctx, struct{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -289,7 +467,7 @@ func newCachedMaxObjectSizeSource(src objectwriter.MaxSizeSource) objectwriter.M
|
|||
}
|
||||
}
|
||||
|
||||
func (c *ttlMaxObjectSizeCache) MaxObjectSize() uint64 {
|
||||
func (c *ttlMaxObjectSizeCache) MaxObjectSize(ctx context.Context) uint64 {
|
||||
const ttl = time.Second * 30
|
||||
|
||||
hit := false
|
||||
|
@ -311,7 +489,7 @@ func (c *ttlMaxObjectSizeCache) MaxObjectSize() uint64 {
|
|||
c.mtx.Lock()
|
||||
size = c.lastSize
|
||||
if !c.lastUpdated.After(prevUpdated) {
|
||||
size = c.src.MaxObjectSize()
|
||||
size = c.src.MaxObjectSize(ctx)
|
||||
c.lastSize = size
|
||||
c.lastUpdated = time.Now()
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -17,7 +20,7 @@ func TestTTLNetCache(t *testing.T) {
|
|||
t.Run("Test Add and Get", func(t *testing.T) {
|
||||
ti := time.Now()
|
||||
cache.set(key, ti, nil)
|
||||
val, err := cache.get(key)
|
||||
val, err := cache.get(context.Background(), key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ti, val)
|
||||
})
|
||||
|
@ -26,7 +29,7 @@ func TestTTLNetCache(t *testing.T) {
|
|||
ti := time.Now()
|
||||
cache.set(key, ti, nil)
|
||||
time.Sleep(2 * ttlDuration)
|
||||
val, err := cache.get(key)
|
||||
val, err := cache.get(context.Background(), key)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, val, ti)
|
||||
})
|
||||
|
@ -35,20 +38,20 @@ func TestTTLNetCache(t *testing.T) {
|
|||
ti := time.Now()
|
||||
cache.set(key, ti, nil)
|
||||
cache.remove(key)
|
||||
val, err := cache.get(key)
|
||||
val, err := cache.get(context.Background(), key)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, val, ti)
|
||||
})
|
||||
|
||||
t.Run("Test Cache Error", func(t *testing.T) {
|
||||
cache.set("error", time.Now(), errors.New("mock error"))
|
||||
_, err := cache.get("error")
|
||||
_, err := cache.get(context.Background(), "error")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "mock error", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func testNetValueReader(key string) (time.Time, error) {
|
||||
func testNetValueReader(_ context.Context, key string) (time.Time, error) {
|
||||
if key == "error" {
|
||||
return time.Now(), errors.New("mock error")
|
||||
}
|
||||
|
@ -58,3 +61,75 @@ func testNetValueReader(key string) (time.Time, error) {
|
|||
type noopCacheMetricts struct{}
|
||||
|
||||
func (m *noopCacheMetricts) AddMethodDuration(method string, d time.Duration, hit bool) {}
|
||||
|
||||
type rawSrc struct{}
|
||||
|
||||
func (r *rawSrc) GetCandidates(_ context.Context) ([]netmapSDK.NodeInfo, error) {
|
||||
node0 := netmapSDK.NodeInfo{}
|
||||
node0.SetPublicKey([]byte{byte(1)})
|
||||
node0.SetStatus(netmapSDK.Online)
|
||||
node0.SetExternalAddresses("1", "0")
|
||||
node0.SetNetworkEndpoints("1", "0")
|
||||
|
||||
node1 := netmapSDK.NodeInfo{}
|
||||
node1.SetPublicKey([]byte{byte(1)})
|
||||
node1.SetStatus(netmapSDK.Online)
|
||||
node1.SetExternalAddresses("1", "0")
|
||||
node1.SetNetworkEndpoints("1", "0")
|
||||
|
||||
return []netmapSDK.NodeInfo{node0, node1}, nil
|
||||
}
|
||||
|
||||
func (r *rawSrc) GetNetMapByEpoch(ctx context.Context, epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
nm := netmapSDK.NetMap{}
|
||||
nm.SetEpoch(1)
|
||||
|
||||
node0 := netmapSDK.NodeInfo{}
|
||||
node0.SetPublicKey([]byte{byte(1)})
|
||||
node0.SetStatus(netmapSDK.Maintenance)
|
||||
node0.SetExternalAddresses("0")
|
||||
node0.SetNetworkEndpoints("0")
|
||||
|
||||
node1 := netmapSDK.NodeInfo{}
|
||||
node1.SetPublicKey([]byte{byte(1)})
|
||||
node1.SetStatus(netmapSDK.Maintenance)
|
||||
node1.SetExternalAddresses("0")
|
||||
node1.SetNetworkEndpoints("0")
|
||||
|
||||
nm.SetNodes([]netmapSDK.NodeInfo{node0, node1})
|
||||
|
||||
return &nm, nil
|
||||
}
|
||||
|
||||
type st struct{}
|
||||
|
||||
func (s *st) CurrentEpoch() uint64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func TestNetmapStorage(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
wg := sync.WaitGroup{}
|
||||
cache := newCachedNetmapStorage(ctx, nil, &st{}, &rawSrc{}, &wg, time.Millisecond*50)
|
||||
|
||||
nm, err := cache.GetNetMapByEpoch(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
require.True(t, nm.Nodes()[0].Status() == netmapSDK.Maintenance)
|
||||
require.True(t, len(nm.Nodes()[0].ExternalAddresses()) == 1)
|
||||
require.True(t, nm.Nodes()[0].NumberOfNetworkEndpoints() == 1)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
nm, err := cache.GetNetMapByEpoch(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
for _, node := range nm.Nodes() {
|
||||
if !(node.Status() == netmapSDK.Online && len(node.ExternalAddresses()) == 2 &&
|
||||
node.NumberOfNetworkEndpoints() == 2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, time.Second*5, time.Millisecond*10)
|
||||
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
|
||||
internalNet "git.frostfs.info/TrueCloudLab/frostfs-node/internal/net"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/qos"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/chainbase"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
||||
|
@ -69,6 +70,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/state"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-qos/limiting"
|
||||
netmapV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
|
@ -106,6 +108,7 @@ type applicationConfiguration struct {
|
|||
level string
|
||||
destination string
|
||||
timestamp bool
|
||||
options []zap.Option
|
||||
}
|
||||
|
||||
ObjectCfg struct {
|
||||
|
@ -115,7 +118,6 @@ type applicationConfiguration struct {
|
|||
|
||||
EngineCfg struct {
|
||||
errorThreshold uint32
|
||||
shardPoolSize uint32
|
||||
shards []shardCfg
|
||||
lowMem bool
|
||||
}
|
||||
|
@ -134,6 +136,7 @@ type shardCfg struct {
|
|||
refillMetabase bool
|
||||
refillMetabaseWorkersCount int
|
||||
mode shardmode.Mode
|
||||
limiter qos.Limiter
|
||||
|
||||
metaCfg struct {
|
||||
path string
|
||||
|
@ -230,6 +233,14 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
|
|||
a.LoggerCfg.level = loggerconfig.Level(c)
|
||||
a.LoggerCfg.destination = loggerconfig.Destination(c)
|
||||
a.LoggerCfg.timestamp = loggerconfig.Timestamp(c)
|
||||
var opts []zap.Option
|
||||
if loggerconfig.ToLokiConfig(c).Enabled {
|
||||
opts = []zap.Option{zap.WrapCore(func(core zapcore.Core) zapcore.Core {
|
||||
lokiCore := lokicore.New(core, loggerconfig.ToLokiConfig(c))
|
||||
return lokiCore
|
||||
})}
|
||||
}
|
||||
a.LoggerCfg.options = opts
|
||||
|
||||
// Object
|
||||
|
||||
|
@ -247,45 +258,47 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
|
|||
// Storage Engine
|
||||
|
||||
a.EngineCfg.errorThreshold = engineconfig.ShardErrorThreshold(c)
|
||||
a.EngineCfg.shardPoolSize = engineconfig.ShardPoolSize(c)
|
||||
a.EngineCfg.lowMem = engineconfig.EngineLowMemoryConsumption(c)
|
||||
|
||||
return engineconfig.IterateShards(c, false, func(sc *shardconfig.Config) error { return a.updateShardConfig(c, sc) })
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) updateShardConfig(c *config.Config, oldConfig *shardconfig.Config) error {
|
||||
var newConfig shardCfg
|
||||
func (a *applicationConfiguration) updateShardConfig(c *config.Config, source *shardconfig.Config) error {
|
||||
var target shardCfg
|
||||
|
||||
newConfig.refillMetabase = oldConfig.RefillMetabase()
|
||||
newConfig.refillMetabaseWorkersCount = oldConfig.RefillMetabaseWorkersCount()
|
||||
newConfig.mode = oldConfig.Mode()
|
||||
newConfig.compress = oldConfig.Compress()
|
||||
newConfig.estimateCompressibility = oldConfig.EstimateCompressibility()
|
||||
newConfig.estimateCompressibilityThreshold = oldConfig.EstimateCompressibilityThreshold()
|
||||
newConfig.uncompressableContentType = oldConfig.UncompressableContentTypes()
|
||||
newConfig.smallSizeObjectLimit = oldConfig.SmallSizeLimit()
|
||||
target.refillMetabase = source.RefillMetabase()
|
||||
target.refillMetabaseWorkersCount = source.RefillMetabaseWorkersCount()
|
||||
target.mode = source.Mode()
|
||||
target.compress = source.Compress()
|
||||
target.estimateCompressibility = source.EstimateCompressibility()
|
||||
target.estimateCompressibilityThreshold = source.EstimateCompressibilityThreshold()
|
||||
target.uncompressableContentType = source.UncompressableContentTypes()
|
||||
target.smallSizeObjectLimit = source.SmallSizeLimit()
|
||||
|
||||
a.setShardWriteCacheConfig(&newConfig, oldConfig)
|
||||
a.setShardWriteCacheConfig(&target, source)
|
||||
|
||||
a.setShardPiloramaConfig(c, &newConfig, oldConfig)
|
||||
a.setShardPiloramaConfig(c, &target, source)
|
||||
|
||||
if err := a.setShardStorageConfig(&newConfig, oldConfig); err != nil {
|
||||
if err := a.setShardStorageConfig(&target, source); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.setMetabaseConfig(&newConfig, oldConfig)
|
||||
a.setMetabaseConfig(&target, source)
|
||||
|
||||
a.setGCConfig(&newConfig, oldConfig)
|
||||
a.setGCConfig(&target, source)
|
||||
if err := a.setLimiter(&target, source); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.EngineCfg.shards = append(a.EngineCfg.shards, newConfig)
|
||||
a.EngineCfg.shards = append(a.EngineCfg.shards, target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) setShardWriteCacheConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
||||
writeCacheCfg := oldConfig.WriteCache()
|
||||
func (a *applicationConfiguration) setShardWriteCacheConfig(target *shardCfg, source *shardconfig.Config) {
|
||||
writeCacheCfg := source.WriteCache()
|
||||
if writeCacheCfg.Enabled() {
|
||||
wc := &newConfig.writecacheCfg
|
||||
wc := &target.writecacheCfg
|
||||
|
||||
wc.enabled = true
|
||||
wc.path = writeCacheCfg.Path()
|
||||
|
@ -298,10 +311,10 @@ func (a *applicationConfiguration) setShardWriteCacheConfig(newConfig *shardCfg,
|
|||
}
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) setShardPiloramaConfig(c *config.Config, newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
||||
func (a *applicationConfiguration) setShardPiloramaConfig(c *config.Config, target *shardCfg, source *shardconfig.Config) {
|
||||
if config.BoolSafe(c.Sub("tree"), "enabled") {
|
||||
piloramaCfg := oldConfig.Pilorama()
|
||||
pr := &newConfig.piloramaCfg
|
||||
piloramaCfg := source.Pilorama()
|
||||
pr := &target.piloramaCfg
|
||||
|
||||
pr.enabled = true
|
||||
pr.path = piloramaCfg.Path()
|
||||
|
@ -312,8 +325,8 @@ func (a *applicationConfiguration) setShardPiloramaConfig(c *config.Config, newC
|
|||
}
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) setShardStorageConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) error {
|
||||
blobStorCfg := oldConfig.BlobStor()
|
||||
func (a *applicationConfiguration) setShardStorageConfig(target *shardCfg, source *shardconfig.Config) error {
|
||||
blobStorCfg := source.BlobStor()
|
||||
storagesCfg := blobStorCfg.Storages()
|
||||
|
||||
ss := make([]subStorageCfg, 0, len(storagesCfg))
|
||||
|
@ -347,13 +360,13 @@ func (a *applicationConfiguration) setShardStorageConfig(newConfig *shardCfg, ol
|
|||
ss = append(ss, sCfg)
|
||||
}
|
||||
|
||||
newConfig.subStorages = ss
|
||||
target.subStorages = ss
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) setMetabaseConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
||||
metabaseCfg := oldConfig.Metabase()
|
||||
m := &newConfig.metaCfg
|
||||
func (a *applicationConfiguration) setMetabaseConfig(target *shardCfg, source *shardconfig.Config) {
|
||||
metabaseCfg := source.Metabase()
|
||||
m := &target.metaCfg
|
||||
|
||||
m.path = metabaseCfg.Path()
|
||||
m.perm = metabaseCfg.BoltDB().Perm()
|
||||
|
@ -361,12 +374,25 @@ func (a *applicationConfiguration) setMetabaseConfig(newConfig *shardCfg, oldCon
|
|||
m.maxBatchSize = metabaseCfg.BoltDB().MaxBatchSize()
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) setGCConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
||||
gcCfg := oldConfig.GC()
|
||||
newConfig.gcCfg.removerBatchSize = gcCfg.RemoverBatchSize()
|
||||
newConfig.gcCfg.removerSleepInterval = gcCfg.RemoverSleepInterval()
|
||||
newConfig.gcCfg.expiredCollectorBatchSize = gcCfg.ExpiredCollectorBatchSize()
|
||||
newConfig.gcCfg.expiredCollectorWorkerCount = gcCfg.ExpiredCollectorWorkerCount()
|
||||
func (a *applicationConfiguration) setGCConfig(target *shardCfg, source *shardconfig.Config) {
|
||||
gcCfg := source.GC()
|
||||
target.gcCfg.removerBatchSize = gcCfg.RemoverBatchSize()
|
||||
target.gcCfg.removerSleepInterval = gcCfg.RemoverSleepInterval()
|
||||
target.gcCfg.expiredCollectorBatchSize = gcCfg.ExpiredCollectorBatchSize()
|
||||
target.gcCfg.expiredCollectorWorkerCount = gcCfg.ExpiredCollectorWorkerCount()
|
||||
}
|
||||
|
||||
func (a *applicationConfiguration) setLimiter(target *shardCfg, source *shardconfig.Config) error {
|
||||
limitsConfig := source.Limits()
|
||||
limiter, err := qos.NewLimiter(limitsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.limiter != nil {
|
||||
target.limiter.Close()
|
||||
}
|
||||
target.limiter = limiter
|
||||
return nil
|
||||
}
|
||||
|
||||
// internals contains application-specific internals that are created
|
||||
|
@ -456,7 +482,6 @@ type shared struct {
|
|||
// dynamicConfiguration stores parameters of the
|
||||
// components that supports runtime reconfigurations.
|
||||
type dynamicConfiguration struct {
|
||||
logger *logger.Prm
|
||||
pprof *httpComponent
|
||||
metrics *httpComponent
|
||||
}
|
||||
|
@ -493,6 +518,7 @@ type cfg struct {
|
|||
cfgNetmap cfgNetmap
|
||||
cfgControlService cfgControlService
|
||||
cfgObject cfgObject
|
||||
cfgQoSService cfgQoSService
|
||||
}
|
||||
|
||||
// ReadCurrentNetMap reads network map which has been cached at the
|
||||
|
@ -527,6 +553,8 @@ type cfgGRPC struct {
|
|||
maxChunkSize uint64
|
||||
maxAddrAmount uint64
|
||||
reconnectTimeout time.Duration
|
||||
|
||||
limiter atomic.Pointer[limiting.SemaphoreLimiter]
|
||||
}
|
||||
|
||||
func (c *cfgGRPC) append(e string, l net.Listener, s *grpc.Server) {
|
||||
|
@ -663,10 +691,6 @@ type cfgAccessPolicyEngine struct {
|
|||
}
|
||||
|
||||
type cfgObjectRoutines struct {
|
||||
putRemote *ants.Pool
|
||||
|
||||
putLocal *ants.Pool
|
||||
|
||||
replication *ants.Pool
|
||||
}
|
||||
|
||||
|
@ -703,12 +727,6 @@ func initCfg(appCfg *config.Config) *cfg {
|
|||
logPrm.SamplingHook = c.metricsCollector.LogMetrics().GetSamplingHook()
|
||||
log, err := logger.NewLogger(logPrm)
|
||||
fatalOnErr(err)
|
||||
if loggerconfig.ToLokiConfig(appCfg).Enabled {
|
||||
log.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
|
||||
lokiCore := lokicore.New(core, loggerconfig.ToLokiConfig(appCfg))
|
||||
return lokiCore
|
||||
}))
|
||||
}
|
||||
|
||||
c.internals = initInternals(appCfg, log)
|
||||
|
||||
|
@ -852,14 +870,14 @@ func initFrostfsID(appCfg *config.Config) cfgFrostfsID {
|
|||
}
|
||||
}
|
||||
|
||||
func initCfgGRPC() cfgGRPC {
|
||||
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
||||
maxAddrAmount := uint64(maxChunkSize) / addressSize // each address is about 72 bytes
|
||||
func initCfgGRPC() (cfg cfgGRPC) {
|
||||
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
||||
maxAddrAmount := maxChunkSize / addressSize // each address is about 72 bytes
|
||||
|
||||
return cfgGRPC{
|
||||
maxChunkSize: maxChunkSize,
|
||||
maxAddrAmount: maxAddrAmount,
|
||||
}
|
||||
cfg.maxChunkSize = maxChunkSize
|
||||
cfg.maxAddrAmount = maxAddrAmount
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func initCfgObject(appCfg *config.Config) cfgObject {
|
||||
|
@ -876,7 +894,6 @@ func (c *cfg) engineOpts() []engine.Option {
|
|||
var opts []engine.Option
|
||||
|
||||
opts = append(opts,
|
||||
engine.WithShardPoolSize(c.EngineCfg.shardPoolSize),
|
||||
engine.WithErrorThreshold(c.EngineCfg.errorThreshold),
|
||||
engine.WithLogger(c.log),
|
||||
engine.WithLowMemoryConsumption(c.EngineCfg.lowMem),
|
||||
|
@ -916,6 +933,7 @@ func (c *cfg) getWriteCacheOpts(shCfg shardCfg) []writecache.Option {
|
|||
writecache.WithMaxCacheCount(wcRead.countLimit),
|
||||
writecache.WithNoSync(wcRead.noSync),
|
||||
writecache.WithLogger(c.log),
|
||||
writecache.WithQoSLimiter(shCfg.limiter),
|
||||
)
|
||||
}
|
||||
return writeCacheOpts
|
||||
|
@ -1031,6 +1049,7 @@ func (c *cfg) getShardOpts(ctx context.Context, shCfg shardCfg) shardOptsWithID
|
|||
}
|
||||
if c.metricsCollector != nil {
|
||||
mbOptions = append(mbOptions, meta.WithMetrics(lsmetrics.NewMetabaseMetrics(shCfg.metaCfg.path, c.metricsCollector.MetabaseMetrics())))
|
||||
shCfg.limiter.SetMetrics(c.metricsCollector.QoSMetrics())
|
||||
}
|
||||
|
||||
var sh shardOptsWithID
|
||||
|
@ -1055,30 +1074,28 @@ func (c *cfg) getShardOpts(ctx context.Context, shCfg shardCfg) shardOptsWithID
|
|||
|
||||
return pool
|
||||
}),
|
||||
shard.WithLimiter(shCfg.limiter),
|
||||
}
|
||||
return sh
|
||||
}
|
||||
|
||||
func (c *cfg) loggerPrm() (*logger.Prm, error) {
|
||||
// check if it has been inited before
|
||||
if c.dynamicConfiguration.logger == nil {
|
||||
c.dynamicConfiguration.logger = new(logger.Prm)
|
||||
}
|
||||
|
||||
func (c *cfg) loggerPrm() (logger.Prm, error) {
|
||||
var prm logger.Prm
|
||||
// (re)init read configuration
|
||||
err := c.dynamicConfiguration.logger.SetLevelString(c.LoggerCfg.level)
|
||||
err := prm.SetLevelString(c.LoggerCfg.level)
|
||||
if err != nil {
|
||||
// not expected since validation should be performed before
|
||||
panic("incorrect log level format: " + c.LoggerCfg.level)
|
||||
return logger.Prm{}, errors.New("incorrect log level format: " + c.LoggerCfg.level)
|
||||
}
|
||||
err = c.dynamicConfiguration.logger.SetDestination(c.LoggerCfg.destination)
|
||||
err = prm.SetDestination(c.LoggerCfg.destination)
|
||||
if err != nil {
|
||||
// not expected since validation should be performed before
|
||||
panic("incorrect log destination format: " + c.LoggerCfg.destination)
|
||||
return logger.Prm{}, errors.New("incorrect log destination format: " + c.LoggerCfg.destination)
|
||||
}
|
||||
c.dynamicConfiguration.logger.PrependTimestamp = c.LoggerCfg.timestamp
|
||||
prm.PrependTimestamp = c.LoggerCfg.timestamp
|
||||
prm.Options = c.LoggerCfg.options
|
||||
|
||||
return c.dynamicConfiguration.logger, nil
|
||||
return prm, nil
|
||||
}
|
||||
|
||||
func (c *cfg) LocalAddress() network.AddressGroup {
|
||||
|
@ -1147,7 +1164,7 @@ func initAccessPolicyEngine(ctx context.Context, c *cfg) {
|
|||
c.cfgObject.cfgAccessPolicyEngine.policyContractHash)
|
||||
|
||||
cacheSize := morphconfig.APEChainCacheSize(c.appCfg)
|
||||
if cacheSize > 0 {
|
||||
if cacheSize > 0 && c.cfgMorph.cacheTTL > 0 {
|
||||
morphRuleStorage = newMorphCache(morphRuleStorage, int(cacheSize), c.cfgMorph.cacheTTL)
|
||||
}
|
||||
|
||||
|
@ -1166,21 +1183,7 @@ func initAccessPolicyEngine(ctx context.Context, c *cfg) {
|
|||
func initObjectPool(cfg *config.Config) (pool cfgObjectRoutines) {
|
||||
var err error
|
||||
|
||||
optNonBlocking := ants.WithNonblocking(true)
|
||||
|
||||
putRemoteCapacity := objectconfig.Put(cfg).PoolSizeRemote()
|
||||
pool.putRemote, err = ants.NewPool(putRemoteCapacity, optNonBlocking)
|
||||
fatalOnErr(err)
|
||||
|
||||
putLocalCapacity := objectconfig.Put(cfg).PoolSizeLocal()
|
||||
pool.putLocal, err = ants.NewPool(putLocalCapacity, optNonBlocking)
|
||||
fatalOnErr(err)
|
||||
|
||||
replicatorPoolSize := replicatorconfig.PoolSize(cfg)
|
||||
if replicatorPoolSize <= 0 {
|
||||
replicatorPoolSize = putRemoteCapacity
|
||||
}
|
||||
|
||||
pool.replication, err = ants.NewPool(replicatorPoolSize)
|
||||
fatalOnErr(err)
|
||||
|
||||
|
@ -1206,7 +1209,7 @@ func (c *cfg) setContractNodeInfo(ni *netmap.NodeInfo) {
|
|||
}
|
||||
|
||||
func (c *cfg) updateContractNodeInfo(ctx context.Context, epoch uint64) {
|
||||
ni, err := c.netmapLocalNodeState(epoch)
|
||||
ni, err := c.netmapLocalNodeState(ctx, epoch)
|
||||
if err != nil {
|
||||
c.log.Error(ctx, logs.FrostFSNodeCouldNotUpdateNodeStateOnNewEpoch,
|
||||
zap.Uint64("epoch", epoch),
|
||||
|
@ -1220,9 +1223,9 @@ func (c *cfg) updateContractNodeInfo(ctx context.Context, epoch uint64) {
|
|||
// bootstrapWithState calls "addPeer" method of the Sidechain Netmap contract
|
||||
// with the binary-encoded information from the current node's configuration.
|
||||
// The state is set using the provided setter which MUST NOT be nil.
|
||||
func (c *cfg) bootstrapWithState(ctx context.Context, stateSetter func(*netmap.NodeInfo)) error {
|
||||
func (c *cfg) bootstrapWithState(ctx context.Context, state netmap.NodeState) error {
|
||||
ni := c.cfgNodeInfo.localInfo
|
||||
stateSetter(&ni)
|
||||
ni.SetStatus(state)
|
||||
|
||||
prm := nmClient.AddPeerPrm{}
|
||||
prm.SetNodeInfo(ni)
|
||||
|
@ -1232,9 +1235,7 @@ func (c *cfg) bootstrapWithState(ctx context.Context, stateSetter func(*netmap.N
|
|||
|
||||
// bootstrapOnline calls cfg.bootstrapWithState with "online" state.
|
||||
func bootstrapOnline(ctx context.Context, c *cfg) error {
|
||||
return c.bootstrapWithState(ctx, func(ni *netmap.NodeInfo) {
|
||||
ni.SetStatus(netmap.Online)
|
||||
})
|
||||
return c.bootstrapWithState(ctx, netmap.Online)
|
||||
}
|
||||
|
||||
// bootstrap calls bootstrapWithState with:
|
||||
|
@ -1245,9 +1246,7 @@ func (c *cfg) bootstrap(ctx context.Context) error {
|
|||
st := c.cfgNetmap.state.controlNetmapStatus()
|
||||
if st == control.NetmapStatus_MAINTENANCE {
|
||||
c.log.Info(ctx, logs.FrostFSNodeBootstrappingWithTheMaintenanceState)
|
||||
return c.bootstrapWithState(ctx, func(ni *netmap.NodeInfo) {
|
||||
ni.SetStatus(netmap.Maintenance)
|
||||
})
|
||||
return c.bootstrapWithState(ctx, netmap.Maintenance)
|
||||
}
|
||||
|
||||
c.log.Info(ctx, logs.FrostFSNodeBootstrappingWithOnlineState,
|
||||
|
@ -1336,15 +1335,7 @@ func (c *cfg) reloadConfig(ctx context.Context) {
|
|||
// all the components are expected to support
|
||||
// Logger's dynamic reconfiguration approach
|
||||
|
||||
// Logger
|
||||
|
||||
logPrm, err := c.loggerPrm()
|
||||
if err != nil {
|
||||
c.log.Error(ctx, logs.FrostFSNodeLoggerConfigurationPreparation, zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
components := c.getComponents(ctx, logPrm)
|
||||
components := c.getComponents(ctx)
|
||||
|
||||
// Object
|
||||
c.cfgObject.tombstoneLifetime.Store(c.ObjectCfg.tombstoneLifetime)
|
||||
|
@ -1382,10 +1373,17 @@ func (c *cfg) reloadConfig(ctx context.Context) {
|
|||
c.log.Info(ctx, logs.FrostFSNodeConfigurationHasBeenReloadedSuccessfully)
|
||||
}
|
||||
|
||||
func (c *cfg) getComponents(ctx context.Context, logPrm *logger.Prm) []dCmp {
|
||||
func (c *cfg) getComponents(ctx context.Context) []dCmp {
|
||||
var components []dCmp
|
||||
|
||||
components = append(components, dCmp{"logger", logPrm.Reload})
|
||||
components = append(components, dCmp{"logger", func() error {
|
||||
prm, err := c.loggerPrm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.log.Reload(prm)
|
||||
return nil
|
||||
}})
|
||||
components = append(components, dCmp{"runtime", func() error {
|
||||
setRuntimeParameters(ctx, c)
|
||||
return nil
|
||||
|
@ -1418,17 +1416,13 @@ func (c *cfg) getComponents(ctx context.Context, logPrm *logger.Prm) []dCmp {
|
|||
components = append(components, dCmp{cmp.name, func() error { return cmp.reload(ctx) }})
|
||||
}
|
||||
|
||||
components = append(components, dCmp{"rpc_limiter", func() error { return initRPCLimiter(c) }})
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
func (c *cfg) reloadPools() error {
|
||||
newSize := objectconfig.Put(c.appCfg).PoolSizeLocal()
|
||||
c.reloadPool(c.cfgObject.pool.putLocal, newSize, "object.put.local_pool_size")
|
||||
|
||||
newSize = objectconfig.Put(c.appCfg).PoolSizeRemote()
|
||||
c.reloadPool(c.cfgObject.pool.putRemote, newSize, "object.put.remote_pool_size")
|
||||
|
||||
newSize = replicatorconfig.PoolSize(c.appCfg)
|
||||
newSize := replicatorconfig.PoolSize(c.appCfg)
|
||||
c.reloadPool(c.cfgObject.pool.replication, newSize, "replicator.pool_size")
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
|
||||
|
@ -52,6 +53,5 @@ func (x *Config) Value(name string) any {
|
|||
// It supports only one level of nesting and is intended to be used
|
||||
// to provide default values.
|
||||
func (x *Config) SetDefault(from *Config) {
|
||||
x.defaultPath = make([]string, len(from.path))
|
||||
copy(x.defaultPath, from.path)
|
||||
x.defaultPath = slices.Clone(from.path)
|
||||
}
|
||||
|
|
|
@ -12,13 +12,10 @@ import (
|
|||
func TestConfigDir(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
cfgFileName0 := path.Join(dir, "cfg_00.json")
|
||||
cfgFileName1 := path.Join(dir, "cfg_01.yml")
|
||||
cfgFileName := path.Join(dir, "cfg_01.yml")
|
||||
|
||||
require.NoError(t, os.WriteFile(cfgFileName0, []byte(`{"storage":{"shard_pool_size":15}}`), 0o777))
|
||||
require.NoError(t, os.WriteFile(cfgFileName1, []byte("logger:\n level: debug"), 0o777))
|
||||
require.NoError(t, os.WriteFile(cfgFileName, []byte("logger:\n level: debug"), 0o777))
|
||||
|
||||
c := New("", dir, "")
|
||||
require.Equal(t, "debug", cast.ToString(c.Sub("logger").Value("level")))
|
||||
require.EqualValues(t, 15, cast.ToUint32(c.Sub("storage").Value("shard_pool_size")))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ const (
|
|||
subsection = "container"
|
||||
listStreamSubsection = "list_stream"
|
||||
|
||||
// ContainerBatchSizeDefault represents he maximum amount of containers to send via stream at once.
|
||||
// ContainerBatchSizeDefault represents the maximum amount of containers to send via stream at once.
|
||||
ContainerBatchSizeDefault = 1000
|
||||
)
|
||||
|
||||
|
|
|
@ -11,10 +11,6 @@ import (
|
|||
|
||||
const (
|
||||
subsection = "storage"
|
||||
|
||||
// ShardPoolSizeDefault is a default value of routine pool size per-shard to
|
||||
// process object PUT operations in a storage engine.
|
||||
ShardPoolSizeDefault = 20
|
||||
)
|
||||
|
||||
// ErrNoShardConfigured is returned when at least 1 shard is required but none are found.
|
||||
|
@ -65,18 +61,6 @@ func IterateShards(c *config.Config, required bool, f func(*shardconfig.Config)
|
|||
return nil
|
||||
}
|
||||
|
||||
// ShardPoolSize returns the value of "shard_pool_size" config parameter from "storage" section.
|
||||
//
|
||||
// Returns ShardPoolSizeDefault if the value is not a positive number.
|
||||
func ShardPoolSize(c *config.Config) uint32 {
|
||||
v := config.Uint32Safe(c.Sub(subsection), "shard_pool_size")
|
||||
if v > 0 {
|
||||
return v
|
||||
}
|
||||
|
||||
return ShardPoolSizeDefault
|
||||
}
|
||||
|
||||
// ShardErrorThreshold returns the value of "shard_ro_error_threshold" config parameter from "storage" section.
|
||||
//
|
||||
// Returns 0 if the the value is missing.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue