forked from TrueCloudLab/frostfs-node
Compare commits
295 commits
feat/ape_r
...
master
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
df05057ed4 | |||
b6c8ebf493 | |||
6e82661c35 | |||
1a091ea7bb | |||
7ac3542714 | |||
f0c43c8d80 | |||
8ba9f31fca | |||
2af3409d39 | |||
d165ac042c | |||
7151c71d51 | |||
91d9dc2676 | |||
7853dbc315 | |||
3821645085 | |||
72470d6b48 | |||
e9837bbcf9 | |||
a641c91594 | |||
b1614a284d | |||
d0ce835fbf | |||
dfa51048a8 | |||
670305a721 | |||
1f6cf57e30 | |||
386a12eea4 | |||
15139d80c9 | |||
41da27dad5 | |||
ac0511d214 | |||
7e542906ef | |||
d1bc4351c3 | |||
1c12f23b84 | |||
a353d45742 | |||
d5c46d812a | |||
d5d5ce2074 | |||
7df3520d48 | |||
5fe78e51d1 | |||
84b4051b4d | |||
6a51086030 | |||
5c3b2d95ba | |||
2d5d4093be | |||
e3487d5af5 | |||
e37dcdf88b | |||
6c679d1535 | |||
281d65435e | |||
b348b20289 | |||
748edd1999 | |||
47dfd8840c | |||
432042c534 | |||
9cabca9dfe | |||
60feed3b5f | |||
635a292ae4 | |||
edfa3f4825 | |||
e0ac3a583f | |||
00c608c05e | |||
bba1892fa1 | |||
01acec708f | |||
aac65001e5 | |||
1170370753 | |||
9e275d44c8 | |||
2469e0c683 | |||
a6ef4ab524 | |||
49959c4166 | |||
61ee1b5610 | |||
b10c954377 | |||
1605391628 | |||
b1766e47c7 | |||
caa4253249 | |||
7eac5fb18b | |||
0e5524dac7 | |||
3ebd560f42 | |||
1ed7ab75fb |
508 changed files with 10251 additions and 7892 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
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go_versions: [ '1.22', '1.23' ]
|
go_versions: [ '1.23', '1.24' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.24'
|
||||||
|
|
||||||
- name: Run commit format checker
|
- name: Run commit format checker
|
||||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v3
|
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
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.23
|
go-version: 1.24
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
run: |
|
run: |
|
||||||
apt update
|
apt update
|
||||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install linters
|
- name: Install linters
|
||||||
|
@ -30,7 +30,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go_versions: [ '1.22', '1.23' ]
|
go_versions: [ '1.23', '1.24' ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -53,7 +53,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.24'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
@ -68,7 +68,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install staticcheck
|
- name: Install staticcheck
|
||||||
|
@ -104,7 +104,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install gofumpt
|
- name: Install gofumpt
|
||||||
|
|
|
@ -18,7 +18,8 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
- name: Install govulncheck
|
- name: Install govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|
|
@ -22,6 +22,11 @@ linters-settings:
|
||||||
# 'default' case is present, even if all enum members aren't listed in the
|
# 'default' case is present, even if all enum members aren't listed in the
|
||||||
# switch
|
# switch
|
||||||
default-signifies-exhaustive: true
|
default-signifies-exhaustive: true
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
custom-order: true
|
||||||
govet:
|
govet:
|
||||||
# report about shadowed variables
|
# report about shadowed variables
|
||||||
check-shadowing: false
|
check-shadowing: false
|
||||||
|
@ -72,6 +77,7 @@ linters:
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
|
- gci
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
- misspell
|
- misspell
|
||||||
|
@ -89,5 +95,7 @@ linters:
|
||||||
- protogetter
|
- protogetter
|
||||||
- intrange
|
- intrange
|
||||||
- tenv
|
- tenv
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
disable-all: true
|
disable-all: true
|
||||||
fast: false
|
fast: false
|
||||||
|
|
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -9,6 +9,30 @@ Changelog for FrostFS Node
|
||||||
### Removed
|
### Removed
|
||||||
### Updated
|
### Updated
|
||||||
|
|
||||||
|
## [v0.44.0] - 2024-25-11 - Rongbuk
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Allow to prioritize nodes during GET traversal via attributes (#1439)
|
||||||
|
- Add metrics for the frostfsid cache (#1464)
|
||||||
|
- Customize constant attributes attached to every tracing span (#1488)
|
||||||
|
- Manage additional keys in the `frostfsid` contract (#1505)
|
||||||
|
- Describe `--rule` flag in detail for `frostfs-cli ape-manager` subcommands (#1519)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Support richer interaction with the console in `frostfs-cli container policy-playground` (#1396)
|
||||||
|
- Print address in base58 format in `frostfs-adm morph policy set-admin` (#1515)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix EC object search (#1408)
|
||||||
|
- Fix EC object put when one of the nodes is unavailable (#1427)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Drop most of the eACL-related code (#1425)
|
||||||
|
- Remove `--basic-acl` flag from `frostfs-cli container create` (#1483)
|
||||||
|
|
||||||
|
### Upgrading from v0.43.0
|
||||||
|
The metabase schema has changed completely, resync is required.
|
||||||
|
|
||||||
## [v0.42.0]
|
## [v0.42.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
3
CODEOWNERS
Normal file
3
CODEOWNERS
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.* @TrueCloudLab/storage-core-committers @TrueCloudLab/storage-core-developers
|
||||||
|
.forgejo/.* @potyarkin
|
||||||
|
Makefile @potyarkin
|
63
Makefile
63
Makefile
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
SHELL = bash
|
SHELL = bash
|
||||||
|
.SHELLFLAGS = -euo pipefail -c
|
||||||
|
|
||||||
REPO ?= $(shell go list -m)
|
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")
|
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||||
|
@ -7,8 +8,8 @@ VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8
|
||||||
HUB_IMAGE ?= git.frostfs.info/truecloudlab/frostfs
|
HUB_IMAGE ?= git.frostfs.info/truecloudlab/frostfs
|
||||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||||
|
|
||||||
GO_VERSION ?= 1.22
|
GO_VERSION ?= 1.23
|
||||||
LINT_VERSION ?= 1.62.0
|
LINT_VERSION ?= 1.62.2
|
||||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.8
|
TRUECLOUDLAB_LINT_VERSION ?= 0.0.8
|
||||||
PROTOC_VERSION ?= 25.0
|
PROTOC_VERSION ?= 25.0
|
||||||
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-sdk-go)
|
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-sdk-go)
|
||||||
|
@ -16,7 +17,7 @@ PROTOC_OS_VERSION=osx-x86_64
|
||||||
ifeq ($(shell uname), Linux)
|
ifeq ($(shell uname), Linux)
|
||||||
PROTOC_OS_VERSION=linux-x86_64
|
PROTOC_OS_VERSION=linux-x86_64
|
||||||
endif
|
endif
|
||||||
STATICCHECK_VERSION ?= 2024.1.1
|
STATICCHECK_VERSION ?= 2025.1.1
|
||||||
ARCH = amd64
|
ARCH = amd64
|
||||||
|
|
||||||
BIN = bin
|
BIN = bin
|
||||||
|
@ -42,7 +43,7 @@ GOFUMPT_VERSION ?= v0.7.0
|
||||||
GOFUMPT_DIR ?= $(abspath $(BIN))/gofumpt
|
GOFUMPT_DIR ?= $(abspath $(BIN))/gofumpt
|
||||||
GOFUMPT_VERSION_DIR ?= $(GOFUMPT_DIR)/$(GOFUMPT_VERSION)
|
GOFUMPT_VERSION_DIR ?= $(GOFUMPT_DIR)/$(GOFUMPT_VERSION)
|
||||||
|
|
||||||
GOPLS_VERSION ?= v0.15.1
|
GOPLS_VERSION ?= v0.17.1
|
||||||
GOPLS_DIR ?= $(abspath $(BIN))/gopls
|
GOPLS_DIR ?= $(abspath $(BIN))/gopls
|
||||||
GOPLS_VERSION_DIR ?= $(GOPLS_DIR)/$(GOPLS_VERSION)
|
GOPLS_VERSION_DIR ?= $(GOPLS_DIR)/$(GOPLS_VERSION)
|
||||||
GOPLS_TEMP_FILE := $(shell mktemp)
|
GOPLS_TEMP_FILE := $(shell mktemp)
|
||||||
|
@ -115,7 +116,7 @@ protoc:
|
||||||
# Install protoc
|
# Install protoc
|
||||||
protoc-install:
|
protoc-install:
|
||||||
@rm -rf $(PROTOBUF_DIR)
|
@rm -rf $(PROTOBUF_DIR)
|
||||||
@mkdir $(PROTOBUF_DIR)
|
@mkdir -p $(PROTOBUF_DIR)
|
||||||
@echo "⇒ Installing protoc... "
|
@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'
|
@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)
|
@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
|
# Build dirty local Docker images
|
||||||
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
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
|
# Run `make %` in Golang container
|
||||||
docker/%:
|
docker/%:
|
||||||
docker run --rm -t \
|
docker run --rm -t \
|
||||||
|
@ -160,7 +170,7 @@ imports:
|
||||||
# Install gofumpt
|
# Install gofumpt
|
||||||
fumpt-install:
|
fumpt-install:
|
||||||
@rm -rf $(GOFUMPT_DIR)
|
@rm -rf $(GOFUMPT_DIR)
|
||||||
@mkdir $(GOFUMPT_DIR)
|
@mkdir -p $(GOFUMPT_DIR)
|
||||||
@GOBIN=$(GOFUMPT_VERSION_DIR) go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION)
|
@GOBIN=$(GOFUMPT_VERSION_DIR) go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION)
|
||||||
|
|
||||||
# Run gofumpt
|
# Run gofumpt
|
||||||
|
@ -177,14 +187,37 @@ test:
|
||||||
@echo "⇒ Running go test"
|
@echo "⇒ Running go test"
|
||||||
@GOFLAGS="$(GOFLAGS)" 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
|
# Run pre-commit
|
||||||
pre-commit-run:
|
pre-commit-run:
|
||||||
@pre-commit run -a --hook-stage manual
|
@pre-commit run -a --hook-stage manual
|
||||||
|
|
||||||
# Install linters
|
# Install linters
|
||||||
lint-install:
|
lint-install: $(BIN)
|
||||||
@rm -rf $(OUTPUT_LINT_DIR)
|
@rm -rf $(OUTPUT_LINT_DIR)
|
||||||
@mkdir $(OUTPUT_LINT_DIR)
|
@mkdir -p $(OUTPUT_LINT_DIR)
|
||||||
@mkdir -p $(TMP_DIR)
|
@mkdir -p $(TMP_DIR)
|
||||||
@rm -rf $(TMP_DIR)/linters
|
@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
|
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||||
|
@ -203,7 +236,7 @@ lint:
|
||||||
# Install staticcheck
|
# Install staticcheck
|
||||||
staticcheck-install:
|
staticcheck-install:
|
||||||
@rm -rf $(STATICCHECK_DIR)
|
@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)
|
@GOBIN=$(STATICCHECK_VERSION_DIR) go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)
|
||||||
|
|
||||||
# Run staticcheck
|
# Run staticcheck
|
||||||
|
@ -216,7 +249,7 @@ staticcheck-run:
|
||||||
# Install gopls
|
# Install gopls
|
||||||
gopls-install:
|
gopls-install:
|
||||||
@rm -rf $(GOPLS_DIR)
|
@rm -rf $(GOPLS_DIR)
|
||||||
@mkdir $(GOPLS_DIR)
|
@mkdir -p $(GOPLS_DIR)
|
||||||
@GOBIN=$(GOPLS_VERSION_DIR) go install golang.org/x/tools/gopls@$(GOPLS_VERSION)
|
@GOBIN=$(GOPLS_VERSION_DIR) go install golang.org/x/tools/gopls@$(GOPLS_VERSION)
|
||||||
|
|
||||||
# Run gopls
|
# Run gopls
|
||||||
|
@ -270,10 +303,12 @@ env-up: all
|
||||||
echo "Frostfs contracts not found"; exit 1; \
|
echo "Frostfs contracts not found"; exit 1; \
|
||||||
fi
|
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 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 --gas 10.0 \
|
||||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet02.json --gas 10.0
|
--storage-wallet ./dev/storage/wallet01.json \
|
||||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet03.json --gas 10.0
|
--storage-wallet ./dev/storage/wallet02.json \
|
||||||
${BIN}/frostfs-adm --config ./dev/adm/frostfs-adm.yml morph refill-gas --storage-wallet ./dev/storage/wallet04.json --gas 10.0
|
--storage-wallet ./dev/storage/wallet03.json \
|
||||||
|
--storage-wallet ./dev/storage/wallet04.json
|
||||||
|
|
||||||
@if [ ! -f "$(LOCODE_DB_PATH)" ]; then \
|
@if [ ! -f "$(LOCODE_DB_PATH)" ]; then \
|
||||||
make locode-download; \
|
make locode-download; \
|
||||||
fi
|
fi
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
v0.42.0
|
v0.44.0
|
||||||
|
|
|
@ -16,9 +16,16 @@ const (
|
||||||
EndpointFlagDesc = "N3 RPC node endpoint"
|
EndpointFlagDesc = "N3 RPC node endpoint"
|
||||||
EndpointFlagShort = "r"
|
EndpointFlagShort = "r"
|
||||||
|
|
||||||
|
WalletPath = "wallet"
|
||||||
|
WalletPathShorthand = "w"
|
||||||
|
WalletPathUsage = "Path to the wallet"
|
||||||
|
|
||||||
AlphabetWalletsFlag = "alphabet-wallets"
|
AlphabetWalletsFlag = "alphabet-wallets"
|
||||||
AlphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
AlphabetWalletsFlagDesc = "Path to alphabet wallets dir"
|
||||||
|
|
||||||
|
AdminWalletPath = "wallet-admin"
|
||||||
|
AdminWalletUsage = "Path to the admin wallet"
|
||||||
|
|
||||||
LocalDumpFlag = "local-dump"
|
LocalDumpFlag = "local-dump"
|
||||||
ProtoConfigPath = "protocol"
|
ProtoConfigPath = "protocol"
|
||||||
ContractsInitFlag = "contracts"
|
ContractsInitFlag = "contracts"
|
||||||
|
|
|
@ -28,6 +28,7 @@ const (
|
||||||
var (
|
var (
|
||||||
errNoPathsFound = errors.New("no metabase paths found")
|
errNoPathsFound = errors.New("no metabase paths found")
|
||||||
errNoMorphEndpointsFound = errors.New("no morph endpoints found")
|
errNoMorphEndpointsFound = errors.New("no morph endpoints found")
|
||||||
|
errUpgradeFailed = errors.New("upgrade failed")
|
||||||
)
|
)
|
||||||
|
|
||||||
var UpgradeCmd = &cobra.Command{
|
var UpgradeCmd = &cobra.Command{
|
||||||
|
@ -91,14 +92,19 @@ func upgrade(cmd *cobra.Command, _ []string) error {
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
allSuccess := true
|
||||||
for mb, ok := range result {
|
for mb, ok := range result {
|
||||||
if ok {
|
if ok {
|
||||||
cmd.Println(mb, ": success")
|
cmd.Println(mb, ": success")
|
||||||
} else {
|
} else {
|
||||||
cmd.Println(mb, ": failed")
|
cmd.Println(mb, ": failed")
|
||||||
|
allSuccess = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if allSuccess {
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
return errUpgradeFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMetabasePaths(appCfg *config.Config) ([]string, error) {
|
func getMetabasePaths(appCfg *config.Config) ([]string, error) {
|
||||||
|
@ -135,7 +141,7 @@ func createContainerInfoProvider(cli *client.Client) (container.InfoProvider, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("resolve container contract hash: %w", err)
|
return nil, fmt.Errorf("resolve container contract hash: %w", err)
|
||||||
}
|
}
|
||||||
cc, err := morphcontainer.NewFromMorph(cli, sh, 0, morphcontainer.TryNotary())
|
cc, err := morphcontainer.NewFromMorph(cli, sh, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create morph container client: %w", err)
|
return nil, fmt.Errorf("create morph container client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
|
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
|
||||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -200,7 +200,7 @@ func listRuleChains(cmd *cobra.Command, _ []string) {
|
||||||
|
|
||||||
func setAdmin(cmd *cobra.Command, _ []string) {
|
func setAdmin(cmd *cobra.Command, _ []string) {
|
||||||
s, _ := cmd.Flags().GetString(addrAdminFlag)
|
s, _ := cmd.Flags().GetString(addrAdminFlag)
|
||||||
addr, err := util.Uint160DecodeStringLE(s)
|
addr, err := address.StringToUint160(s)
|
||||||
commonCmd.ExitOnErr(cmd, "can't decode admin addr: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't decode admin addr: %w", err)
|
||||||
pci, ac := newPolicyContractInterface(cmd)
|
pci, ac := newPolicyContractInterface(cmd)
|
||||||
h, vub, err := pci.SetAdmin(addr)
|
h, vub, err := pci.SetAdmin(addr)
|
||||||
|
@ -214,7 +214,7 @@ func getAdmin(cmd *cobra.Command, _ []string) {
|
||||||
pci, _ := newPolicyContractReaderInterface(cmd)
|
pci, _ := newPolicyContractReaderInterface(cmd)
|
||||||
addr, err := pci.GetAdmin()
|
addr, err := pci.GetAdmin()
|
||||||
commonCmd.ExitOnErr(cmd, "unable to get admin: %w", err)
|
commonCmd.ExitOnErr(cmd, "unable to get admin: %w", err)
|
||||||
cmd.Println(addr.StringLE())
|
cmd.Println(address.Uint160ToString(addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTargets(cmd *cobra.Command, _ []string) {
|
func listTargets(cmd *cobra.Command, _ []string) {
|
||||||
|
|
|
@ -3,6 +3,8 @@ package ape
|
||||||
import (
|
import (
|
||||||
"errors"
|
"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/constants"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
@ -53,16 +55,15 @@ func (n *invokerAdapter) GetRPCInvoker() invoker.RPCInvoke {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPolicyContractReaderInterface(cmd *cobra.Command) (*morph.ContractStorageReader, *invoker.Invoker) {
|
func newPolicyContractReaderInterface(cmd *cobra.Command) (*morph.ContractStorageReader, *invoker.Invoker) {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
var ch util.Uint160
|
|
||||||
r := management.NewReader(inv)
|
r := management.NewReader(inv)
|
||||||
nnsCs, err := helper.GetContractByID(r, 1)
|
nnsCs, err := helper.GetContractByID(r, 1)
|
||||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
|
||||||
|
|
||||||
ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
|
ch, err := helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
|
||||||
commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)
|
commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)
|
||||||
|
|
||||||
invokerAdapter := &invokerAdapter{
|
invokerAdapter := &invokerAdapter{
|
||||||
|
@ -74,10 +75,11 @@ func newPolicyContractReaderInterface(cmd *cobra.Command) (*morph.ContractStorag
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *helper.LocalActor) {
|
func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *helper.LocalActor) {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||||
|
|
||||||
var ch util.Uint160
|
var ch util.Uint160
|
||||||
|
|
|
@ -51,7 +51,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||||
nmHash util.Uint160
|
nmHash util.Uint160
|
||||||
)
|
)
|
||||||
|
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import (
|
||||||
const forceConfigSet = "force"
|
const forceConfigSet = "force"
|
||||||
|
|
||||||
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,14 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
||||||
nbuf := make([]byte, 8)
|
nbuf := make([]byte, 8)
|
||||||
copy(nbuf[:], v)
|
copy(nbuf[:], v)
|
||||||
n := binary.LittleEndian.Uint64(nbuf)
|
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:
|
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||||
if len(v) == 0 || len(v) > 1 {
|
if len(v) == 0 || len(v) > 1 {
|
||||||
return helper.InvalidConfigValueErr(k)
|
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:
|
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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("invalid filename: %w", err)
|
return fmt.Errorf("invalid filename: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invo
|
||||||
}
|
}
|
||||||
|
|
||||||
func listContainers(cmd *cobra.Command, _ []string) error {
|
func listContainers(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ type contractDumpInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create N3 client: %w", err)
|
return fmt.Errorf("can't create N3 client: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -219,8 +219,8 @@ func printContractInfo(cmd *cobra.Command, infos []contractDumpInfo) {
|
||||||
if info.version == "" {
|
if info.version == "" {
|
||||||
info.version = "unknown"
|
info.version = "unknown"
|
||||||
}
|
}
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t(%s):\t%s\n",
|
_, _ = tw.Write(fmt.Appendf(nil, "%s\t(%s):\t%s\n",
|
||||||
info.name, info.version, info.hash.StringLE())))
|
info.name, info.version, info.hash.StringLE()))
|
||||||
}
|
}
|
||||||
_ = tw.Flush()
|
_ = tw.Flush()
|
||||||
|
|
||||||
|
|
|
@ -34,11 +34,16 @@ const (
|
||||||
subjectNameFlag = "subject-name"
|
subjectNameFlag = "subject-name"
|
||||||
subjectKeyFlag = "subject-key"
|
subjectKeyFlag = "subject-key"
|
||||||
subjectAddressFlag = "subject-address"
|
subjectAddressFlag = "subject-address"
|
||||||
includeNamesFlag = "include-names"
|
extendedFlag = "extended"
|
||||||
groupNameFlag = "group-name"
|
groupNameFlag = "group-name"
|
||||||
groupIDFlag = "group-id"
|
groupIDFlag = "group-id"
|
||||||
|
|
||||||
rootNamespacePlaceholder = "<root>"
|
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 (
|
var (
|
||||||
|
@ -152,6 +157,23 @@ var (
|
||||||
},
|
},
|
||||||
Run: frostfsidListGroupSubjects,
|
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() {
|
func initFrostfsIDCreateNamespaceCmd() {
|
||||||
|
@ -187,7 +209,7 @@ func initFrostfsIDListSubjectsCmd() {
|
||||||
Cmd.AddCommand(frostfsidListSubjectsCmd)
|
Cmd.AddCommand(frostfsidListSubjectsCmd)
|
||||||
frostfsidListSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
frostfsidListSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects")
|
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() {
|
func initFrostfsIDCreateGroupCmd() {
|
||||||
|
@ -234,7 +256,22 @@ func initFrostfsIDListGroupSubjectsCmd() {
|
||||||
frostfsidListGroupSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
frostfsidListGroupSubjectsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
frostfsidListGroupSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace name")
|
||||||
frostfsidListGroupSubjectsCmd.Flags().Int64(groupIDFlag, 0, "Group id")
|
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) {
|
func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -254,7 +291,7 @@ func frostfsidListNamespaces(cmd *cobra.Command, _ []string) {
|
||||||
reader := frostfsidrpclient.NewReader(inv, hash)
|
reader := frostfsidrpclient.NewReader(inv, hash)
|
||||||
sessionID, it, err := reader.ListNamespaces()
|
sessionID, it, err := reader.ListNamespaces()
|
||||||
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
|
||||||
|
|
||||||
namespaces, err := frostfsidclient.ParseNamespaces(items)
|
namespaces, err := frostfsidclient.ParseNamespaces(items)
|
||||||
|
@ -299,34 +336,32 @@ func frostfsidDeleteSubject(cmd *cobra.Command, _ []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func frostfsidListSubjects(cmd *cobra.Command, _ []string) {
|
func frostfsidListSubjects(cmd *cobra.Command, _ []string) {
|
||||||
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
|
extended, _ := cmd.Flags().GetBool(extendedFlag)
|
||||||
ns := getFrostfsIDNamespace(cmd)
|
ns := getFrostfsIDNamespace(cmd)
|
||||||
inv, _, hash := initInvoker(cmd)
|
inv, _, hash := initInvoker(cmd)
|
||||||
reader := frostfsidrpclient.NewReader(inv, hash)
|
reader := frostfsidrpclient.NewReader(inv, hash)
|
||||||
sessionID, it, err := reader.ListNamespaceSubjects(ns)
|
sessionID, it, err := reader.ListNamespaceSubjects(ns)
|
||||||
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't unwrap: %w", err)
|
||||||
|
|
||||||
sort.Slice(subAddresses, func(i, j int) bool { return subAddresses[i].Less(subAddresses[j]) })
|
sort.Slice(subAddresses, func(i, j int) bool { return subAddresses[i].Less(subAddresses[j]) })
|
||||||
|
|
||||||
for _, addr := range subAddresses {
|
for _, addr := range subAddresses {
|
||||||
if !includeNames {
|
if !extended {
|
||||||
cmd.Println(address.Uint160ToString(addr))
|
cmd.Println(address.Uint160ToString(addr))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionID, it, err := reader.ListSubjects()
|
items, err := reader.GetSubject(addr)
|
||||||
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
|
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)
|
subj, err := frostfsidclient.ParseSubject(items)
|
||||||
commonCmd.ExitOnErr(cmd, "can't parse subject: %w", err)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +401,7 @@ func frostfsidListGroups(cmd *cobra.Command, _ []string) {
|
||||||
sessionID, it, err := reader.ListGroups(ns)
|
sessionID, it, err := reader.ListGroups(ns)
|
||||||
commonCmd.ExitOnErr(cmd, "can't get namespace: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't list groups: %w", err)
|
||||||
groups, err := frostfsidclient.ParseGroups(items)
|
groups, err := frostfsidclient.ParseGroups(items)
|
||||||
commonCmd.ExitOnErr(cmd, "can't parse groups: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't parse groups: %w", err)
|
||||||
|
@ -404,10 +439,49 @@ func frostfsidRemoveSubjectFromGroup(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "remove subject from group error: %w", err)
|
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) {
|
func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
||||||
ns := getFrostfsIDNamespace(cmd)
|
ns := getFrostfsIDNamespace(cmd)
|
||||||
groupID := getFrostfsIDGroupID(cmd)
|
groupID := getFrostfsIDGroupID(cmd)
|
||||||
includeNames, _ := cmd.Flags().GetBool(includeNamesFlag)
|
extended, _ := cmd.Flags().GetBool(extendedFlag)
|
||||||
inv, cs, hash := initInvoker(cmd)
|
inv, cs, hash := initInvoker(cmd)
|
||||||
_, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.FrostfsIDContract))
|
_, err := helper.NNSResolveHash(inv, cs.Hash, helper.DomainOf(constants.FrostfsIDContract))
|
||||||
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||||
|
@ -416,7 +490,7 @@ func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
||||||
sessionID, it, err := reader.ListGroupSubjects(ns, big.NewInt(groupID))
|
sessionID, it, err := reader.ListGroupSubjects(ns, big.NewInt(groupID))
|
||||||
commonCmd.ExitOnErr(cmd, "can't list groups: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
|
||||||
|
|
||||||
subjects, err := frostfsidclient.UnwrapArrayOfUint160(items, err)
|
subjects, err := frostfsidclient.UnwrapArrayOfUint160(items, err)
|
||||||
|
@ -425,7 +499,7 @@ func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
||||||
sort.Slice(subjects, func(i, j int) bool { return subjects[i].Less(subjects[j]) })
|
sort.Slice(subjects, func(i, j int) bool { return subjects[i].Less(subjects[j]) })
|
||||||
|
|
||||||
for _, subjAddr := range subjects {
|
for _, subjAddr := range subjects {
|
||||||
if !includeNames {
|
if !extended {
|
||||||
cmd.Println(address.Uint160ToString(subjAddr))
|
cmd.Println(address.Uint160ToString(subjAddr))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -434,7 +508,8 @@ func frostfsidListGroupSubjects(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't get subject: %w", err)
|
||||||
subj, err := frostfsidclient.ParseSubject(items)
|
subj, err := frostfsidclient.ParseSubject(items)
|
||||||
commonCmd.ExitOnErr(cmd, "can't parse subject: %w", err)
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,32 +564,28 @@ func (f *frostfsidClient) sendWaitRes() (*state.AppExecResult, error) {
|
||||||
}
|
}
|
||||||
f.bw.Reset()
|
f.bw.Reset()
|
||||||
|
|
||||||
if len(f.wCtx.SentTxs) == 0 {
|
|
||||||
return nil, errors.New("no transactions to wait")
|
|
||||||
}
|
|
||||||
|
|
||||||
f.wCtx.Command.Println("Waiting for transactions to persist...")
|
f.wCtx.Command.Println("Waiting for transactions to persist...")
|
||||||
return f.roCli.Wait(f.wCtx.SentTxs[0].Hash, f.wCtx.SentTxs[0].Vub, nil)
|
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
|
var shouldStop bool
|
||||||
res := make([]stackitem.Item, 0)
|
res := make([]stackitem.Item, 0)
|
||||||
for !shouldStop {
|
for !shouldStop {
|
||||||
items, err := inv.TraverseIterator(sessionID, iter, batchSize)
|
items, err := inv.TraverseIterator(sessionID, iter, iteratorBatchSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, items...)
|
res = append(res, items...)
|
||||||
shouldStop = len(items) < batchSize
|
shouldStop = len(items) < iteratorBatchSize
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initInvoker(cmd *cobra.Command) (*invoker.Invoker, *state.Contract, util.Uint160) {
|
func initInvoker(cmd *cobra.Command) (*invoker.Invoker, *state.Contract, util.Uint160) {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
@ -528,3 +599,30 @@ func initInvoker(cmd *cobra.Command) (*invoker.Invoker, *state.Contract, util.Ui
|
||||||
|
|
||||||
return inv, cs, nmHash
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,59 +1,12 @@
|
||||||
package frostfsid
|
package frostfsid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/ape"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFrostfsIDConfig(t *testing.T) {
|
|
||||||
pks := make([]*keys.PrivateKey, 4)
|
|
||||||
for i := range pks {
|
|
||||||
pk, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
pks[i] = pk
|
|
||||||
}
|
|
||||||
|
|
||||||
fmts := []string{
|
|
||||||
pks[0].GetScriptHash().StringLE(),
|
|
||||||
address.Uint160ToString(pks[1].GetScriptHash()),
|
|
||||||
hex.EncodeToString(pks[2].PublicKey().UncompressedBytes()),
|
|
||||||
hex.EncodeToString(pks[3].PublicKey().Bytes()),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range fmts {
|
|
||||||
v := viper.New()
|
|
||||||
v.Set("frostfsid.admin", fmts[i])
|
|
||||||
|
|
||||||
actual, found, err := helper.GetFrostfsIDAdmin(v)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
require.Equal(t, pks[i].GetScriptHash(), actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("bad key", func(t *testing.T) {
|
|
||||||
v := viper.New()
|
|
||||||
v.Set("frostfsid.admin", "abc")
|
|
||||||
|
|
||||||
_, found, err := helper.GetFrostfsIDAdmin(v)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.True(t, found)
|
|
||||||
})
|
|
||||||
t.Run("missing key", func(t *testing.T) {
|
|
||||||
v := viper.New()
|
|
||||||
|
|
||||||
_, found, err := helper.GetFrostfsIDAdmin(v)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, found)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNamespaceRegexp(t *testing.T) {
|
func TestNamespaceRegexp(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
@ -12,6 +12,8 @@ func init() {
|
||||||
initFrostfsIDAddSubjectToGroupCmd()
|
initFrostfsIDAddSubjectToGroupCmd()
|
||||||
initFrostfsIDRemoveSubjectFromGroupCmd()
|
initFrostfsIDRemoveSubjectFromGroupCmd()
|
||||||
initFrostfsIDListGroupSubjectsCmd()
|
initFrostfsIDListGroupSubjectsCmd()
|
||||||
|
initFrostfsIDSetKVCmd()
|
||||||
|
initFrostfsIDDeleteKVCmd()
|
||||||
initFrostfsIDAddSubjectKeyCmd()
|
initFrostfsIDAddSubjectKeyCmd()
|
||||||
initFrostfsIDRemoveSubjectKeyCmd()
|
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/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"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/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
@ -141,41 +140,11 @@ func addMultisigAccount(w *wallet.Wallet, m int, name, password string, pubs key
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateStorageCreds(cmd *cobra.Command, _ []string) error {
|
func generateStorageCreds(cmd *cobra.Command, _ []string) error {
|
||||||
return refillGas(cmd, storageGasConfigFlag, true)
|
walletPath, _ := cmd.Flags().GetString(commonflags.StorageWalletFlag)
|
||||||
}
|
w, err := wallet.NewWallet(walletPath)
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid wallet address %s: %w", walletAddress, err)
|
return fmt.Errorf("create wallet: %w", 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)
|
label, _ := cmd.Flags().GetString(storageWalletLabelFlag)
|
||||||
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
password, err := config.GetStoragePassword(viper.GetViper(), label)
|
||||||
|
@ -190,11 +159,10 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
||||||
if err := w.CreateAccount(label, password); err != nil {
|
if err := w.CreateAccount(label, password); err != nil {
|
||||||
return fmt.Errorf("can't create account: %w", err)
|
return fmt.Errorf("can't create account: %w", err)
|
||||||
}
|
}
|
||||||
}
|
return refillGas(cmd, storageGasConfigFlag, w.Accounts[0].ScriptHash())
|
||||||
|
}
|
||||||
gasReceiver = w.Accounts[0].Contract.ScriptHash()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func refillGas(cmd *cobra.Command, gasFlag string, gasReceivers ...util.Uint160) (err error) {
|
||||||
gasStr := viper.GetString(gasFlag)
|
gasStr := viper.GetString(gasFlag)
|
||||||
|
|
||||||
gasAmount, err := helper.ParseGASAmount(gasStr)
|
gasAmount, err := helper.ParseGASAmount(gasStr)
|
||||||
|
@ -208,9 +176,11 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
|
||||||
}
|
}
|
||||||
|
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
|
for _, gasReceiver := range gasReceivers {
|
||||||
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
emit.AppCall(bw.BinWriter, gas.Hash, "transfer", callflag.All,
|
||||||
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
wCtx.CommitteeAcc.Contract.ScriptHash(), gasReceiver, int64(gasAmount), nil)
|
||||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||||
|
}
|
||||||
if bw.Err != nil {
|
if bw.Err != nil {
|
||||||
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package generate
|
package generate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"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/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -33,7 +38,27 @@ var (
|
||||||
_ = viper.BindPFlag(commonflags.RefillGasAmountFlag, cmd.Flags().Lookup(commonflags.RefillGasAmountFlag))
|
_ = viper.BindPFlag(commonflags.RefillGasAmountFlag, cmd.Flags().Lookup(commonflags.RefillGasAmountFlag))
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
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{
|
GenerateAlphabetCmd = &cobra.Command{
|
||||||
|
@ -50,10 +75,10 @@ var (
|
||||||
func initRefillGasCmd() {
|
func initRefillGasCmd() {
|
||||||
RefillGasCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
RefillGasCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
RefillGasCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
RefillGasCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
RefillGasCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
RefillGasCmd.Flags().StringArray(commonflags.StorageWalletFlag, nil, "Path to storage node wallet")
|
||||||
RefillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
|
RefillGasCmd.Flags().StringArray(walletAddressFlag, nil, "Address of wallet")
|
||||||
RefillGasCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
RefillGasCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
|
||||||
RefillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, commonflags.StorageWalletFlag)
|
RefillGasCmd.MarkFlagsOneRequired(walletAddressFlag, commonflags.StorageWalletFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initGenerateStorageCmd() {
|
func initGenerateStorageCmd() {
|
||||||
|
|
|
@ -3,9 +3,6 @@ package helper
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,32 +24,86 @@ type LocalActor struct {
|
||||||
rpcInvoker invoker.RPCInvoke
|
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.
|
// NewLocalActor create LocalActor with accounts form provided wallets.
|
||||||
// In case of empty wallets provided created actor with dummy account only for read operation.
|
// 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.
|
// 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
|
// To determine which account name should be used in a contract client, refer to how the contract
|
||||||
// verifies the transaction signature.
|
// verifies the transaction signature.
|
||||||
func NewLocalActor(cmd *cobra.Command, c actor.RPCActor, accName string) (*LocalActor, error) {
|
func NewLocalActor(c actor.RPCActor, alphabet *AlphabetWallets, regularWallets ...*RegularWallets) (*LocalActor, error) {
|
||||||
walletDir := config.ResolveHomePath(viper.GetString(commonflags.AlphabetWalletsFlag))
|
|
||||||
var act *actor.Actor
|
var act *actor.Actor
|
||||||
var accounts []*wallet.Account
|
var accounts []*wallet.Account
|
||||||
|
var signers []actor.SignerAccount
|
||||||
|
|
||||||
wallets, err := GetAlphabetWallets(viper.GetViper(), walletDir)
|
if alphabet != nil {
|
||||||
commonCmd.ExitOnErr(cmd, "unable to get alphabet wallets: %w", err)
|
account, err := alphabet.GetAccount(viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
for _, w := range wallets {
|
return nil, err
|
||||||
acc, err := GetWalletAccount(w, accName)
|
|
||||||
commonCmd.ExitOnErr(cmd, fmt.Sprintf("can't find %s account: %%w", accName), err)
|
|
||||||
accounts = append(accounts, acc)
|
|
||||||
}
|
}
|
||||||
act, err = actor.New(c, []actor.SignerAccount{{
|
|
||||||
|
accounts = append(accounts, account...)
|
||||||
|
signers = append(signers, actor.SignerAccount{
|
||||||
Signer: transaction.Signer{
|
Signer: transaction.Signer{
|
||||||
Account: accounts[0].Contract.ScriptHash(),
|
Account: account[0].Contract.ScriptHash(),
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
},
|
},
|
||||||
Account: accounts[0],
|
Account: account[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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ func GetContractDeployData(c *InitializeContext, ctrName string, keysParam []any
|
||||||
h, found, err = getFrostfsIDAdminFromContract(c.ReadOnlyInvoker)
|
h, found, err = getFrostfsIDAdminFromContract(c.ReadOnlyInvoker)
|
||||||
}
|
}
|
||||||
if method != constants.UpdateMethodName || err == nil && !found {
|
if method != constants.UpdateMethodName || err == nil && !found {
|
||||||
h, found, err = GetFrostfsIDAdmin(viper.GetViper())
|
h, found, err = getFrostfsIDAdmin(viper.GetViper())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
const frostfsIDAdminConfigKey = "frostfsid.admin"
|
const frostfsIDAdminConfigKey = "frostfsid.admin"
|
||||||
|
|
||||||
func GetFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) {
|
func getFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) {
|
||||||
admin := v.GetString(frostfsIDAdminConfigKey)
|
admin := v.GetString(frostfsIDAdminConfigKey)
|
||||||
if admin == "" {
|
if admin == "" {
|
||||||
return util.Uint160{}, false, nil
|
return util.Uint160{}, false, nil
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFrostfsIDConfig(t *testing.T) {
|
||||||
|
pks := make([]*keys.PrivateKey, 4)
|
||||||
|
for i := range pks {
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
pks[i] = pk
|
||||||
|
}
|
||||||
|
|
||||||
|
fmts := []string{
|
||||||
|
pks[0].GetScriptHash().StringLE(),
|
||||||
|
address.Uint160ToString(pks[1].GetScriptHash()),
|
||||||
|
hex.EncodeToString(pks[2].PublicKey().UncompressedBytes()),
|
||||||
|
hex.EncodeToString(pks[3].PublicKey().Bytes()),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range fmts {
|
||||||
|
v := viper.New()
|
||||||
|
v.Set("frostfsid.admin", fmts[i])
|
||||||
|
|
||||||
|
actual, found, err := getFrostfsIDAdmin(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, found)
|
||||||
|
require.Equal(t, pks[i].GetScriptHash(), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("bad key", func(t *testing.T) {
|
||||||
|
v := viper.New()
|
||||||
|
v.Set("frostfsid.admin", "abc")
|
||||||
|
|
||||||
|
_, found, err := getFrostfsIDAdmin(v)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.True(t, found)
|
||||||
|
})
|
||||||
|
t.Run("missing key", func(t *testing.T) {
|
||||||
|
v := viper.New()
|
||||||
|
|
||||||
|
_, found, err := getFrostfsIDAdmin(v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, found)
|
||||||
|
})
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
"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/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
"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/constants"
|
||||||
|
@ -13,9 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"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/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"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"
|
"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/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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) {
|
func NNSIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||||
switch c.(type) {
|
|
||||||
case *rpcclient.Client:
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
reader := nns2.NewReader(inv, nnsHash)
|
reader := nns2.NewReader(inv, nnsHash)
|
||||||
return reader.IsAvailable(name)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckNotaryEnabled(c Client) error {
|
func CheckNotaryEnabled(c Client) error {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/actor"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
"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/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
@ -28,7 +29,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"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/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -134,12 +134,12 @@ func NewInitializeContext(cmd *cobra.Command, v *viper.Viper) (*InitializeContex
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, err := createWalletAccounts(wallets)
|
accounts, err := getSingleAccounts(wallets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cliCtx, err := DefaultClientContext(c, committeeAcc)
|
cliCtx, err := defaultClientContext(c, committeeAcc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("client context: %w", err)
|
return nil, fmt.Errorf("client context: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet)
|
||||||
}
|
}
|
||||||
c, err = NewLocalClient(cmd, v, wallets, ldf.Value.String())
|
c, err = NewLocalClient(cmd, v, wallets, ldf.Value.String())
|
||||||
} else {
|
} else {
|
||||||
c, err = GetN3Client(v)
|
c, err = NewRemoteClient(v)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create N3 client: %w", err)
|
return nil, fmt.Errorf("can't create N3 client: %w", err)
|
||||||
|
@ -211,7 +211,7 @@ func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
|
||||||
return ctrPath, nil
|
return ctrPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) {
|
func getSingleAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) {
|
||||||
accounts := make([]*wallet.Account, len(wallets))
|
accounts := make([]*wallet.Account, len(wallets))
|
||||||
for i, w := range wallets {
|
for i, w := range wallets {
|
||||||
acc, err := GetWalletAccount(w, constants.SingleAccountName)
|
acc, err := GetWalletAccount(w, constants.SingleAccountName)
|
||||||
|
@ -524,12 +524,8 @@ func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.U
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) {
|
||||||
res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone)
|
avail, err := unwrap.Bool(c.CommitteeAct.Call(nnsHash, "isAvailable", zone))
|
||||||
if err != nil {
|
return !avail, err
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.State == vmstate.Halt.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *ContractState) bool {
|
func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *ContractState) bool {
|
||||||
|
|
|
@ -58,35 +58,30 @@ func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
|
go bc.Run()
|
||||||
accounts := make([]*wallet.Account, len(wallets))
|
|
||||||
for i := range accounts {
|
accounts, err := getBlockSigningAccounts(cfg.ProtocolConfiguration, wallets)
|
||||||
accounts[i], err = GetWalletAccount(wallets[i], constants.ConsensusAccountName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
indexMap := make(map[string]int)
|
|
||||||
for i, pub := range cfg.ProtocolConfiguration.StandbyCommittee {
|
|
||||||
indexMap[pub] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(accounts, func(i, j int) bool {
|
|
||||||
pi := accounts[i].PrivateKey().PublicKey().Bytes()
|
|
||||||
pj := accounts[j].PrivateKey().PublicKey().Bytes()
|
|
||||||
return indexMap[string(pi)] < indexMap[string(pj)]
|
|
||||||
})
|
|
||||||
sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool {
|
|
||||||
return accounts[i].PublicKey().Cmp(accounts[j].PublicKey()) == -1
|
|
||||||
})
|
|
||||||
|
|
||||||
go bc.Run()
|
|
||||||
|
|
||||||
if cmd.Name() != "init" {
|
if cmd.Name() != "init" {
|
||||||
|
if err := restoreDump(bc, dumpPath); err != nil {
|
||||||
|
return nil, fmt.Errorf("restore dump: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LocalClient{
|
||||||
|
bc: bc,
|
||||||
|
dumpPath: dumpPath,
|
||||||
|
accounts: accounts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreDump(bc *core.Blockchain, dumpPath string) error {
|
||||||
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0o600)
|
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't open local dump: %w", err)
|
return fmt.Errorf("can't open local dump: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
@ -99,15 +94,37 @@ func NewLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
||||||
|
|
||||||
count := r.ReadU32LE() - skip
|
count := r.ReadU32LE() - skip
|
||||||
if err := chaindump.Restore(bc, r, skip, count, nil); err != nil {
|
if err := chaindump.Restore(bc, r, skip, count, nil); err != nil {
|
||||||
return nil, fmt.Errorf("can't restore local dump: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBlockSigningAccounts(cfg config.ProtocolConfiguration, wallets []*wallet.Wallet) ([]*wallet.Account, error) {
|
||||||
|
accounts := make([]*wallet.Account, len(wallets))
|
||||||
|
for i := range accounts {
|
||||||
|
acc, err := GetWalletAccount(wallets[i], constants.ConsensusAccountName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accounts[i] = acc
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LocalClient{
|
indexMap := make(map[string]int)
|
||||||
bc: bc,
|
for i, pub := range cfg.StandbyCommittee {
|
||||||
dumpPath: dumpPath,
|
indexMap[pub] = i
|
||||||
accounts: accounts[:m],
|
}
|
||||||
}, nil
|
|
||||||
|
sort.Slice(accounts, func(i, j int) bool {
|
||||||
|
pi := accounts[i].PrivateKey().PublicKey().Bytes()
|
||||||
|
pj := accounts[j].PrivateKey().PublicKey().Bytes()
|
||||||
|
return indexMap[string(pi)] < indexMap[string(pj)]
|
||||||
|
})
|
||||||
|
sort.Slice(accounts[:cfg.ValidatorsCount], func(i, j int) bool {
|
||||||
|
return accounts[i].PublicKey().Cmp(accounts[j].PublicKey()) == -1
|
||||||
|
})
|
||||||
|
|
||||||
|
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ValidatorsCount))
|
||||||
|
return accounts[:m], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LocalClient) GetBlockCount() (uint32, error) {
|
func (l *LocalClient) GetBlockCount() (uint32, error) {
|
||||||
|
@ -128,11 +145,6 @@ func (l *LocalClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*resul
|
||||||
return &a, nil
|
return &a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LocalClient) GetCommittee() (keys.PublicKeys, error) {
|
|
||||||
// not used by `morph init` command
|
|
||||||
panic("unexpected call")
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvokeFunction is implemented via `InvokeScript`.
|
// InvokeFunction is implemented via `InvokeScript`.
|
||||||
func (l *LocalClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
func (l *LocalClient) InvokeFunction(h util.Uint160, method string, sPrm []smartcontract.Parameter, ss []transaction.Signer) (*result.Invoke, error) {
|
||||||
var err error
|
var err error
|
||||||
|
@ -296,13 +308,7 @@ func (l *LocalClient) InvokeScript(script []byte, signers []transaction.Signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
func (l *LocalClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
|
||||||
// We need to test that transaction was formed correctly to catch as many errors as we can.
|
tx = tx.Copy()
|
||||||
bs := tx.Bytes()
|
|
||||||
_, err := transaction.NewTransactionFromBytes(bs)
|
|
||||||
if err != nil {
|
|
||||||
return tx.Hash(), fmt.Errorf("invalid transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.transactions = append(l.transactions, tx)
|
l.transactions = append(l.transactions, tx)
|
||||||
return tx.Hash(), nil
|
return tx.Hash(), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
|
@ -25,15 +24,10 @@ import (
|
||||||
// Client represents N3 client interface capable of test-invoking scripts
|
// Client represents N3 client interface capable of test-invoking scripts
|
||||||
// and sending signed transactions to chain.
|
// and sending signed transactions to chain.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
invoker.RPCInvoke
|
actor.RPCActor
|
||||||
|
|
||||||
GetBlockCount() (uint32, error)
|
|
||||||
GetNativeContracts() ([]state.Contract, error)
|
GetNativeContracts() ([]state.Contract, error)
|
||||||
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
|
GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error)
|
||||||
GetVersion() (*result.Version, error)
|
|
||||||
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
|
||||||
GetCommittee() (keys.PublicKeys, error)
|
|
||||||
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HashVUBPair struct {
|
type HashVUBPair struct {
|
||||||
|
@ -48,7 +42,7 @@ type ClientContext struct {
|
||||||
SentTxs []HashVUBPair
|
SentTxs []HashVUBPair
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetN3Client(v *viper.Viper) (Client, error) {
|
func NewRemoteClient(v *viper.Viper) (Client, error) {
|
||||||
// number of opened connections
|
// number of opened connections
|
||||||
// by neo-go client per one host
|
// by neo-go client per one host
|
||||||
const (
|
const (
|
||||||
|
@ -88,8 +82,14 @@ func GetN3Client(v *viper.Viper) (Client, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultClientContext(c Client, committeeAcc *wallet.Account) (*ClientContext, error) {
|
func defaultClientContext(c Client, committeeAcc *wallet.Account) (*ClientContext, error) {
|
||||||
commAct, err := NewActor(c, committeeAcc)
|
commAct, err := actor.New(c, []actor.SignerAccount{{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: committeeAcc.Contract.ScriptHash(),
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
Account: committeeAcc,
|
||||||
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package helper
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"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/constants"
|
||||||
|
@ -118,11 +119,8 @@ func MergeNetmapConfig(roInvoker *invoker.Invoker, md map[string]any) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
for _, key := range NetmapConfigKeys {
|
if slices.Contains(NetmapConfigKeys, k) {
|
||||||
if k == key {
|
|
||||||
md[k] = v
|
md[k] = v
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -14,16 +14,36 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
"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/constants"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
"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/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"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/management"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/spf13/viper"
|
"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) {
|
func GetAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
|
||||||
wallets, err := openAlphabetWallets(v, walletDir)
|
wallets, err := openAlphabetWallets(v, walletDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,7 +73,7 @@ func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, er
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
err = nil
|
err = nil
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("can't open wallet: %w", err)
|
err = fmt.Errorf("can't open alphabet wallet: %w", err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -87,16 +107,6 @@ func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, er
|
||||||
return wallets, nil
|
return wallets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewActor(c actor.RPCActor, committeeAcc *wallet.Account) (*actor.Actor, error) {
|
|
||||||
return actor.New(c, []actor.SignerAccount{{
|
|
||||||
Signer: transaction.Signer{
|
|
||||||
Account: committeeAcc.Contract.ScriptHash(),
|
|
||||||
Scopes: transaction.Global,
|
|
||||||
},
|
|
||||||
Account: committeeAcc,
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadContract(ctrPath, ctrName string) (*ContractState, error) {
|
func ReadContract(ctrPath, ctrName string) (*ContractState, error) {
|
||||||
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
|
rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
@ -11,11 +10,8 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"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/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/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/rpcclient/unwrap"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -30,7 +26,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerCandidateRange(c *helper.InitializeContext, start, end int) error {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -116,7 +113,7 @@ func registerCandidates(c *helper.InitializeContext) error {
|
||||||
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
||||||
neoHash := neo.Hash
|
neoHash := neo.Hash
|
||||||
|
|
||||||
ok, err := transferNEOFinished(c, neoHash)
|
ok, err := transferNEOFinished(c)
|
||||||
if ok || err != nil {
|
if ok || err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -139,33 +136,8 @@ func transferNEOToAlphabetContracts(c *helper.InitializeContext) error {
|
||||||
return c.AwaitTx()
|
return c.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferNEOFinished(c *helper.InitializeContext, neoHash util.Uint160) (bool, error) {
|
func transferNEOFinished(c *helper.InitializeContext) (bool, error) {
|
||||||
r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
|
r := neo.NewReader(c.ReadOnlyInvoker)
|
||||||
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
|
||||||
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err
|
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 (
|
const (
|
||||||
gasInitialTotalSupply = 30000000 * native.GASFactor
|
|
||||||
// initialAlphabetGASAmount represents the amount of GAS given to each alphabet node.
|
// initialAlphabetGASAmount represents the amount of GAS given to each alphabet node.
|
||||||
initialAlphabetGASAmount = 10_000 * native.GASFactor
|
initialAlphabetGASAmount = 10_000 * native.GASFactor
|
||||||
// initialProxyGASAmount represents the amount of GAS given to a proxy contract.
|
// initialProxyGASAmount represents the amount of GAS given to a proxy contract.
|
||||||
initialProxyGASAmount = 50_000 * native.GASFactor
|
initialProxyGASAmount = 50_000 * native.GASFactor
|
||||||
)
|
)
|
||||||
|
|
||||||
func initialCommitteeGASAmount(c *helper.InitializeContext) int64 {
|
func initialCommitteeGASAmount(c *helper.InitializeContext, initialGasDistribution int64) int64 {
|
||||||
return (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2
|
return (initialGasDistribution - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferFunds(c *helper.InitializeContext) error {
|
func transferFunds(c *helper.InitializeContext) error {
|
||||||
|
@ -42,6 +41,11 @@ func transferFunds(c *helper.InitializeContext) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version, err := c.Client.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var transfers []transferTarget
|
var transfers []transferTarget
|
||||||
for _, acc := range c.Accounts {
|
for _, acc := range c.Accounts {
|
||||||
to := acc.Contract.ScriptHash()
|
to := acc.Contract.ScriptHash()
|
||||||
|
@ -59,7 +63,7 @@ func transferFunds(c *helper.InitializeContext) error {
|
||||||
transferTarget{
|
transferTarget{
|
||||||
Token: gas.Hash,
|
Token: gas.Hash,
|
||||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
Amount: initialCommitteeGASAmount(c),
|
Amount: initialCommitteeGASAmount(c, int64(version.Protocol.InitialGasDistribution)),
|
||||||
},
|
},
|
||||||
transferTarget{
|
transferTarget{
|
||||||
Token: neo.Hash,
|
Token: neo.Hash,
|
||||||
|
@ -83,16 +87,23 @@ func transferFunds(c *helper.InitializeContext) error {
|
||||||
// transferFundsFinished checks balances of accounts we transfer GAS to.
|
// 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.
|
// 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) {
|
func transferFundsFinished(c *helper.InitializeContext) (bool, error) {
|
||||||
acc := c.Accounts[0]
|
|
||||||
|
|
||||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||||
res, err := r.BalanceOf(acc.Contract.ScriptHash())
|
res, err := r.BalanceOf(c.ConsensusAcc.ScriptHash())
|
||||||
if err != nil || res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) != 1 {
|
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
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err = r.BalanceOf(c.CommitteeAcc.ScriptHash())
|
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 {
|
func transferGASToProxy(c *helper.InitializeContext) error {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
|
|
@ -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/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/constants"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
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/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initRegisterCmd() {
|
func initRegisterCmd() {
|
||||||
|
@ -19,6 +21,7 @@ func initRegisterCmd() {
|
||||||
registerCmd.Flags().Int64(nnsRetryFlag, constants.NNSRetryDefVal, "SOA record RETRY parameter")
|
registerCmd.Flags().Int64(nnsRetryFlag, constants.NNSRetryDefVal, "SOA record RETRY parameter")
|
||||||
registerCmd.Flags().Int64(nnsExpireFlag, int64(constants.DefaultExpirationTime), "SOA record EXPIRE parameter")
|
registerCmd.Flags().Int64(nnsExpireFlag, int64(constants.DefaultExpirationTime), "SOA record EXPIRE parameter")
|
||||||
registerCmd.Flags().Int64(nnsTTLFlag, constants.NNSTtlDefVal, "SOA record TTL 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)
|
_ = cobra.MarkFlagRequired(registerCmd.Flags(), nnsNameFlag)
|
||||||
}
|
}
|
||||||
|
@ -48,6 +51,7 @@ func initDeleteCmd() {
|
||||||
deleteCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
deleteCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
deleteCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
deleteCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
deleteCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
deleteCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||||
|
deleteCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(deleteCmd.Flags(), nnsNameFlag)
|
_ = cobra.MarkFlagRequired(deleteCmd.Flags(), nnsNameFlag)
|
||||||
}
|
}
|
||||||
|
@ -62,3 +66,28 @@ func deleteDomain(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "delete domain error: %w", err)
|
commonCmd.ExitOnErr(cmd, "delete domain error: %w", err)
|
||||||
cmd.Println("Domain deleted successfully")
|
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
|
package nns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
|
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/constants"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
@ -13,10 +17,35 @@ import (
|
||||||
|
|
||||||
func nnsWriter(cmd *cobra.Command) (*client.Contract, *helper.LocalActor) {
|
func nnsWriter(cmd *cobra.Command) (*client.Contract, *helper.LocalActor) {
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
c, err := helper.GetN3Client(v)
|
c, err := helper.NewRemoteClient(v)
|
||||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
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)
|
commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)
|
||||||
|
|
||||||
r := management.NewReader(ac.Invoker)
|
r := management.NewReader(ac.Invoker)
|
||||||
|
@ -26,7 +55,7 @@ func nnsWriter(cmd *cobra.Command) (*client.Contract, *helper.LocalActor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func nnsReader(cmd *cobra.Command) (*client.ContractReader, *invoker.Invoker) {
|
func nnsReader(cmd *cobra.Command) (*client.ContractReader, *invoker.Invoker) {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
|
|
@ -19,6 +19,7 @@ func initAddRecordCmd() {
|
||||||
addRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
addRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||||
addRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
addRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||||
addRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
addRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
||||||
|
addRecordCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsNameFlag)
|
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsNameFlag)
|
||||||
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsRecordTypeFlag)
|
_ = cobra.MarkFlagRequired(addRecordCmd.Flags(), nnsRecordTypeFlag)
|
||||||
|
@ -40,6 +41,7 @@ func initDelRecordsCmd() {
|
||||||
delRecordsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
delRecordsCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
delRecordsCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
delRecordsCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||||
delRecordsCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
delRecordsCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||||
|
delRecordsCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsNameFlag)
|
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsNameFlag)
|
||||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsRecordTypeFlag)
|
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsRecordTypeFlag)
|
||||||
|
@ -52,6 +54,7 @@ func initDelRecordCmd() {
|
||||||
delRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
delRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||||
delRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
delRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||||
delRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
delRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
||||||
|
delRecordCmd.Flags().StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, "", commonflags.WalletPathUsage)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsNameFlag)
|
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsNameFlag)
|
||||||
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordTypeFlag)
|
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordTypeFlag)
|
||||||
|
|
|
@ -39,6 +39,7 @@ var (
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||||
},
|
},
|
||||||
Run: registerDomain,
|
Run: registerDomain,
|
||||||
}
|
}
|
||||||
|
@ -48,6 +49,7 @@ var (
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||||
},
|
},
|
||||||
Run: deleteDomain,
|
Run: deleteDomain,
|
||||||
}
|
}
|
||||||
|
@ -75,6 +77,7 @@ var (
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||||
},
|
},
|
||||||
Run: addRecord,
|
Run: addRecord,
|
||||||
}
|
}
|
||||||
|
@ -92,6 +95,7 @@ var (
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||||
},
|
},
|
||||||
Run: delRecords,
|
Run: delRecords,
|
||||||
}
|
}
|
||||||
|
@ -101,9 +105,21 @@ var (
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||||
},
|
},
|
||||||
Run: delRecord,
|
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() {
|
func init() {
|
||||||
|
@ -116,4 +132,5 @@ func init() {
|
||||||
initGetRecordsCmd()
|
initGetRecordsCmd()
|
||||||
initDelRecordsCmd()
|
initDelRecordsCmd()
|
||||||
initDelRecordCmd()
|
initDelRecordCmd()
|
||||||
|
initSetAdminCmd()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
"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()
|
accHash := w.GetChangeAddress()
|
||||||
if addr, err := cmd.Flags().GetString(walletAccountFlag); err == nil {
|
addr, _ := cmd.Flags().GetString(walletAccountFlag)
|
||||||
|
if addr != "" {
|
||||||
accHash, err = address.StringToUint160(addr)
|
accHash, err = address.StringToUint160(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid address: %s", addr)
|
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)
|
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)
|
pass, err := input.ReadPassword(prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get password: %v", err)
|
return fmt.Errorf("can't get password: %v", err)
|
||||||
|
@ -73,23 +73,16 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
till := int64(defaultNotaryDepositLifetime)
|
till, _ := cmd.Flags().GetInt64(notaryDepositTillFlag)
|
||||||
tillStr, err := cmd.Flags().GetString(notaryDepositTillFlag)
|
if till <= 0 {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tillStr != "" {
|
|
||||||
till, err = strconv.ParseInt(tillStr, 10, 64)
|
|
||||||
if err != nil || till <= 0 {
|
|
||||||
return errInvalidNotaryDepositLifetime
|
return errInvalidNotaryDepositLifetime
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return transferGas(cmd, acc, accHash, gasAmount, till)
|
return transferGas(cmd, acc, accHash, gasAmount, till)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
|
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ func initDepositoryNotaryCmd() {
|
||||||
DepositCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
DepositCmd.Flags().String(commonflags.StorageWalletFlag, "", "Path to storage node wallet")
|
||||||
DepositCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
|
DepositCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
|
||||||
DepositCmd.Flags().String(commonflags.RefillGasAmountFlag, "", "Amount of GAS to deposit")
|
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() {
|
func init() {
|
||||||
|
|
|
@ -62,7 +62,7 @@ func SetPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
||||||
c, err := helper.GetN3Client(viper.GetViper())
|
c, err := helper.NewRemoteClient(viper.GetViper())
|
||||||
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
||||||
|
|
||||||
inv := invoker.New(c, nil)
|
inv := invoker.New(c, nil)
|
||||||
|
@ -80,9 +80,9 @@ func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||||
|
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("Execution Fee Factor:\t%d (int)\n", execFee)))
|
_, _ = tw.Write(fmt.Appendf(nil, "Execution Fee Factor:\t%d (int)\n", execFee))
|
||||||
_, _ = tw.Write([]byte(fmt.Sprintf("Fee Per Byte:\t%d (int)\n", feePerByte)))
|
_, _ = tw.Write(fmt.Appendf(nil, "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, "Storage Price:\t%d (int)\n", storagePrice))
|
||||||
|
|
||||||
_ = tw.Flush()
|
_ = tw.Flush()
|
||||||
cmd.Print(buf.String())
|
cmd.Print(buf.String())
|
||||||
|
|
|
@ -20,23 +20,32 @@ const (
|
||||||
accountAddressFlag = "account"
|
accountAddressFlag = "account"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addProxyAccount(cmd *cobra.Command, _ []string) {
|
func parseAddresses(cmd *cobra.Command) []util.Uint160 {
|
||||||
acc, _ := cmd.Flags().GetString(accountAddressFlag)
|
var addrs []util.Uint160
|
||||||
|
|
||||||
|
accs, _ := cmd.Flags().GetStringArray(accountAddressFlag)
|
||||||
|
for _, acc := range accs {
|
||||||
addr, err := address.StringToUint160(acc)
|
addr, err := address.StringToUint160(acc)
|
||||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
||||||
err = processAccount(cmd, addr, "addAccount")
|
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func addProxyAccount(cmd *cobra.Command, _ []string) {
|
||||||
|
addrs := parseAddresses(cmd)
|
||||||
|
err := processAccount(cmd, addrs, "addAccount")
|
||||||
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeProxyAccount(cmd *cobra.Command, _ []string) {
|
func removeProxyAccount(cmd *cobra.Command, _ []string) {
|
||||||
acc, _ := cmd.Flags().GetString(accountAddressFlag)
|
addrs := parseAddresses(cmd)
|
||||||
addr, err := address.StringToUint160(acc)
|
err := processAccount(cmd, addrs, "removeAccount")
|
||||||
commonCmd.ExitOnErr(cmd, "invalid account: %w", err)
|
|
||||||
err = processAccount(cmd, addr, "removeAccount")
|
|
||||||
commonCmd.ExitOnErr(cmd, "processing error: %w", err)
|
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())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't initialize context: %w", err)
|
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()
|
bw := io.NewBufBinWriter()
|
||||||
|
for _, addr := range addrs {
|
||||||
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
emit.AppCall(bw.BinWriter, proxyHash, method, callflag.All, addr)
|
||||||
|
}
|
||||||
|
|
||||||
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
if err := wCtx.SendConsensusTx(bw.Bytes()); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -29,13 +29,15 @@ var (
|
||||||
|
|
||||||
func initProxyAddAccount() {
|
func initProxyAddAccount() {
|
||||||
AddAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
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)
|
AddAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initProxyRemoveAccount() {
|
func initProxyRemoveAccount() {
|
||||||
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
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)
|
RemoveAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/metabase"
|
"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/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/misc"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||||
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||||
|
@ -41,7 +40,6 @@ func init() {
|
||||||
|
|
||||||
rootCmd.AddCommand(config.RootCmd)
|
rootCmd.AddCommand(config.RootCmd)
|
||||||
rootCmd.AddCommand(morph.RootCmd)
|
rootCmd.AddCommand(morph.RootCmd)
|
||||||
rootCmd.AddCommand(storagecfg.RootCmd)
|
|
||||||
rootCmd.AddCommand(metabase.RootCmd)
|
rootCmd.AddCommand(metabase.RootCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
||||||
|
|
|
@ -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,8 +9,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||||
|
@ -78,13 +76,29 @@ func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContain
|
||||||
// SortedIDList returns sorted list of identifiers of user's containers.
|
// SortedIDList returns sorted list of identifiers of user's containers.
|
||||||
func (x ListContainersRes) SortedIDList() []cid.ID {
|
func (x ListContainersRes) SortedIDList() []cid.ID {
|
||||||
list := x.cliRes.Containers()
|
list := x.cliRes.Containers()
|
||||||
sort.Slice(list, func(i, j int) bool {
|
slices.SortFunc(list, cid.ID.Cmp)
|
||||||
lhs, rhs := list[i].EncodeToString(), list[j].EncodeToString()
|
|
||||||
return strings.Compare(lhs, rhs) < 0
|
|
||||||
})
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListContainersStream(ctx context.Context, prm ListContainersPrm, processCnr func(id cid.ID) bool) (err error) {
|
||||||
|
cliPrm := &client.PrmContainerListStream{
|
||||||
|
XHeaders: prm.XHeaders,
|
||||||
|
OwnerID: prm.OwnerID,
|
||||||
|
Session: prm.Session,
|
||||||
|
}
|
||||||
|
rdr, err := prm.cli.ContainerListInit(ctx, *cliPrm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("init container list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rdr.Iterate(processCnr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read container list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// PutContainerPrm groups parameters of PutContainer operation.
|
// PutContainerPrm groups parameters of PutContainer operation.
|
||||||
type PutContainerPrm struct {
|
type PutContainerPrm struct {
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
|
@ -670,9 +684,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes
|
||||||
return nil, fmt.Errorf("read object list: %w", err)
|
return nil, fmt.Errorf("read object list: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.SortFunc(list, func(a, b oid.ID) int {
|
slices.SortFunc(list, oid.ID.Cmp)
|
||||||
return strings.Compare(a.EncodeToString(), b.EncodeToString())
|
|
||||||
})
|
|
||||||
|
|
||||||
return &SearchObjectsRes{
|
return &SearchObjectsRes{
|
||||||
ids: list,
|
ids: list,
|
||||||
|
@ -846,6 +858,8 @@ type PatchObjectPrm struct {
|
||||||
|
|
||||||
ReplaceAttribute bool
|
ReplaceAttribute bool
|
||||||
|
|
||||||
|
NewSplitHeader *objectSDK.SplitHeader
|
||||||
|
|
||||||
PayloadPatches []PayloadPatch
|
PayloadPatches []PayloadPatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -876,7 +890,11 @@ func Patch(ctx context.Context, prm PatchObjectPrm) (*PatchRes, error) {
|
||||||
return nil, fmt.Errorf("init payload reading: %w", err)
|
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 {
|
for _, pp := range prm.PayloadPatches {
|
||||||
payloadFile, err := os.OpenFile(pp.PayloadPath, os.O_RDONLY, os.ModePerm)
|
payloadFile, err := os.OpenFile(pp.PayloadPath, os.O_RDONLY, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
|
||||||
prmDial := client.PrmDial{
|
prmDial := client.PrmDial{
|
||||||
Endpoint: addr.URIAddr(),
|
Endpoint: addr.URIAddr(),
|
||||||
GRPCDialOptions: []grpc.DialOption{
|
GRPCDialOptions: []grpc.DialOption{
|
||||||
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInterceptor()),
|
||||||
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
||||||
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,7 @@ const (
|
||||||
RPC = "rpc-endpoint"
|
RPC = "rpc-endpoint"
|
||||||
RPCShorthand = "r"
|
RPCShorthand = "r"
|
||||||
RPCDefault = ""
|
RPCDefault = ""
|
||||||
RPCUsage = "Remote node address (as 'multiaddr' or '<host>:<port>')"
|
RPCUsage = "Remote node address ('<host>:<port>' or 'grpcs://<host>:<port>')"
|
||||||
|
|
||||||
Timeout = "timeout"
|
Timeout = "timeout"
|
||||||
TimeoutShorthand = "t"
|
TimeoutShorthand = "t"
|
||||||
|
|
|
@ -52,7 +52,7 @@ func genereateAPEOverride(cmd *cobra.Command, _ []string) {
|
||||||
|
|
||||||
outputPath, _ := cmd.Flags().GetString(outputFlag)
|
outputPath, _ := cmd.Flags().GetString(outputFlag)
|
||||||
if outputPath != "" {
|
if outputPath != "" {
|
||||||
err := os.WriteFile(outputPath, []byte(overrideMarshalled), 0o644)
|
err := os.WriteFile(outputPath, overrideMarshalled, 0o644)
|
||||||
commonCmd.ExitOnErr(cmd, "dump error: %w", err)
|
commonCmd.ExitOnErr(cmd, "dump error: %w", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Print("\n")
|
fmt.Print("\n")
|
||||||
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// flags of list command.
|
// flags of list command.
|
||||||
|
@ -51,42 +54,58 @@ var listContainersCmd = &cobra.Command{
|
||||||
|
|
||||||
var prm internalclient.ListContainersPrm
|
var prm internalclient.ListContainersPrm
|
||||||
prm.SetClient(cli)
|
prm.SetClient(cli)
|
||||||
prm.Account = idUser
|
prm.OwnerID = idUser
|
||||||
|
|
||||||
res, err := internalclient.ListContainers(cmd.Context(), prm)
|
|
||||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
|
||||||
|
|
||||||
prmGet := internalclient.GetContainerPrm{
|
prmGet := internalclient.GetContainerPrm{
|
||||||
Client: cli,
|
Client: cli,
|
||||||
}
|
}
|
||||||
|
var containerIDs []cid.ID
|
||||||
|
|
||||||
containerIDs := res.SortedIDList()
|
err := internalclient.ListContainersStream(cmd.Context(), prm, func(id cid.ID) bool {
|
||||||
for _, cnrID := range containerIDs {
|
printContainer(cmd, prmGet, id)
|
||||||
if flagVarListName == "" && !flagVarListPrintAttr {
|
return false
|
||||||
cmd.Println(cnrID.String())
|
})
|
||||||
continue
|
if err == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
prmGet.ClientParams.ContainerID = &cnrID
|
if e, ok := status.FromError(err); ok && e.Code() == codes.Unimplemented {
|
||||||
|
res, err := internalclient.ListContainers(cmd.Context(), prm)
|
||||||
|
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||||
|
containerIDs = res.SortedIDList()
|
||||||
|
} else {
|
||||||
|
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cnrID := range containerIDs {
|
||||||
|
printContainer(cmd, prmGet, cnrID)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func printContainer(cmd *cobra.Command, prmGet internalclient.GetContainerPrm, id cid.ID) {
|
||||||
|
if flagVarListName == "" && !flagVarListPrintAttr {
|
||||||
|
cmd.Println(id.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prmGet.ClientParams.ContainerID = &id
|
||||||
res, err := internalclient.GetContainer(cmd.Context(), prmGet)
|
res, err := internalclient.GetContainer(cmd.Context(), prmGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Printf(" failed to read attributes: %v\n", err)
|
cmd.Printf(" failed to read attributes: %v\n", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cnr := res.Container()
|
cnr := res.Container()
|
||||||
if cnrName := containerSDK.Name(cnr); flagVarListName != "" && cnrName != flagVarListName {
|
if cnrName := containerSDK.Name(cnr); flagVarListName != "" && cnrName != flagVarListName {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
cmd.Println(cnrID.String())
|
cmd.Println(id.String())
|
||||||
|
|
||||||
if flagVarListPrintAttr {
|
if flagVarListPrintAttr {
|
||||||
cnr.IterateUserAttributes(func(key, val string) {
|
cnr.IterateUserAttributes(func(key, val string) {
|
||||||
cmd.Printf(" %s: %s\n", key, val)
|
cmd.Printf(" %s: %s\n", key, val)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initContainerListContainersCmd() {
|
func initContainerListContainersCmd() {
|
||||||
|
|
|
@ -23,11 +23,11 @@ type policyPlaygroundREPL struct {
|
||||||
nodes map[string]netmap.NodeInfo
|
nodes map[string]netmap.NodeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPolicyPlaygroundREPL(cmd *cobra.Command) (*policyPlaygroundREPL, error) {
|
func newPolicyPlaygroundREPL(cmd *cobra.Command) *policyPlaygroundREPL {
|
||||||
return &policyPlaygroundREPL{
|
return &policyPlaygroundREPL{
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
nodes: map[string]netmap.NodeInfo{},
|
nodes: map[string]netmap.NodeInfo{},
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repl *policyPlaygroundREPL) handleLs(args []string) error {
|
func (repl *policyPlaygroundREPL) handleLs(args []string) error {
|
||||||
|
@ -246,8 +246,7 @@ var policyPlaygroundCmd = &cobra.Command{
|
||||||
Long: `A REPL for testing placement policies.
|
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.`,
|
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) {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
repl, err := newPolicyPlaygroundREPL(cmd)
|
repl := newPolicyPlaygroundREPL(cmd)
|
||||||
commonCmd.ExitOnErr(cmd, "could not create policy playground: %w", err)
|
|
||||||
commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run())
|
commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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-node/pkg/services/control"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ignoreErrorsFlag = "no-errors"
|
|
||||||
|
|
||||||
var evacuateShardCmd = &cobra.Command{
|
|
||||||
Use: "evacuate",
|
|
||||||
Short: "Evacuate objects from shard",
|
|
||||||
Long: "Evacuate objects from shard to other shards",
|
|
||||||
Run: evacuateShard,
|
|
||||||
Deprecated: "use frostfs-cli control shards evacuation start",
|
|
||||||
}
|
|
||||||
|
|
||||||
func evacuateShard(cmd *cobra.Command, _ []string) {
|
|
||||||
pk := key.Get(cmd)
|
|
||||||
|
|
||||||
req := &control.EvacuateShardRequest{Body: new(control.EvacuateShardRequest_Body)}
|
|
||||||
req.Body.Shard_ID = getShardIDList(cmd)
|
|
||||||
req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(ignoreErrorsFlag)
|
|
||||||
|
|
||||||
signRequest(cmd, pk, req)
|
|
||||||
|
|
||||||
cli := getClient(cmd, pk)
|
|
||||||
|
|
||||||
var resp *control.EvacuateShardResponse
|
|
||||||
var err error
|
|
||||||
err = cli.ExecRaw(func(client *client.Client) error {
|
|
||||||
resp, err = control.EvacuateShard(client, req)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
|
||||||
|
|
||||||
cmd.Printf("Objects moved: %d\n", resp.GetBody().GetCount())
|
|
||||||
|
|
||||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
|
||||||
|
|
||||||
cmd.Println("Shard has successfully been evacuated.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func initControlEvacuateShardCmd() {
|
|
||||||
initControlFlags(evacuateShardCmd)
|
|
||||||
|
|
||||||
flags := evacuateShardCmd.Flags()
|
|
||||||
flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding")
|
|
||||||
flags.Bool(shardAllFlag, false, "Process all shards")
|
|
||||||
flags.Bool(ignoreErrorsFlag, false, "Skip invalid/unreadable objects")
|
|
||||||
|
|
||||||
evacuateShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
|
||||||
}
|
|
|
@ -21,6 +21,7 @@ const (
|
||||||
noProgressFlag = "no-progress"
|
noProgressFlag = "no-progress"
|
||||||
scopeFlag = "scope"
|
scopeFlag = "scope"
|
||||||
repOneOnlyFlag = "rep-one-only"
|
repOneOnlyFlag = "rep-one-only"
|
||||||
|
ignoreErrorsFlag = "no-errors"
|
||||||
|
|
||||||
containerWorkerCountFlag = "container-worker-count"
|
containerWorkerCountFlag = "container-worker-count"
|
||||||
objectWorkerCountFlag = "object-worker-count"
|
objectWorkerCountFlag = "object-worker-count"
|
||||||
|
|
|
@ -62,7 +62,7 @@ func listTargets(cmd *cobra.Command, _ []string) {
|
||||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||||
_, _ = tw.Write([]byte("#\tName\tType\n"))
|
_, _ = tw.Write([]byte("#\tName\tType\n"))
|
||||||
for i, t := range targets {
|
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()
|
_ = tw.Flush()
|
||||||
cmd.Print(buf.String())
|
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,
|
listRulesCmd,
|
||||||
getRuleCmd,
|
getRuleCmd,
|
||||||
listTargetsCmd,
|
listTargetsCmd,
|
||||||
|
locateObjectCmd,
|
||||||
)
|
)
|
||||||
|
|
||||||
initControlHealthCheckCmd()
|
initControlHealthCheckCmd()
|
||||||
|
@ -52,4 +53,5 @@ func init() {
|
||||||
initControlListRulesCmd()
|
initControlListRulesCmd()
|
||||||
initControGetRuleCmd()
|
initControGetRuleCmd()
|
||||||
initControlListTargetsCmd()
|
initControlListTargetsCmd()
|
||||||
|
initControlLocateObjectCmd()
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ func awaitSetNetmapStatus(cmd *cobra.Command, pk *ecdsa.PrivateKey, cli *client.
|
||||||
var resp *control.GetNetmapStatusResponse
|
var resp *control.GetNetmapStatusResponse
|
||||||
var err error
|
var err error
|
||||||
err = cli.ExecRaw(func(client *rawclient.Client) 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
|
return err
|
||||||
})
|
})
|
||||||
commonCmd.ExitOnErr(cmd, "failed to get current netmap status: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to get current netmap status: %w", err)
|
||||||
|
|
|
@ -13,7 +13,6 @@ var shardsCmd = &cobra.Command{
|
||||||
func initControlShardsCmd() {
|
func initControlShardsCmd() {
|
||||||
shardsCmd.AddCommand(listShardsCmd)
|
shardsCmd.AddCommand(listShardsCmd)
|
||||||
shardsCmd.AddCommand(setShardModeCmd)
|
shardsCmd.AddCommand(setShardModeCmd)
|
||||||
shardsCmd.AddCommand(evacuateShardCmd)
|
|
||||||
shardsCmd.AddCommand(evacuationShardCmd)
|
shardsCmd.AddCommand(evacuationShardCmd)
|
||||||
shardsCmd.AddCommand(flushCacheCmd)
|
shardsCmd.AddCommand(flushCacheCmd)
|
||||||
shardsCmd.AddCommand(doctorCmd)
|
shardsCmd.AddCommand(doctorCmd)
|
||||||
|
@ -23,7 +22,6 @@ func initControlShardsCmd() {
|
||||||
|
|
||||||
initControlShardsListCmd()
|
initControlShardsListCmd()
|
||||||
initControlSetShardModeCmd()
|
initControlSetShardModeCmd()
|
||||||
initControlEvacuateShardCmd()
|
|
||||||
initControlEvacuationShardCmd()
|
initControlEvacuationShardCmd()
|
||||||
initControlFlushCacheCmd()
|
initControlFlushCacheCmd()
|
||||||
initControlDoctorCmd()
|
initControlDoctorCmd()
|
||||||
|
|
|
@ -24,7 +24,7 @@ var writecacheShardCmd = &cobra.Command{
|
||||||
var sealWritecacheShardCmd = &cobra.Command{
|
var sealWritecacheShardCmd = &cobra.Command{
|
||||||
Use: "seal",
|
Use: "seal",
|
||||||
Short: "Flush objects from write-cache and move write-cache to degraded read only mode.",
|
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,
|
Run: sealWritecache,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ func deleteObject(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.OIDFlag))
|
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)
|
pk := key.GetOrGenerate(cmd)
|
||||||
|
|
|
@ -46,7 +46,7 @@ func getObject(cmd *cobra.Command, _ []string) {
|
||||||
var cnr cid.ID
|
var cnr cid.ID
|
||||||
var obj oid.ID
|
var obj oid.ID
|
||||||
|
|
||||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||||
|
|
||||||
filename := cmd.Flag(fileFlag).Value.String()
|
filename := cmd.Flag(fileFlag).Value.String()
|
||||||
out, closer := createOutWriter(cmd, filename)
|
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/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
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"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -42,7 +41,9 @@ func initObjectHashCmd() {
|
||||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||||
_ = objectHashCmd.MarkFlagRequired(commonflags.OIDFlag)
|
_ = 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("type", hashSha256, "Hash type. Either 'sha256' or 'tz'")
|
||||||
flags.String(getRangeHashSaltFlag, "", "Salt in hex format")
|
flags.String(getRangeHashSaltFlag, "", "Salt in hex format")
|
||||||
}
|
}
|
||||||
|
@ -51,7 +52,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
||||||
var cnr cid.ID
|
var cnr cid.ID
|
||||||
var obj oid.ID
|
var obj oid.ID
|
||||||
|
|
||||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||||
|
|
||||||
ranges, err := getRangeList(cmd)
|
ranges, err := getRangeList(cmd)
|
||||||
commonCmd.ExitOnErr(cmd, "", err)
|
commonCmd.ExitOnErr(cmd, "", err)
|
||||||
|
@ -66,36 +67,6 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
||||||
pk := key.GetOrGenerate(cmd)
|
pk := key.GetOrGenerate(cmd)
|
||||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
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
|
var hashPrm internalclient.HashPayloadRangesPrm
|
||||||
hashPrm.SetClient(cli)
|
hashPrm.SetClient(cli)
|
||||||
Prepare(cmd, &hashPrm)
|
Prepare(cmd, &hashPrm)
|
||||||
|
@ -104,7 +75,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
||||||
hashPrm.SetSalt(salt)
|
hashPrm.SetSalt(salt)
|
||||||
hashPrm.SetRanges(ranges)
|
hashPrm.SetRanges(ranges)
|
||||||
|
|
||||||
if tz {
|
if typ == hashTz {
|
||||||
hashPrm.TZ()
|
hashPrm.TZ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
||||||
var cnr cid.ID
|
var cnr cid.ID
|
||||||
var obj oid.ID
|
var obj oid.ID
|
||||||
|
|
||||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||||
pk := key.GetOrGenerate(cmd)
|
pk := key.GetOrGenerate(cmd)
|
||||||
|
|
||||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"cmp"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||||
|
@ -104,7 +101,7 @@ func initObjectNodesCmd() {
|
||||||
func objectNodes(cmd *cobra.Command, _ []string) {
|
func objectNodes(cmd *cobra.Command, _ []string) {
|
||||||
var cnrID cid.ID
|
var cnrID cid.ID
|
||||||
var objID oid.ID
|
var objID oid.ID
|
||||||
readObjectAddress(cmd, &cnrID, &objID)
|
ReadObjectAddress(cmd, &cnrID, &objID)
|
||||||
|
|
||||||
pk := key.GetOrGenerate(cmd)
|
pk := key.GetOrGenerate(cmd)
|
||||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||||
|
@ -323,7 +320,7 @@ func getReplicaRequiredPlacement(cmd *cobra.Command, objects []phyObject, placem
|
||||||
}
|
}
|
||||||
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
||||||
for _, object := range objects {
|
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)
|
commonCmd.ExitOnErr(cmd, "failed to get required placement for object: %w", err)
|
||||||
for repIdx, rep := range placement {
|
for repIdx, rep := range placement {
|
||||||
numOfReplicas := placementPolicy.ReplicaDescriptor(repIdx).NumberOfObjects()
|
numOfReplicas := placementPolicy.ReplicaDescriptor(repIdx).NumberOfObjects()
|
||||||
|
@ -361,7 +358,7 @@ func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placem
|
||||||
placementObjectID = object.ecHeader.parent
|
placementObjectID = object.ecHeader.parent
|
||||||
}
|
}
|
||||||
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
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)
|
commonCmd.ExitOnErr(cmd, "failed to get required placement: %w", err)
|
||||||
|
|
||||||
for _, vector := range placement {
|
for _, vector := range placement {
|
||||||
|
@ -507,7 +504,6 @@ func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
func printPlacement(cmd *cobra.Command, objID oid.ID, objects []phyObject, result *objectNodesResult) {
|
func printPlacement(cmd *cobra.Command, objID oid.ID, objects []phyObject, result *objectNodesResult) {
|
||||||
normilizeObjectNodesResult(objects, result)
|
|
||||||
if json, _ := cmd.Flags().GetBool(commonflags.JSON); json {
|
if json, _ := cmd.Flags().GetBool(commonflags.JSON); json {
|
||||||
printObjectNodesAsJSON(cmd, objID, objects, result)
|
printObjectNodesAsJSON(cmd, objID, objects, result)
|
||||||
} else {
|
} else {
|
||||||
|
@ -515,34 +511,6 @@ func printPlacement(cmd *cobra.Command, objID oid.ID, objects []phyObject, resul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normilizeObjectNodesResult(objects []phyObject, result *objectNodesResult) {
|
|
||||||
slices.SortFunc(objects, func(lhs, rhs phyObject) int {
|
|
||||||
if lhs.ecHeader == nil && rhs.ecHeader == nil {
|
|
||||||
return bytes.Compare(lhs.objectID[:], rhs.objectID[:])
|
|
||||||
}
|
|
||||||
if lhs.ecHeader == nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if rhs.ecHeader == nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if lhs.ecHeader.parent == rhs.ecHeader.parent {
|
|
||||||
return cmp.Compare(lhs.ecHeader.index, rhs.ecHeader.index)
|
|
||||||
}
|
|
||||||
return bytes.Compare(lhs.ecHeader.parent[:], rhs.ecHeader.parent[:])
|
|
||||||
})
|
|
||||||
for _, obj := range objects {
|
|
||||||
op := result.placements[obj.objectID]
|
|
||||||
slices.SortFunc(op.confirmedNodes, func(lhs, rhs netmapSDK.NodeInfo) int {
|
|
||||||
return bytes.Compare(lhs.PublicKey(), rhs.PublicKey())
|
|
||||||
})
|
|
||||||
slices.SortFunc(op.requiredNodes, func(lhs, rhs netmapSDK.NodeInfo) int {
|
|
||||||
return bytes.Compare(lhs.PublicKey(), rhs.PublicKey())
|
|
||||||
})
|
|
||||||
result.placements[obj.objectID] = op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printObjectNodesAsText(cmd *cobra.Command, objID oid.ID, objects []phyObject, result *objectNodesResult) {
|
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(), len(objects))
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
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"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -20,6 +22,7 @@ const (
|
||||||
replaceAttrsFlagName = "replace-attrs"
|
replaceAttrsFlagName = "replace-attrs"
|
||||||
rangeFlagName = "range"
|
rangeFlagName = "range"
|
||||||
payloadFlagName = "payload"
|
payloadFlagName = "payload"
|
||||||
|
splitHeaderFlagName = "split-header"
|
||||||
)
|
)
|
||||||
|
|
||||||
var objectPatchCmd = &cobra.Command{
|
var objectPatchCmd = &cobra.Command{
|
||||||
|
@ -46,17 +49,18 @@ func initObjectPatchCmd() {
|
||||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||||
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
_ = 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.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(rangeFlagName, []string{}, "Range to which patch payload is applied. Format: offset:length")
|
||||||
flags.StringSlice(payloadFlagName, []string{}, "Path to file with patch payload.")
|
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) {
|
func patch(cmd *cobra.Command, _ []string) {
|
||||||
var cnr cid.ID
|
var cnr cid.ID
|
||||||
var obj oid.ID
|
var obj oid.ID
|
||||||
|
|
||||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||||
|
|
||||||
ranges, err := getRangeSlice(cmd)
|
ranges, err := getRangeSlice(cmd)
|
||||||
commonCmd.ExitOnErr(cmd, "", err)
|
commonCmd.ExitOnErr(cmd, "", err)
|
||||||
|
@ -84,6 +88,8 @@ func patch(cmd *cobra.Command, _ []string) {
|
||||||
prm.NewAttributes = newAttrs
|
prm.NewAttributes = newAttrs
|
||||||
prm.ReplaceAttribute = replaceAttrs
|
prm.ReplaceAttribute = replaceAttrs
|
||||||
|
|
||||||
|
prm.NewSplitHeader = parseSplitHeaderBinaryOrJSON(cmd)
|
||||||
|
|
||||||
for i := range ranges {
|
for i := range ranges {
|
||||||
prm.PayloadPatches = append(prm.PayloadPatches, internalclient.PayloadPatch{
|
prm.PayloadPatches = append(prm.PayloadPatches, internalclient.PayloadPatch{
|
||||||
Range: ranges[i],
|
Range: ranges[i],
|
||||||
|
@ -99,11 +105,9 @@ func patch(cmd *cobra.Command, _ []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseNewObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
func parseNewObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||||
var rawAttrs []string
|
rawAttrs, err := cmd.Flags().GetStringSlice(newAttrsFlagName)
|
||||||
|
if err != nil {
|
||||||
raw := cmd.Flag(newAttrsFlagName).Value.String()
|
return nil, err
|
||||||
if len(raw) != 0 {
|
|
||||||
rawAttrs = strings.Split(raw, ",")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
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)
|
v, _ := cmd.Flags().GetStringSlice(payloadFlagName)
|
||||||
return v
|
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(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-filename", false, "Do not set well-known filename attribute")
|
||||||
flags.Bool("disable-timestamp", false, "Do not set well-known timestamp 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")
|
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) {
|
func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||||
var rawAttrs []string
|
rawAttrs, err := cmd.Flags().GetStringSlice("attributes")
|
||||||
|
if err != nil {
|
||||||
raw := cmd.Flag("attributes").Value.String()
|
return nil, err
|
||||||
if len(raw) != 0 {
|
|
||||||
rawAttrs = strings.Split(raw, ",")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||||
|
|
|
@ -38,7 +38,7 @@ func initObjectRangeCmd() {
|
||||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||||
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
_ = 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.String(fileFlag, "", "File to write object payload to. Default: stdout.")
|
||||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func getObjectRange(cmd *cobra.Command, _ []string) {
|
||||||
var cnr cid.ID
|
var cnr cid.ID
|
||||||
var obj oid.ID
|
var obj oid.ID
|
||||||
|
|
||||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
objAddr := ReadObjectAddress(cmd, &cnr, &obj)
|
||||||
|
|
||||||
ranges, err := getRangeList(cmd)
|
ranges, err := getRangeList(cmd)
|
||||||
commonCmd.ExitOnErr(cmd, "", err)
|
commonCmd.ExitOnErr(cmd, "", err)
|
||||||
|
@ -195,11 +195,10 @@ func marshalECInfo(cmd *cobra.Command, info *objectSDK.ECInfo) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRangeList(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
func getRangeList(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
||||||
v := cmd.Flag("range").Value.String()
|
vs, err := cmd.Flags().GetStringSlice("range")
|
||||||
if len(v) == 0 {
|
if len(vs) == 0 || err != nil {
|
||||||
return nil, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
vs := strings.Split(v, ",")
|
|
||||||
rs := make([]objectSDK.Range, len(vs))
|
rs := make([]objectSDK.Range, len(vs))
|
||||||
for i := range vs {
|
for i := range vs {
|
||||||
before, after, found := strings.Cut(vs[i], rangeSep)
|
before, after, found := strings.Cut(vs[i], rangeSep)
|
||||||
|
|
|
@ -74,7 +74,7 @@ func parseXHeaders(cmd *cobra.Command) []string {
|
||||||
return xs
|
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)
|
readCID(cmd, cnr)
|
||||||
readOID(cmd, obj)
|
readOID(cmd, obj)
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,19 @@ package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
"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/cmd/frostfs-cli/internal/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
|
"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"
|
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/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,23 +33,29 @@ func _client() (tree.TreeServiceClient, error) {
|
||||||
return nil, err
|
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{
|
opts := []grpc.DialOption{
|
||||||
grpc.WithChainUnaryInterceptor(
|
grpc.WithChainUnaryInterceptor(
|
||||||
metrics.NewUnaryClientInterceptor(),
|
tracing.NewUnaryClientInterceptor(),
|
||||||
tracing.NewUnaryClientInteceptor(),
|
|
||||||
),
|
),
|
||||||
grpc.WithChainStreamInterceptor(
|
grpc.WithChainStreamInterceptor(
|
||||||
metrics.NewStreamClientInterceptor(),
|
|
||||||
tracing.NewStreamClientInterceptor(),
|
tracing.NewStreamClientInterceptor(),
|
||||||
),
|
),
|
||||||
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
||||||
|
grpc.WithDisableServiceConfig(),
|
||||||
|
grpc.WithTransportCredentials(creds),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(netAddr.URIAddr(), "grpcs:") {
|
cc, err := grpc.NewClient(host, opts...)
|
||||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
}
|
|
||||||
|
|
||||||
cc, err := grpc.NewClient(netAddr.URIAddr(), opts...)
|
|
||||||
return tree.NewTreeServiceClient(cc), err
|
return tree.NewTreeServiceClient(cc), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
|
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||||
control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -38,13 +39,14 @@ func reloadConfig() error {
|
||||||
}
|
}
|
||||||
cmode.Store(cfg.GetBool("node.kludge_compatibility_mode"))
|
cmode.Store(cfg.GetBool("node.kludge_compatibility_mode"))
|
||||||
audit.Store(cfg.GetBool("audit.enabled"))
|
audit.Store(cfg.GetBool("audit.enabled"))
|
||||||
|
var logPrm logger.Prm
|
||||||
err = logPrm.SetLevelString(cfg.GetString("logger.level"))
|
err = logPrm.SetLevelString(cfg.GetString("logger.level"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logPrm.PrependTimestamp = cfg.GetBool("logger.timestamp")
|
log.Reload(logPrm)
|
||||||
|
|
||||||
return logPrm.Reload()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchForSignal(ctx context.Context, cancel func()) {
|
func watchForSignal(ctx context.Context, cancel func()) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (c *httpComponent) reload(ctx context.Context) {
|
||||||
log.Info(ctx, c.name+" config updated")
|
log.Info(ctx, c.name+" config updated")
|
||||||
if err := c.shutdown(ctx); err != nil {
|
if err := c.shutdown(ctx); err != nil {
|
||||||
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
||||||
zap.String("error", err.Error()),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
c.init(ctx)
|
c.init(ctx)
|
||||||
|
|
|
@ -31,7 +31,6 @@ const (
|
||||||
var (
|
var (
|
||||||
wg = new(sync.WaitGroup)
|
wg = new(sync.WaitGroup)
|
||||||
intErr = make(chan error) // internal inner ring errors
|
intErr = make(chan error) // internal inner ring errors
|
||||||
logPrm = new(logger.Prm)
|
|
||||||
innerRing *innerring.Server
|
innerRing *innerring.Server
|
||||||
pprofCmp *pprofComponent
|
pprofCmp *pprofComponent
|
||||||
metricsCmp *httpComponent
|
metricsCmp *httpComponent
|
||||||
|
@ -70,6 +69,7 @@ func main() {
|
||||||
|
|
||||||
metrics := irMetrics.NewInnerRingMetrics()
|
metrics := irMetrics.NewInnerRingMetrics()
|
||||||
|
|
||||||
|
var logPrm logger.Prm
|
||||||
err = logPrm.SetLevelString(
|
err = logPrm.SetLevelString(
|
||||||
cfg.GetString("logger.level"),
|
cfg.GetString("logger.level"),
|
||||||
)
|
)
|
||||||
|
@ -119,12 +119,12 @@ func shutdown(ctx context.Context) {
|
||||||
innerRing.Stop(ctx)
|
innerRing.Stop(ctx)
|
||||||
if err := metricsCmp.shutdown(ctx); err != nil {
|
if err := metricsCmp.shutdown(ctx); err != nil {
|
||||||
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
||||||
zap.String("error", err.Error()),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err := pprofCmp.shutdown(ctx); err != nil {
|
if err := pprofCmp.shutdown(ctx); err != nil {
|
||||||
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
||||||
zap.String("error", err.Error()),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (c *pprofComponent) reload(ctx context.Context) {
|
||||||
log.Info(ctx, c.name+" config updated")
|
log.Info(ctx, c.name+" config updated")
|
||||||
if err := c.shutdown(ctx); err != nil {
|
if err := c.shutdown(ctx); err != nil {
|
||||||
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
log.Debug(ctx, logs.FrostFSIRCouldNotShutdownHTTPServer,
|
||||||
zap.String("error", err.Error()))
|
zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,17 @@ package meta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
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"
|
schema "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tuiCMD = &cobra.Command{
|
var tuiCMD = &cobra.Command{
|
||||||
|
@ -27,6 +31,11 @@ Available search filters:
|
||||||
|
|
||||||
var initialPrompt string
|
var initialPrompt string
|
||||||
|
|
||||||
|
var parserPerSchemaVersion = map[uint64]schemaCommon.Parser{
|
||||||
|
2: schema.MetabaseParserV2,
|
||||||
|
3: schema.MetabaseParserV3,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.AddComponentPathFlag(tuiCMD, &vPath)
|
common.AddComponentPathFlag(tuiCMD, &vPath)
|
||||||
|
|
||||||
|
@ -49,12 +58,22 @@ func runTUI(cmd *cobra.Command) error {
|
||||||
}
|
}
|
||||||
defer db.Close()
|
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.
|
// Need if app was stopped with Ctrl-C.
|
||||||
ctx, cancel := context.WithCancel(cmd.Context())
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
app := tview.NewApplication()
|
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("cid", tui.CIDParser, "CID")
|
||||||
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
||||||
|
@ -69,3 +88,31 @@ func runTUI(cmd *cobra.Command) error {
|
||||||
app.SetRoot(ui, true).SetFocus(ui)
|
app.SetRoot(ui, true).SetFocus(ui)
|
||||||
return app.Run()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -80,10 +80,15 @@ var (
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
UserAttributeParser = NewUserAttributeKeyBucketParser(
|
UserAttributeParserV2 = NewUserAttributeKeyBucketParser(
|
||||||
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UserAttributeParserV3 = NewUserAttributeKeyBucketParserWithSpecificKeys(
|
||||||
|
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||||
|
[]string{"FilePath", "S3-Access-Box-CRDT-Name"},
|
||||||
|
)
|
||||||
|
|
||||||
PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{
|
PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{
|
||||||
cidResolver: StrictResolver,
|
cidResolver: StrictResolver,
|
||||||
oidResolver: StrictResolver,
|
oidResolver: StrictResolver,
|
||||||
|
@ -108,4 +113,14 @@ var (
|
||||||
cidResolver: StrictResolver,
|
cidResolver: StrictResolver,
|
||||||
oidResolver: LenientResolver,
|
oidResolver: LenientResolver,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ExpirationEpochToObjectParser = NewPrefixBucketParser(ExpirationEpochToObject, records.ExpirationEpochToObjectRecordParser, Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
ObjectToExpirationEpochParser = NewPrefixContainerBucketParser(ObjectToExpirationEpoch, records.ObjectToExpirationEpochRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,6 +22,8 @@ const (
|
||||||
Split
|
Split
|
||||||
ContainerCounters
|
ContainerCounters
|
||||||
ECInfo
|
ECInfo
|
||||||
|
ExpirationEpochToObject
|
||||||
|
ObjectToExpirationEpoch
|
||||||
)
|
)
|
||||||
|
|
||||||
var x = map[Prefix]string{
|
var x = map[Prefix]string{
|
||||||
|
@ -43,6 +45,8 @@ var x = map[Prefix]string{
|
||||||
Split: "Split",
|
Split: "Split",
|
||||||
ContainerCounters: "Container Counters",
|
ContainerCounters: "Container Counters",
|
||||||
ECInfo: "EC Info",
|
ECInfo: "EC Info",
|
||||||
|
ExpirationEpochToObject: "Exp. Epoch to Object",
|
||||||
|
ObjectToExpirationEpoch: "Object to Exp. Epoch",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Prefix) String() string {
|
func (p Prefix) String() string {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
func (b *PrefixBucket) String() string {
|
func (b *PrefixBucket) String() string {
|
||||||
return common.FormatSimple(
|
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(
|
return fmt.Sprintf(
|
||||||
"%s CID %s",
|
"%s CID %s",
|
||||||
common.FormatSimple(
|
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),
|
common.FormatSimple(b.id.String(), tcell.ColorAqua),
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ func (b *ContainerBucket) String() string {
|
||||||
func (b *UserAttributeKeyBucket) String() string {
|
func (b *UserAttributeKeyBucket) String() string {
|
||||||
return fmt.Sprintf("%s CID %s ATTR-KEY %s",
|
return fmt.Sprintf("%s CID %s ATTR-KEY %s",
|
||||||
common.FormatSimple(
|
common.FormatSimple(
|
||||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
),
|
),
|
||||||
common.FormatSimple(
|
common.FormatSimple(
|
||||||
fmt.Sprintf("%-44s", b.id), tcell.ColorAqua,
|
fmt.Sprintf("%-44s", b.id), tcell.ColorAqua,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package buckets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
@ -61,6 +62,7 @@ var (
|
||||||
ErrInvalidKeyLength = errors.New("invalid key length")
|
ErrInvalidKeyLength = errors.New("invalid key length")
|
||||||
ErrInvalidValueLength = errors.New("invalid value length")
|
ErrInvalidValueLength = errors.New("invalid value length")
|
||||||
ErrInvalidPrefix = errors.New("invalid prefix")
|
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||||
|
ErrUnexpectedAttributeKey = errors.New("unexpected attribute key")
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPrefixBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser {
|
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 {
|
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) {
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
return nil, nil, ErrNotBucket
|
return nil, nil, ErrNotBucket
|
||||||
|
@ -147,6 +153,11 @@ func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
b.key = string(key[33:])
|
b.key = string(key[33:])
|
||||||
|
|
||||||
|
if len(keys) != 0 && !slices.Contains(keys, b.key) {
|
||||||
|
return nil, nil, ErrUnexpectedAttributeKey
|
||||||
|
}
|
||||||
|
|
||||||
return &b, next, nil
|
return &b, next, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,30 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/buckets"
|
"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(
|
common.Any(
|
||||||
buckets.GraveyardParser,
|
buckets.GraveyardParser,
|
||||||
buckets.GarbageParser,
|
buckets.GarbageParser,
|
||||||
|
@ -18,7 +41,7 @@ var MetabaseParser = common.WithFallback(
|
||||||
buckets.SmallParser,
|
buckets.SmallParser,
|
||||||
buckets.RootParser,
|
buckets.RootParser,
|
||||||
buckets.OwnerParser,
|
buckets.OwnerParser,
|
||||||
buckets.UserAttributeParser,
|
buckets.UserAttributeParserV2,
|
||||||
buckets.PayloadHashParser,
|
buckets.PayloadHashParser,
|
||||||
buckets.ParentParser,
|
buckets.ParentParser,
|
||||||
buckets.SplitParser,
|
buckets.SplitParser,
|
||||||
|
|
|
@ -63,3 +63,11 @@ func (r *ContainerCountersRecord) DetailedString() string {
|
||||||
func (r *ECInfoRecord) DetailedString() string {
|
func (r *ECInfoRecord) DetailedString() string {
|
||||||
return spew.Sdump(*r)
|
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
|
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
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -133,3 +134,22 @@ func (r *ECInfoRecord) String() string {
|
||||||
len(r.ids),
|
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
|
id oid.ID
|
||||||
ids []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
|
path := parentBucket.Path
|
||||||
parser := parentBucket.NextParser
|
parser := parentBucket.NextParser
|
||||||
|
|
||||||
buffer, err := LoadBuckets(ctx, v.ui.db, path, v.ui.loadBufferSize)
|
buffer := LoadBuckets(ctx, v.ui.db, path, v.ui.loadBufferSize)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for item := range buffer {
|
for item := range buffer {
|
||||||
if item.err != nil {
|
if item.err != nil {
|
||||||
|
@ -135,6 +132,7 @@ func (v *BucketsView) loadNodeChildren(
|
||||||
}
|
}
|
||||||
bucket := item.val
|
bucket := item.val
|
||||||
|
|
||||||
|
var err error
|
||||||
bucket.Entry, bucket.NextParser, err = parser(bucket.Name, nil)
|
bucket.Entry, bucket.NextParser, err = parser(bucket.Name, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -180,10 +178,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Check the current bucket's nested buckets if exist
|
// Check the current bucket's nested buckets if exist
|
||||||
bucketsBuffer, err := LoadBuckets(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
bucketsBuffer := LoadBuckets(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for item := range bucketsBuffer {
|
for item := range bucketsBuffer {
|
||||||
if item.err != nil {
|
if item.err != nil {
|
||||||
|
@ -191,6 +186,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
||||||
}
|
}
|
||||||
b := item.val
|
b := item.val
|
||||||
|
|
||||||
|
var err error
|
||||||
b.Entry, b.NextParser, err = bucket.NextParser(b.Name, nil)
|
b.Entry, b.NextParser, err = bucket.NextParser(b.Name, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -206,10 +202,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the current bucket's nested records if exist
|
// Check the current bucket's nested records if exist
|
||||||
recordsBuffer, err := LoadRecords(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
recordsBuffer := LoadRecords(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for item := range recordsBuffer {
|
for item := range recordsBuffer {
|
||||||
if item.err != nil {
|
if item.err != nil {
|
||||||
|
@ -217,6 +210,7 @@ func (v *BucketsView) bucketSatisfiesFilter(
|
||||||
}
|
}
|
||||||
r := item.val
|
r := item.val
|
||||||
|
|
||||||
|
var err error
|
||||||
r.Entry, _, err = bucket.NextParser(r.Key, r.Value)
|
r.Entry, _, err = bucket.NextParser(r.Key, r.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
@ -35,7 +35,7 @@ func resolvePath(tx *bbolt.Tx, path [][]byte) (*bbolt.Bucket, error) {
|
||||||
func load[T any](
|
func load[T any](
|
||||||
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
||||||
filter func(key, value []byte) bool, transform func(key, value []byte) T,
|
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)
|
buffer := make(chan Item[T], bufferSize)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -77,13 +77,13 @@ func load[T any](
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return buffer, nil
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadBuckets(
|
func LoadBuckets(
|
||||||
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
||||||
) (<-chan Item[*Bucket], error) {
|
) <-chan Item[*Bucket] {
|
||||||
buffer, err := load(
|
buffer := load(
|
||||||
ctx, db, path, bufferSize,
|
ctx, db, path, bufferSize,
|
||||||
func(_, value []byte) bool {
|
func(_, value []byte) bool {
|
||||||
return value == nil
|
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(
|
func LoadRecords(
|
||||||
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
ctx context.Context, db *bbolt.DB, path [][]byte, bufferSize int,
|
||||||
) (<-chan Item[*Record], error) {
|
) <-chan Item[*Record] {
|
||||||
buffer, err := load(
|
buffer := load(
|
||||||
ctx, db, path, bufferSize,
|
ctx, db, path, bufferSize,
|
||||||
func(_, value []byte) bool {
|
func(_, value []byte) bool {
|
||||||
return value != nil
|
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
|
// 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)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
buffer, err := load(
|
buffer := load(
|
||||||
ctx, db, path, 1,
|
ctx, db, path, 1,
|
||||||
nil,
|
nil,
|
||||||
func(_, value []byte) []byte { return value },
|
func(_, value []byte) []byte { return value },
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
x, ok := <-buffer
|
x, ok := <-buffer
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if x.err != nil {
|
if x.err != nil {
|
||||||
return false, err
|
return false, x.err
|
||||||
}
|
}
|
||||||
if x.val != nil {
|
if x.val != nil {
|
||||||
return false, err
|
return false, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"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.
|
// Used history data for search prompt, so just make that data recent.
|
||||||
if f.historyPointer != len(f.history) && s == f.history[f.historyPointer] {
|
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)
|
f.history = append(f.history, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,10 +62,7 @@ func (v *RecordsView) Mount(ctx context.Context) error {
|
||||||
|
|
||||||
ctx, v.onUnmount = context.WithCancel(ctx)
|
ctx, v.onUnmount = context.WithCancel(ctx)
|
||||||
|
|
||||||
tempBuffer, err := LoadRecords(ctx, v.ui.db, v.bucket.Path, v.ui.loadBufferSize)
|
tempBuffer := LoadRecords(ctx, v.ui.db, v.bucket.Path, v.ui.loadBufferSize)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
v.buffer = make(chan *Record, v.ui.loadBufferSize)
|
v.buffer = make(chan *Record, v.ui.loadBufferSize)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -73,11 +70,12 @@ func (v *RecordsView) Mount(ctx context.Context) error {
|
||||||
|
|
||||||
for item := range tempBuffer {
|
for item := range tempBuffer {
|
||||||
if item.err != nil {
|
if item.err != nil {
|
||||||
v.ui.stopOnError(err)
|
v.ui.stopOnError(item.err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
record := item.val
|
record := item.val
|
||||||
|
|
||||||
|
var err error
|
||||||
record.Entry, _, err = v.bucket.NextParser(record.Key, record.Value)
|
record.Entry, _, err = v.bucket.NextParser(record.Key, record.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.ui.stopOnError(err)
|
v.ui.stopOnError(err)
|
||||||
|
|
|
@ -19,6 +19,7 @@ func initAPEManagerService(c *cfg) {
|
||||||
c.cfgObject.cfgAccessPolicyEngine.policyContractHash)
|
c.cfgObject.cfgAccessPolicyEngine.policyContractHash)
|
||||||
|
|
||||||
execsvc := apemanager.New(c.cfgObject.cnrSource, contractStorage,
|
execsvc := apemanager.New(c.cfgObject.cnrSource, contractStorage,
|
||||||
|
c.cfgMorph.client,
|
||||||
apemanager.WithLogger(c.log))
|
apemanager.WithLogger(c.log))
|
||||||
sigsvc := apemanager.NewSignService(&c.key.PrivateKey, execsvc)
|
sigsvc := apemanager.NewSignService(&c.key.PrivateKey, execsvc)
|
||||||
auditSvc := apemanager.NewAuditService(sigsvc, c.log, c.audit)
|
auditSvc := apemanager.NewAuditService(sigsvc, c.log, c.audit)
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
|
"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/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||||
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
|
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"
|
utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
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/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 {
|
type valueWithError[V any] struct {
|
||||||
v V
|
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.
|
// updates the value from the network on cache miss or by TTL.
|
||||||
//
|
//
|
||||||
// returned value should not be modified.
|
// 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
|
hit := false
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -71,7 +79,7 @@ func (c *ttlNetCache[K, V]) get(key K) (V, error) {
|
||||||
return val.v, val.e
|
return val.v, val.e
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := c.netRdr(key)
|
v, err := c.netRdr(ctx, key)
|
||||||
|
|
||||||
c.cache.Add(key, &valueWithError[V]{
|
c.cache.Add(key, &valueWithError[V]{
|
||||||
v: v,
|
v: v,
|
||||||
|
@ -109,55 +117,6 @@ func (c *ttlNetCache[K, V]) remove(key K) {
|
||||||
hit = c.cache.Remove(key)
|
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
|
// wrapper over TTL cache of values read from the network
|
||||||
// that implements container storage.
|
// that implements container storage.
|
||||||
type ttlContainerStorage struct {
|
type ttlContainerStorage struct {
|
||||||
|
@ -166,11 +125,11 @@ type ttlContainerStorage struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCachedContainerStorage(v container.Source, ttl time.Duration, containerCacheSize uint32) ttlContainerStorage {
|
func newCachedContainerStorage(v container.Source, ttl time.Duration, containerCacheSize uint32) ttlContainerStorage {
|
||||||
lruCnrCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(id cid.ID) (*container.Container, error) {
|
lruCnrCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(ctx context.Context, id cid.ID) (*container.Container, error) {
|
||||||
return v.Get(id)
|
return v.Get(ctx, id)
|
||||||
}, metrics.NewCacheMetrics("container"))
|
}, metrics.NewCacheMetrics("container"))
|
||||||
lruDelInfoCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(id cid.ID) (*container.DelInfo, error) {
|
lruDelInfoCache := newNetworkTTLCache(int(containerCacheSize), ttl, func(ctx context.Context, id cid.ID) (*container.DelInfo, error) {
|
||||||
return v.DeletionInfo(id)
|
return v.DeletionInfo(ctx, id)
|
||||||
}, metrics.NewCacheMetrics("container_deletion_info"))
|
}, metrics.NewCacheMetrics("container_deletion_info"))
|
||||||
|
|
||||||
return ttlContainerStorage{
|
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
|
// 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.
|
// or expired, then it returns value from side chain and updates the cache.
|
||||||
func (s ttlContainerStorage) Get(cnr cid.ID) (*container.Container, error) {
|
func (s ttlContainerStorage) Get(ctx context.Context, cnr cid.ID) (*container.Container, error) {
|
||||||
return s.containerCache.get(cnr)
|
return s.containerCache.get(ctx, cnr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s ttlContainerStorage) DeletionInfo(cnr cid.ID) (*container.DelInfo, error) {
|
func (s ttlContainerStorage) DeletionInfo(ctx context.Context, cnr cid.ID) (*container.DelInfo, error) {
|
||||||
return s.delInfoCache.get(cnr)
|
return s.delInfoCache.get(ctx, cnr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type lruNetmapSource struct {
|
type lruNetmapSource struct {
|
||||||
netState netmap.State
|
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
|
const netmapCacheSize = 10
|
||||||
|
|
||||||
lruNetmapCache := newNetworkLRUCache(netmapCacheSize, func(key uint64) (*netmapSDK.NetMap, error) {
|
cache, err := simplelru.NewLRU[uint64, *atomic.Pointer[netmapSDK.NetMap]](netmapCacheSize, nil)
|
||||||
return v.GetNetMapByEpoch(key)
|
fatalOnErr(err)
|
||||||
}, metrics.NewCacheMetrics("netmap"))
|
|
||||||
|
|
||||||
return &lruNetmapSource{
|
src := &lruNetmapSource{
|
||||||
netState: s,
|
netState: netState,
|
||||||
cache: lruNetmapCache,
|
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) {
|
func (s *lruNetmapSource) mergeCacheWithCandidates(candidates []netmapSDK.NodeInfo) {
|
||||||
return s.getNetMapByEpoch(s.netState.CurrentEpoch() - diff)
|
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) {
|
// reads value by the key.
|
||||||
return s.getNetMapByEpoch(epoch)
|
//
|
||||||
|
// 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) {
|
// mergeNetmapWithCandidates updates nodes state in the provided netmap with state in the list of candidates.
|
||||||
val, err := s.cache.get(epoch)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -232,7 +407,7 @@ func (s *lruNetmapSource) getNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, err
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *lruNetmapSource) Epoch() (uint64, error) {
|
func (s *lruNetmapSource) Epoch(_ context.Context) (uint64, error) {
|
||||||
return s.netState.CurrentEpoch(), nil
|
return s.netState.CurrentEpoch(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +415,10 @@ type cachedIRFetcher struct {
|
||||||
*ttlNetCache[struct{}, [][]byte]
|
*ttlNetCache[struct{}, [][]byte]
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCachedIRFetcher(f interface{ InnerRingKeys() ([][]byte, error) }) cachedIRFetcher {
|
func newCachedIRFetcher(f interface {
|
||||||
|
InnerRingKeys(ctx context.Context) ([][]byte, error)
|
||||||
|
},
|
||||||
|
) cachedIRFetcher {
|
||||||
const (
|
const (
|
||||||
irFetcherCacheSize = 1 // we intend to store only one value
|
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,
|
irFetcherCache := newNetworkTTLCache(irFetcherCacheSize, irFetcherCacheTTL,
|
||||||
func(_ struct{}) ([][]byte, error) {
|
func(ctx context.Context, _ struct{}) ([][]byte, error) {
|
||||||
return f.InnerRingKeys()
|
return f.InnerRingKeys(ctx)
|
||||||
}, metrics.NewCacheMetrics("ir_keys"),
|
}, 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
|
// 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 or expired, then it returns keys from side chain and updates
|
||||||
// the cache.
|
// the cache.
|
||||||
func (f cachedIRFetcher) InnerRingKeys() ([][]byte, error) {
|
func (f cachedIRFetcher) InnerRingKeys(ctx context.Context) ([][]byte, error) {
|
||||||
val, err := f.get(struct{}{})
|
val, err := f.get(ctx, struct{}{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
const ttl = time.Second * 30
|
||||||
|
|
||||||
hit := false
|
hit := false
|
||||||
|
@ -311,7 +489,7 @@ func (c *ttlMaxObjectSizeCache) MaxObjectSize() uint64 {
|
||||||
c.mtx.Lock()
|
c.mtx.Lock()
|
||||||
size = c.lastSize
|
size = c.lastSize
|
||||||
if !c.lastUpdated.After(prevUpdated) {
|
if !c.lastUpdated.After(prevUpdated) {
|
||||||
size = c.src.MaxObjectSize()
|
size = c.src.MaxObjectSize(ctx)
|
||||||
c.lastSize = size
|
c.lastSize = size
|
||||||
c.lastUpdated = time.Now()
|
c.lastUpdated = time.Now()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,7 +20,7 @@ func TestTTLNetCache(t *testing.T) {
|
||||||
t.Run("Test Add and Get", func(t *testing.T) {
|
t.Run("Test Add and Get", func(t *testing.T) {
|
||||||
ti := time.Now()
|
ti := time.Now()
|
||||||
cache.set(key, ti, nil)
|
cache.set(key, ti, nil)
|
||||||
val, err := cache.get(key)
|
val, err := cache.get(context.Background(), key)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, ti, val)
|
require.Equal(t, ti, val)
|
||||||
})
|
})
|
||||||
|
@ -26,7 +29,7 @@ func TestTTLNetCache(t *testing.T) {
|
||||||
ti := time.Now()
|
ti := time.Now()
|
||||||
cache.set(key, ti, nil)
|
cache.set(key, ti, nil)
|
||||||
time.Sleep(2 * ttlDuration)
|
time.Sleep(2 * ttlDuration)
|
||||||
val, err := cache.get(key)
|
val, err := cache.get(context.Background(), key)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, val, ti)
|
require.NotEqual(t, val, ti)
|
||||||
})
|
})
|
||||||
|
@ -35,20 +38,20 @@ func TestTTLNetCache(t *testing.T) {
|
||||||
ti := time.Now()
|
ti := time.Now()
|
||||||
cache.set(key, ti, nil)
|
cache.set(key, ti, nil)
|
||||||
cache.remove(key)
|
cache.remove(key)
|
||||||
val, err := cache.get(key)
|
val, err := cache.get(context.Background(), key)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, val, ti)
|
require.NotEqual(t, val, ti)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test Cache Error", func(t *testing.T) {
|
t.Run("Test Cache Error", func(t *testing.T) {
|
||||||
cache.set("error", time.Now(), errors.New("mock error"))
|
cache.set("error", time.Now(), errors.New("mock error"))
|
||||||
_, err := cache.get("error")
|
_, err := cache.get(context.Background(), "error")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, "mock error", err.Error())
|
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" {
|
if key == "error" {
|
||||||
return time.Now(), errors.New("mock error")
|
return time.Now(), errors.New("mock error")
|
||||||
}
|
}
|
||||||
|
@ -58,3 +61,75 @@ func testNetValueReader(key string) (time.Time, error) {
|
||||||
type noopCacheMetricts struct{}
|
type noopCacheMetricts struct{}
|
||||||
|
|
||||||
func (m *noopCacheMetricts) AddMethodDuration(method string, d time.Duration, hit bool) {}
|
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/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
|
||||||
internalNet "git.frostfs.info/TrueCloudLab/frostfs-node/internal/net"
|
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/ape/chainbase"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
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-node/pkg/util/state"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore"
|
"git.frostfs.info/TrueCloudLab/frostfs-observability/logging/lokicore"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-qos/limiting"
|
||||||
netmapV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
|
netmapV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
@ -106,6 +108,7 @@ type applicationConfiguration struct {
|
||||||
level string
|
level string
|
||||||
destination string
|
destination string
|
||||||
timestamp bool
|
timestamp bool
|
||||||
|
options []zap.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectCfg struct {
|
ObjectCfg struct {
|
||||||
|
@ -115,7 +118,6 @@ type applicationConfiguration struct {
|
||||||
|
|
||||||
EngineCfg struct {
|
EngineCfg struct {
|
||||||
errorThreshold uint32
|
errorThreshold uint32
|
||||||
shardPoolSize uint32
|
|
||||||
shards []shardCfg
|
shards []shardCfg
|
||||||
lowMem bool
|
lowMem bool
|
||||||
}
|
}
|
||||||
|
@ -134,6 +136,7 @@ type shardCfg struct {
|
||||||
refillMetabase bool
|
refillMetabase bool
|
||||||
refillMetabaseWorkersCount int
|
refillMetabaseWorkersCount int
|
||||||
mode shardmode.Mode
|
mode shardmode.Mode
|
||||||
|
limiter qos.Limiter
|
||||||
|
|
||||||
metaCfg struct {
|
metaCfg struct {
|
||||||
path string
|
path string
|
||||||
|
@ -230,6 +233,14 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
|
||||||
a.LoggerCfg.level = loggerconfig.Level(c)
|
a.LoggerCfg.level = loggerconfig.Level(c)
|
||||||
a.LoggerCfg.destination = loggerconfig.Destination(c)
|
a.LoggerCfg.destination = loggerconfig.Destination(c)
|
||||||
a.LoggerCfg.timestamp = loggerconfig.Timestamp(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
|
// Object
|
||||||
|
|
||||||
|
@ -247,45 +258,47 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
|
||||||
// Storage Engine
|
// Storage Engine
|
||||||
|
|
||||||
a.EngineCfg.errorThreshold = engineconfig.ShardErrorThreshold(c)
|
a.EngineCfg.errorThreshold = engineconfig.ShardErrorThreshold(c)
|
||||||
a.EngineCfg.shardPoolSize = engineconfig.ShardPoolSize(c)
|
|
||||||
a.EngineCfg.lowMem = engineconfig.EngineLowMemoryConsumption(c)
|
a.EngineCfg.lowMem = engineconfig.EngineLowMemoryConsumption(c)
|
||||||
|
|
||||||
return engineconfig.IterateShards(c, false, func(sc *shardconfig.Config) error { return a.updateShardConfig(c, sc) })
|
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 {
|
func (a *applicationConfiguration) updateShardConfig(c *config.Config, source *shardconfig.Config) error {
|
||||||
var newConfig shardCfg
|
var target shardCfg
|
||||||
|
|
||||||
newConfig.refillMetabase = oldConfig.RefillMetabase()
|
target.refillMetabase = source.RefillMetabase()
|
||||||
newConfig.refillMetabaseWorkersCount = oldConfig.RefillMetabaseWorkersCount()
|
target.refillMetabaseWorkersCount = source.RefillMetabaseWorkersCount()
|
||||||
newConfig.mode = oldConfig.Mode()
|
target.mode = source.Mode()
|
||||||
newConfig.compress = oldConfig.Compress()
|
target.compress = source.Compress()
|
||||||
newConfig.estimateCompressibility = oldConfig.EstimateCompressibility()
|
target.estimateCompressibility = source.EstimateCompressibility()
|
||||||
newConfig.estimateCompressibilityThreshold = oldConfig.EstimateCompressibilityThreshold()
|
target.estimateCompressibilityThreshold = source.EstimateCompressibilityThreshold()
|
||||||
newConfig.uncompressableContentType = oldConfig.UncompressableContentTypes()
|
target.uncompressableContentType = source.UncompressableContentTypes()
|
||||||
newConfig.smallSizeObjectLimit = oldConfig.SmallSizeLimit()
|
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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *applicationConfiguration) setShardWriteCacheConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
func (a *applicationConfiguration) setShardWriteCacheConfig(target *shardCfg, source *shardconfig.Config) {
|
||||||
writeCacheCfg := oldConfig.WriteCache()
|
writeCacheCfg := source.WriteCache()
|
||||||
if writeCacheCfg.Enabled() {
|
if writeCacheCfg.Enabled() {
|
||||||
wc := &newConfig.writecacheCfg
|
wc := &target.writecacheCfg
|
||||||
|
|
||||||
wc.enabled = true
|
wc.enabled = true
|
||||||
wc.path = writeCacheCfg.Path()
|
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") {
|
if config.BoolSafe(c.Sub("tree"), "enabled") {
|
||||||
piloramaCfg := oldConfig.Pilorama()
|
piloramaCfg := source.Pilorama()
|
||||||
pr := &newConfig.piloramaCfg
|
pr := &target.piloramaCfg
|
||||||
|
|
||||||
pr.enabled = true
|
pr.enabled = true
|
||||||
pr.path = piloramaCfg.Path()
|
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 {
|
func (a *applicationConfiguration) setShardStorageConfig(target *shardCfg, source *shardconfig.Config) error {
|
||||||
blobStorCfg := oldConfig.BlobStor()
|
blobStorCfg := source.BlobStor()
|
||||||
storagesCfg := blobStorCfg.Storages()
|
storagesCfg := blobStorCfg.Storages()
|
||||||
|
|
||||||
ss := make([]subStorageCfg, 0, len(storagesCfg))
|
ss := make([]subStorageCfg, 0, len(storagesCfg))
|
||||||
|
@ -347,13 +360,13 @@ func (a *applicationConfiguration) setShardStorageConfig(newConfig *shardCfg, ol
|
||||||
ss = append(ss, sCfg)
|
ss = append(ss, sCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig.subStorages = ss
|
target.subStorages = ss
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *applicationConfiguration) setMetabaseConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
func (a *applicationConfiguration) setMetabaseConfig(target *shardCfg, source *shardconfig.Config) {
|
||||||
metabaseCfg := oldConfig.Metabase()
|
metabaseCfg := source.Metabase()
|
||||||
m := &newConfig.metaCfg
|
m := &target.metaCfg
|
||||||
|
|
||||||
m.path = metabaseCfg.Path()
|
m.path = metabaseCfg.Path()
|
||||||
m.perm = metabaseCfg.BoltDB().Perm()
|
m.perm = metabaseCfg.BoltDB().Perm()
|
||||||
|
@ -361,12 +374,25 @@ func (a *applicationConfiguration) setMetabaseConfig(newConfig *shardCfg, oldCon
|
||||||
m.maxBatchSize = metabaseCfg.BoltDB().MaxBatchSize()
|
m.maxBatchSize = metabaseCfg.BoltDB().MaxBatchSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *applicationConfiguration) setGCConfig(newConfig *shardCfg, oldConfig *shardconfig.Config) {
|
func (a *applicationConfiguration) setGCConfig(target *shardCfg, source *shardconfig.Config) {
|
||||||
gcCfg := oldConfig.GC()
|
gcCfg := source.GC()
|
||||||
newConfig.gcCfg.removerBatchSize = gcCfg.RemoverBatchSize()
|
target.gcCfg.removerBatchSize = gcCfg.RemoverBatchSize()
|
||||||
newConfig.gcCfg.removerSleepInterval = gcCfg.RemoverSleepInterval()
|
target.gcCfg.removerSleepInterval = gcCfg.RemoverSleepInterval()
|
||||||
newConfig.gcCfg.expiredCollectorBatchSize = gcCfg.ExpiredCollectorBatchSize()
|
target.gcCfg.expiredCollectorBatchSize = gcCfg.ExpiredCollectorBatchSize()
|
||||||
newConfig.gcCfg.expiredCollectorWorkerCount = gcCfg.ExpiredCollectorWorkerCount()
|
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
|
// internals contains application-specific internals that are created
|
||||||
|
@ -456,7 +482,6 @@ type shared struct {
|
||||||
// dynamicConfiguration stores parameters of the
|
// dynamicConfiguration stores parameters of the
|
||||||
// components that supports runtime reconfigurations.
|
// components that supports runtime reconfigurations.
|
||||||
type dynamicConfiguration struct {
|
type dynamicConfiguration struct {
|
||||||
logger *logger.Prm
|
|
||||||
pprof *httpComponent
|
pprof *httpComponent
|
||||||
metrics *httpComponent
|
metrics *httpComponent
|
||||||
}
|
}
|
||||||
|
@ -493,6 +518,7 @@ type cfg struct {
|
||||||
cfgNetmap cfgNetmap
|
cfgNetmap cfgNetmap
|
||||||
cfgControlService cfgControlService
|
cfgControlService cfgControlService
|
||||||
cfgObject cfgObject
|
cfgObject cfgObject
|
||||||
|
cfgQoSService cfgQoSService
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadCurrentNetMap reads network map which has been cached at the
|
// ReadCurrentNetMap reads network map which has been cached at the
|
||||||
|
@ -527,6 +553,8 @@ type cfgGRPC struct {
|
||||||
maxChunkSize uint64
|
maxChunkSize uint64
|
||||||
maxAddrAmount uint64
|
maxAddrAmount uint64
|
||||||
reconnectTimeout time.Duration
|
reconnectTimeout time.Duration
|
||||||
|
|
||||||
|
limiter atomic.Pointer[limiting.SemaphoreLimiter]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cfgGRPC) append(e string, l net.Listener, s *grpc.Server) {
|
func (c *cfgGRPC) append(e string, l net.Listener, s *grpc.Server) {
|
||||||
|
@ -591,8 +619,6 @@ type cfgMorph struct {
|
||||||
|
|
||||||
client *client.Client
|
client *client.Client
|
||||||
|
|
||||||
notaryEnabled bool
|
|
||||||
|
|
||||||
// TTL of Sidechain cached values. Non-positive value disables caching.
|
// TTL of Sidechain cached values. Non-positive value disables caching.
|
||||||
cacheTTL time.Duration
|
cacheTTL time.Duration
|
||||||
|
|
||||||
|
@ -611,6 +637,7 @@ type cfgContainer struct {
|
||||||
parsers map[event.Type]event.NotificationParser
|
parsers map[event.Type]event.NotificationParser
|
||||||
subscribers map[event.Type][]event.Handler
|
subscribers map[event.Type][]event.Handler
|
||||||
workerPool util.WorkerPool // pool for asynchronous handlers
|
workerPool util.WorkerPool // pool for asynchronous handlers
|
||||||
|
containerBatchSize uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type cfgFrostfsID struct {
|
type cfgFrostfsID struct {
|
||||||
|
@ -664,10 +691,6 @@ type cfgAccessPolicyEngine struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type cfgObjectRoutines struct {
|
type cfgObjectRoutines struct {
|
||||||
putRemote *ants.Pool
|
|
||||||
|
|
||||||
putLocal *ants.Pool
|
|
||||||
|
|
||||||
replication *ants.Pool
|
replication *ants.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,12 +727,6 @@ func initCfg(appCfg *config.Config) *cfg {
|
||||||
logPrm.SamplingHook = c.metricsCollector.LogMetrics().GetSamplingHook()
|
logPrm.SamplingHook = c.metricsCollector.LogMetrics().GetSamplingHook()
|
||||||
log, err := logger.NewLogger(logPrm)
|
log, err := logger.NewLogger(logPrm)
|
||||||
fatalOnErr(err)
|
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)
|
c.internals = initInternals(appCfg, log)
|
||||||
|
|
||||||
|
@ -853,14 +870,14 @@ func initFrostfsID(appCfg *config.Config) cfgFrostfsID {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initCfgGRPC() cfgGRPC {
|
func initCfgGRPC() (cfg cfgGRPC) {
|
||||||
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
maxChunkSize := uint64(maxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
||||||
maxAddrAmount := uint64(maxChunkSize) / addressSize // each address is about 72 bytes
|
maxAddrAmount := maxChunkSize / addressSize // each address is about 72 bytes
|
||||||
|
|
||||||
return cfgGRPC{
|
cfg.maxChunkSize = maxChunkSize
|
||||||
maxChunkSize: maxChunkSize,
|
cfg.maxAddrAmount = maxAddrAmount
|
||||||
maxAddrAmount: maxAddrAmount,
|
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func initCfgObject(appCfg *config.Config) cfgObject {
|
func initCfgObject(appCfg *config.Config) cfgObject {
|
||||||
|
@ -877,7 +894,6 @@ func (c *cfg) engineOpts() []engine.Option {
|
||||||
var opts []engine.Option
|
var opts []engine.Option
|
||||||
|
|
||||||
opts = append(opts,
|
opts = append(opts,
|
||||||
engine.WithShardPoolSize(c.EngineCfg.shardPoolSize),
|
|
||||||
engine.WithErrorThreshold(c.EngineCfg.errorThreshold),
|
engine.WithErrorThreshold(c.EngineCfg.errorThreshold),
|
||||||
engine.WithLogger(c.log),
|
engine.WithLogger(c.log),
|
||||||
engine.WithLowMemoryConsumption(c.EngineCfg.lowMem),
|
engine.WithLowMemoryConsumption(c.EngineCfg.lowMem),
|
||||||
|
@ -917,6 +933,7 @@ func (c *cfg) getWriteCacheOpts(shCfg shardCfg) []writecache.Option {
|
||||||
writecache.WithMaxCacheCount(wcRead.countLimit),
|
writecache.WithMaxCacheCount(wcRead.countLimit),
|
||||||
writecache.WithNoSync(wcRead.noSync),
|
writecache.WithNoSync(wcRead.noSync),
|
||||||
writecache.WithLogger(c.log),
|
writecache.WithLogger(c.log),
|
||||||
|
writecache.WithQoSLimiter(shCfg.limiter),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return writeCacheOpts
|
return writeCacheOpts
|
||||||
|
@ -1032,6 +1049,7 @@ func (c *cfg) getShardOpts(ctx context.Context, shCfg shardCfg) shardOptsWithID
|
||||||
}
|
}
|
||||||
if c.metricsCollector != nil {
|
if c.metricsCollector != nil {
|
||||||
mbOptions = append(mbOptions, meta.WithMetrics(lsmetrics.NewMetabaseMetrics(shCfg.metaCfg.path, c.metricsCollector.MetabaseMetrics())))
|
mbOptions = append(mbOptions, meta.WithMetrics(lsmetrics.NewMetabaseMetrics(shCfg.metaCfg.path, c.metricsCollector.MetabaseMetrics())))
|
||||||
|
shCfg.limiter.SetMetrics(c.metricsCollector.QoSMetrics())
|
||||||
}
|
}
|
||||||
|
|
||||||
var sh shardOptsWithID
|
var sh shardOptsWithID
|
||||||
|
@ -1056,30 +1074,28 @@ func (c *cfg) getShardOpts(ctx context.Context, shCfg shardCfg) shardOptsWithID
|
||||||
|
|
||||||
return pool
|
return pool
|
||||||
}),
|
}),
|
||||||
|
shard.WithLimiter(shCfg.limiter),
|
||||||
}
|
}
|
||||||
return sh
|
return sh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cfg) loggerPrm() (*logger.Prm, error) {
|
func (c *cfg) loggerPrm() (logger.Prm, error) {
|
||||||
// check if it has been inited before
|
var prm logger.Prm
|
||||||
if c.dynamicConfiguration.logger == nil {
|
|
||||||
c.dynamicConfiguration.logger = new(logger.Prm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// (re)init read configuration
|
// (re)init read configuration
|
||||||
err := c.dynamicConfiguration.logger.SetLevelString(c.LoggerCfg.level)
|
err := prm.SetLevelString(c.LoggerCfg.level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// not expected since validation should be performed before
|
// 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 {
|
if err != nil {
|
||||||
// not expected since validation should be performed before
|
// 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 {
|
func (c *cfg) LocalAddress() network.AddressGroup {
|
||||||
|
@ -1121,7 +1137,7 @@ func initLocalStorage(ctx context.Context, c *cfg) {
|
||||||
err := ls.Close(context.WithoutCancel(ctx))
|
err := ls.Close(context.WithoutCancel(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Info(ctx, logs.FrostFSNodeStorageEngineClosingFailure,
|
c.log.Info(ctx, logs.FrostFSNodeStorageEngineClosingFailure,
|
||||||
zap.String("error", err.Error()),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
c.log.Info(ctx, logs.FrostFSNodeAllComponentsOfTheStorageEngineClosedSuccessfully)
|
c.log.Info(ctx, logs.FrostFSNodeAllComponentsOfTheStorageEngineClosedSuccessfully)
|
||||||
|
@ -1148,7 +1164,7 @@ func initAccessPolicyEngine(ctx context.Context, c *cfg) {
|
||||||
c.cfgObject.cfgAccessPolicyEngine.policyContractHash)
|
c.cfgObject.cfgAccessPolicyEngine.policyContractHash)
|
||||||
|
|
||||||
cacheSize := morphconfig.APEChainCacheSize(c.appCfg)
|
cacheSize := morphconfig.APEChainCacheSize(c.appCfg)
|
||||||
if cacheSize > 0 {
|
if cacheSize > 0 && c.cfgMorph.cacheTTL > 0 {
|
||||||
morphRuleStorage = newMorphCache(morphRuleStorage, int(cacheSize), c.cfgMorph.cacheTTL)
|
morphRuleStorage = newMorphCache(morphRuleStorage, int(cacheSize), c.cfgMorph.cacheTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1167,21 +1183,7 @@ func initAccessPolicyEngine(ctx context.Context, c *cfg) {
|
||||||
func initObjectPool(cfg *config.Config) (pool cfgObjectRoutines) {
|
func initObjectPool(cfg *config.Config) (pool cfgObjectRoutines) {
|
||||||
var err error
|
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)
|
replicatorPoolSize := replicatorconfig.PoolSize(cfg)
|
||||||
if replicatorPoolSize <= 0 {
|
|
||||||
replicatorPoolSize = putRemoteCapacity
|
|
||||||
}
|
|
||||||
|
|
||||||
pool.replication, err = ants.NewPool(replicatorPoolSize)
|
pool.replication, err = ants.NewPool(replicatorPoolSize)
|
||||||
fatalOnErr(err)
|
fatalOnErr(err)
|
||||||
|
|
||||||
|
@ -1207,11 +1209,11 @@ func (c *cfg) setContractNodeInfo(ni *netmap.NodeInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cfg) updateContractNodeInfo(ctx context.Context, epoch uint64) {
|
func (c *cfg) updateContractNodeInfo(ctx context.Context, epoch uint64) {
|
||||||
ni, err := c.netmapLocalNodeState(epoch)
|
ni, err := c.netmapLocalNodeState(ctx, epoch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Error(ctx, logs.FrostFSNodeCouldNotUpdateNodeStateOnNewEpoch,
|
c.log.Error(ctx, logs.FrostFSNodeCouldNotUpdateNodeStateOnNewEpoch,
|
||||||
zap.Uint64("epoch", epoch),
|
zap.Uint64("epoch", epoch),
|
||||||
zap.String("error", err.Error()))
|
zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1221,9 +1223,9 @@ func (c *cfg) updateContractNodeInfo(ctx context.Context, epoch uint64) {
|
||||||
// bootstrapWithState calls "addPeer" method of the Sidechain Netmap contract
|
// bootstrapWithState calls "addPeer" method of the Sidechain Netmap contract
|
||||||
// with the binary-encoded information from the current node's configuration.
|
// with the binary-encoded information from the current node's configuration.
|
||||||
// The state is set using the provided setter which MUST NOT be nil.
|
// 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
|
ni := c.cfgNodeInfo.localInfo
|
||||||
stateSetter(&ni)
|
ni.SetStatus(state)
|
||||||
|
|
||||||
prm := nmClient.AddPeerPrm{}
|
prm := nmClient.AddPeerPrm{}
|
||||||
prm.SetNodeInfo(ni)
|
prm.SetNodeInfo(ni)
|
||||||
|
@ -1233,9 +1235,7 @@ func (c *cfg) bootstrapWithState(ctx context.Context, stateSetter func(*netmap.N
|
||||||
|
|
||||||
// bootstrapOnline calls cfg.bootstrapWithState with "online" state.
|
// bootstrapOnline calls cfg.bootstrapWithState with "online" state.
|
||||||
func bootstrapOnline(ctx context.Context, c *cfg) error {
|
func bootstrapOnline(ctx context.Context, c *cfg) error {
|
||||||
return c.bootstrapWithState(ctx, func(ni *netmap.NodeInfo) {
|
return c.bootstrapWithState(ctx, netmap.Online)
|
||||||
ni.SetStatus(netmap.Online)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// bootstrap calls bootstrapWithState with:
|
// bootstrap calls bootstrapWithState with:
|
||||||
|
@ -1246,9 +1246,7 @@ func (c *cfg) bootstrap(ctx context.Context) error {
|
||||||
st := c.cfgNetmap.state.controlNetmapStatus()
|
st := c.cfgNetmap.state.controlNetmapStatus()
|
||||||
if st == control.NetmapStatus_MAINTENANCE {
|
if st == control.NetmapStatus_MAINTENANCE {
|
||||||
c.log.Info(ctx, logs.FrostFSNodeBootstrappingWithTheMaintenanceState)
|
c.log.Info(ctx, logs.FrostFSNodeBootstrappingWithTheMaintenanceState)
|
||||||
return c.bootstrapWithState(ctx, func(ni *netmap.NodeInfo) {
|
return c.bootstrapWithState(ctx, netmap.Maintenance)
|
||||||
ni.SetStatus(netmap.Maintenance)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.log.Info(ctx, logs.FrostFSNodeBootstrappingWithOnlineState,
|
c.log.Info(ctx, logs.FrostFSNodeBootstrappingWithOnlineState,
|
||||||
|
@ -1337,15 +1335,7 @@ func (c *cfg) reloadConfig(ctx context.Context) {
|
||||||
// all the components are expected to support
|
// all the components are expected to support
|
||||||
// Logger's dynamic reconfiguration approach
|
// Logger's dynamic reconfiguration approach
|
||||||
|
|
||||||
// Logger
|
components := c.getComponents(ctx)
|
||||||
|
|
||||||
logPrm, err := c.loggerPrm()
|
|
||||||
if err != nil {
|
|
||||||
c.log.Error(ctx, logs.FrostFSNodeLoggerConfigurationPreparation, zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
components := c.getComponents(ctx, logPrm)
|
|
||||||
|
|
||||||
// Object
|
// Object
|
||||||
c.cfgObject.tombstoneLifetime.Store(c.ObjectCfg.tombstoneLifetime)
|
c.cfgObject.tombstoneLifetime.Store(c.ObjectCfg.tombstoneLifetime)
|
||||||
|
@ -1383,10 +1373,17 @@ func (c *cfg) reloadConfig(ctx context.Context) {
|
||||||
c.log.Info(ctx, logs.FrostFSNodeConfigurationHasBeenReloadedSuccessfully)
|
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
|
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 {
|
components = append(components, dCmp{"runtime", func() error {
|
||||||
setRuntimeParameters(ctx, c)
|
setRuntimeParameters(ctx, c)
|
||||||
return nil
|
return nil
|
||||||
|
@ -1419,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{cmp.name, func() error { return cmp.reload(ctx) }})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
components = append(components, dCmp{"rpc_limiter", func() error { return initRPCLimiter(c) }})
|
||||||
|
|
||||||
return components
|
return components
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cfg) reloadPools() error {
|
func (c *cfg) reloadPools() error {
|
||||||
newSize := objectconfig.Put(c.appCfg).PoolSizeLocal()
|
newSize := replicatorconfig.PoolSize(c.appCfg)
|
||||||
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)
|
|
||||||
c.reloadPool(c.cfgObject.pool.replication, newSize, "replicator.pool_size")
|
c.reloadPool(c.cfgObject.pool.replication, newSize, "replicator.pool_size")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1466,7 +1459,7 @@ func (c *cfg) createTombstoneSource() *tombstone.ExpirationChecker {
|
||||||
func (c *cfg) createContainerInfoProvider(ctx context.Context) container.InfoProvider {
|
func (c *cfg) createContainerInfoProvider(ctx context.Context) container.InfoProvider {
|
||||||
return container.NewInfoProvider(func() (container.Source, error) {
|
return container.NewInfoProvider(func() (container.Source, error) {
|
||||||
c.initMorphComponents(ctx)
|
c.initMorphComponents(ctx)
|
||||||
cc, err := containerClient.NewFromMorph(c.cfgMorph.client, c.cfgContainer.scriptHash, 0, containerClient.TryNotary())
|
cc, err := containerClient.NewFromMorph(c.cfgMorph.client, c.cfgContainer.scriptHash, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
|
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
|
// It supports only one level of nesting and is intended to be used
|
||||||
// to provide default values.
|
// to provide default values.
|
||||||
func (x *Config) SetDefault(from *Config) {
|
func (x *Config) SetDefault(from *Config) {
|
||||||
x.defaultPath = make([]string, len(from.path))
|
x.defaultPath = slices.Clone(from.path)
|
||||||
copy(x.defaultPath, from.path)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,10 @@ import (
|
||||||
func TestConfigDir(t *testing.T) {
|
func TestConfigDir(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
cfgFileName0 := path.Join(dir, "cfg_00.json")
|
cfgFileName := path.Join(dir, "cfg_01.yml")
|
||||||
cfgFileName1 := path.Join(dir, "cfg_01.yml")
|
|
||||||
|
|
||||||
require.NoError(t, os.WriteFile(cfgFileName0, []byte(`{"storage":{"shard_pool_size":15}}`), 0o777))
|
require.NoError(t, os.WriteFile(cfgFileName, []byte("logger:\n level: debug"), 0o777))
|
||||||
require.NoError(t, os.WriteFile(cfgFileName1, []byte("logger:\n level: debug"), 0o777))
|
|
||||||
|
|
||||||
c := New("", dir, "")
|
c := New("", dir, "")
|
||||||
require.Equal(t, "debug", cast.ToString(c.Sub("logger").Value("level")))
|
require.Equal(t, "debug", cast.ToString(c.Sub("logger").Value("level")))
|
||||||
require.EqualValues(t, 15, cast.ToUint32(c.Sub("storage").Value("shard_pool_size")))
|
|
||||||
}
|
}
|
||||||
|
|
27
cmd/frostfs-node/config/container/container.go
Normal file
27
cmd/frostfs-node/config/container/container.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package containerconfig
|
||||||
|
|
||||||
|
import "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||||
|
|
||||||
|
const (
|
||||||
|
subsection = "container"
|
||||||
|
listStreamSubsection = "list_stream"
|
||||||
|
|
||||||
|
// ContainerBatchSizeDefault represents the maximum amount of containers to send via stream at once.
|
||||||
|
ContainerBatchSizeDefault = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContainerBatchSize returns the value of "batch_size" config parameter
|
||||||
|
// from "list_stream" subsection of "container" section.
|
||||||
|
//
|
||||||
|
// Returns ContainerBatchSizeDefault if the value is missing or if
|
||||||
|
// the value is not positive integer.
|
||||||
|
func ContainerBatchSize(c *config.Config) uint32 {
|
||||||
|
if c.Sub(subsection).Sub(listStreamSubsection).Value("batch_size") == nil {
|
||||||
|
return ContainerBatchSizeDefault
|
||||||
|
}
|
||||||
|
size := config.Uint32Safe(c.Sub(subsection).Sub(listStreamSubsection), "batch_size")
|
||||||
|
if size == 0 {
|
||||||
|
return ContainerBatchSizeDefault
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue