forked from TrueCloudLab/frostfs-node
Compare commits
34 commits
master
...
support/v0
Author | SHA1 | Date | |
---|---|---|---|
f152f5d553 | |||
2ea3be33c1 | |||
de36cb9a7d | |||
14886ae7a4 | |||
1cae03c47c | |||
0252875aec | |||
25bedab91a | |||
405e17b2ec | |||
a7dab2a22b | |||
8aa5907e1b | |||
5e2fcec60f | |||
139ded93e1 | |||
2360cf263b | |||
f866ec1399 | |||
a506da97d6 | |||
1dd84eef77 | |||
1501f11e4d | |||
4f55417914 | |||
9bda6e0b8b | |||
ceb9deb7f1 | |||
4148590668 | |||
493cafc62a | |||
3711976dfc | |||
|
c3f5045842 | ||
|
ab65063d6d | ||
|
c60029d3b0 | ||
|
0beb7ccf5c | ||
0fe5e34fb0 | |||
bcf3f0f517 | |||
79d59e4ed2 | |||
364b4ac572 | |||
f7679a8168 | |||
2dc2fe8780 | |||
21412ef24a |
898 changed files with 28184 additions and 25378 deletions
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.19 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21
|
||||
FROM golang:1.19
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.19 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.19 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.19 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
|
|
19
.docker/Dockerfile.storage-testnet
Normal file
19
.docker/Dockerfile.storage-testnet
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM golang:1.19 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
|
||||
COPY --from=builder /src/config/testnet/config.yml /config.yml
|
||||
|
||||
CMD ["frostfs-node", "--config", "/config.yml"]
|
|
@ -1,41 +0,0 @@
|
|||
name: Build
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Components
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.20', '1.21' ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# Allows to fetch all history for all branches and tags.
|
||||
# Need this for proper versioning.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
|
||||
- name: Build CLI
|
||||
run: make bin/frostfs-cli
|
||||
- run: bin/frostfs-cli --version
|
||||
|
||||
- name: Build NODE
|
||||
run: make bin/frostfs-node
|
||||
|
||||
- name: Build IR
|
||||
run: make bin/frostfs-ir
|
||||
|
||||
- name: Build ADM
|
||||
run: make bin/frostfs-adm
|
||||
- run: bin/frostfs-adm --version
|
||||
|
||||
- name: Build LENS
|
||||
run: make bin/frostfs-lens
|
||||
- run: bin/frostfs-lens --version
|
|
@ -1,21 +0,0 @@
|
|||
name: DCO action
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
dco:
|
||||
name: DCO
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Run commit format checker
|
||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v2
|
||||
with:
|
||||
from: 'origin/${{ github.event.pull_request.base.ref }}'
|
|
@ -1,73 +0,0 @@
|
|||
name: Tests and linters
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install linters
|
||||
run: make lint-install
|
||||
|
||||
- name: Run linters
|
||||
run: make lint
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.20', '1.21' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
tests-race:
|
||||
name: Tests with -race
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./... -count=1 -race
|
||||
|
||||
staticcheck:
|
||||
name: Staticcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install staticcheck
|
||||
run: make staticcheck-install
|
||||
|
||||
- name: Run staticcheck
|
||||
run: make staticcheck-run
|
|
@ -1,22 +0,0 @@
|
|||
name: Vulncheck
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
vulncheck:
|
||||
name: Vulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
- name: Run govulncheck
|
||||
run: govulncheck ./...
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,3 +1,2 @@
|
|||
/**/*.pb.go -diff -merge
|
||||
/**/*.pb.go linguist-generated=true
|
||||
/go.sum -diff
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# options for analysis running
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 20m
|
||||
timeout: 10m
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: false
|
||||
|
@ -31,21 +31,6 @@ linters-settings:
|
|||
statements: 60 # default 40
|
||||
gocognit:
|
||||
min-complexity: 40 # default 30
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||
alias: objectSDK
|
||||
custom:
|
||||
truecloudlab-linters:
|
||||
path: bin/external_linters.so
|
||||
original-url: git.frostfs.info/TrueCloudLab/linters.git
|
||||
settings:
|
||||
noliteral:
|
||||
target-methods : ["reportFlushError", "reportError"]
|
||||
disable-packages: ["codes", "err", "res","exec"]
|
||||
constants-package: "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
|
@ -77,7 +62,5 @@ linters:
|
|||
- funlen
|
||||
- gocognit
|
||||
- contextcheck
|
||||
- importas
|
||||
- truecloudlab-linters
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
|
|
@ -26,17 +26,14 @@ repos:
|
|||
exclude: ".key$"
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.9.0.5
|
||||
rev: v0.9.0.2
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
- repo: local
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.51.2
|
||||
hooks:
|
||||
- id: make-lint
|
||||
name: Run Make Lint
|
||||
entry: make lint
|
||||
language: system
|
||||
pass_filenames: false
|
||||
- id: golangci-lint
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
|
@ -51,4 +48,3 @@ repos:
|
|||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-staticcheck-repo-mod
|
||||
- id: go-mod-tidy
|
||||
|
|
50
CHANGELOG.md
50
CHANGELOG.md
|
@ -4,49 +4,13 @@ Changelog for FrostFS Node
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Support impersonate bearer token (#229)
|
||||
- Change log level on SIGHUP for ir (#125)
|
||||
- Reload pprof and metrics on SIGHUP for ir (#125)
|
||||
- Support copies number parameter in `frostfs-cli object put` (#351)
|
||||
- Set extra wallets on SIGHUP for ir (#125)
|
||||
- Writecache metrics (#312)
|
||||
- Add tree service metrics (#370)
|
||||
|
||||
### Changed
|
||||
- `frostfs-cli util locode generate` is now much faster (#309)
|
||||
### Fixed
|
||||
- Take network settings into account during netmap contract update (#100)
|
||||
- Read config files from dir even if config file not provided via `--config` for node (#238)
|
||||
- Notary requests parsing according to `neo-go`'s updates (#268)
|
||||
- Tree service panic in its internal client cache (#322)
|
||||
- Iterate over endpoints when create ws client in morph's constructor (#304)
|
||||
- Delete complex objects with GC (#332)
|
||||
|
||||
### Removed
|
||||
### Updated
|
||||
- `neo-go` to `v0.101.1`
|
||||
- `google.golang.org/grpc` to `v1.55.0`
|
||||
- `paulmach/orb` to `v0.9.2`
|
||||
- `go.etcd.io/bbolt` to `v1.3.7`
|
||||
- `github.com/nats-io/nats.go` to `v1.25.0`
|
||||
- `golang.org/x/sync` to `v0.2.0`
|
||||
- `golang.org/x/term` to `v0.8.0`
|
||||
- `github.com/spf13/cobra` to `v1.7.0`
|
||||
- `github.com/panjf2000/ants/v2` `v2.7.4`
|
||||
- `github.com/multiformats/go-multiaddr` to `v0.9.0`
|
||||
- `github.com/hashicorp/golang-lru/v2` to `v2.0.2`
|
||||
- `go.uber.org/atomic` to `v1.11.0`
|
||||
- Minimum go version to v1.20
|
||||
- `github.com/prometheus/client_golang` to `v1.15.1`
|
||||
- `github.com/prometheus/client_model` to `v0.4.0`
|
||||
- `go.opentelemetry.io/otel` to `v1.15.1`
|
||||
- `go.opentelemetry.io/otel/trace` to `v1.15.1`
|
||||
- `github.com/spf13/cast` to `v1.5.1`
|
||||
- `git.frostfs.info/TrueCloudLab/hrw` to `v1.2.1`
|
||||
|
||||
### Updating from v0.36.0
|
||||
|
||||
## [v0.36.0] - 2023-04-12 - Furtwängler
|
||||
## [v0.36.0] - Karpinsky - 2023-07-12
|
||||
|
||||
### Added
|
||||
- Add GAS pouring mechanism for a configurable list of wallets (#128)
|
||||
|
@ -61,7 +25,7 @@ Changelog for FrostFS Node
|
|||
- Parameters `nns-name` and `nns-zone` for command `frostfs-cli container create` (#37)
|
||||
- Tree service now saves the last synchronization height which persists across restarts (#82)
|
||||
- Add tracing support (#135)
|
||||
- Multiple (and a fix for single) copies number support for `PUT` requests (#221)
|
||||
- Support copies number parameter in `frostfs-cli object put` (#351)
|
||||
|
||||
### Changed
|
||||
- Change `frostfs_node_engine_container_size` to counting sizes of logical objects
|
||||
|
@ -74,6 +38,8 @@ Changelog for FrostFS Node
|
|||
- Expired locked object is available for reading (#56)
|
||||
- Initialize write-cache asynchronously (#32)
|
||||
- Update system attribute names (#159)
|
||||
- Flush objects from the write-cache immediately (#314)
|
||||
- Do not accept requests to the tree service until the first sync cycle is finished (#266)
|
||||
|
||||
### Fixed
|
||||
- Increase payload size metric on shards' `put` operation (#1794)
|
||||
|
@ -101,9 +67,9 @@ Changelog for FrostFS Node
|
|||
- Iterating over just removed files by FSTree (#98)
|
||||
- Parts of a locked object could not be removed anymore (#141)
|
||||
- Non-alphabet nodes do not try to handle alphabet events (#181)
|
||||
- Failing SN and IR transactions because of incorrect scopes (#2230, #2263)
|
||||
- Global scope used for some transactions (#2230, #2263)
|
||||
- Concurrent morph cache misses (#30)
|
||||
- Delete complex objects with GC (#332)
|
||||
- Copy number was not used for `PUT` requests (#284)
|
||||
- Tree service panic in its internal client cache (#323)
|
||||
|
||||
### Removed
|
||||
### Updated
|
||||
|
@ -115,7 +81,7 @@ Changelog for FrostFS Node
|
|||
- `github.com/spf13/viper` to `v1.15.0`
|
||||
- `github.com/nats-io/nats.go` to `v1.22.1`
|
||||
- `github.com/TrueCloudLab/hrw` to `v.1.1.1`
|
||||
- Minimum go version to v1.18
|
||||
- Minimum go version to v1.19
|
||||
|
||||
### Updating from v0.35.0 (old NeoFS)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
First, thank you for contributing! We love and encourage pull requests from
|
||||
everyone. Please follow the guidelines:
|
||||
|
||||
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues) and
|
||||
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls) for existing
|
||||
- Check the open [issues](https://github.com/TrueCloudLab/frostfs-node/issues) and
|
||||
[pull requests](https://github.com/TrueCloudLab/frostfs-node/pulls) for existing
|
||||
discussions.
|
||||
|
||||
- Open an issue first, to discuss a new feature or enhancement.
|
||||
|
@ -27,19 +27,19 @@ Start by forking the `frostfs-node` repository, make changes in a branch and the
|
|||
send a pull request. We encourage pull requests to discuss code changes. Here
|
||||
are the steps in details:
|
||||
|
||||
### Set up your Forgejo repository
|
||||
Fork [FrostFS node upstream](https://git.frostfs.info/TrueCloudLab/frostfs-node) source
|
||||
### Set up your GitHub Repository
|
||||
Fork [FrostFS node upstream](https://github.com/TrueCloudLab/frostfs-node/fork) source
|
||||
repository to your own personal repository. Copy the URL of your fork (you will
|
||||
need it for the `git clone` command below).
|
||||
|
||||
```sh
|
||||
$ git clone https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||
$ git clone https://github.com/TrueCloudLab/frostfs-node
|
||||
```
|
||||
|
||||
### Set up git remote as ``upstream``
|
||||
```sh
|
||||
$ cd frostfs-node
|
||||
$ git remote add upstream https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||
$ git remote add upstream https://github.com/TrueCloudLab/frostfs-node
|
||||
$ git fetch upstream
|
||||
$ git merge upstream/master
|
||||
...
|
||||
|
@ -58,7 +58,7 @@ $ git checkout -b feature/123-something_awesome
|
|||
After your code changes, make sure
|
||||
|
||||
- To add test cases for the new code.
|
||||
- To run `make lint` and `make staticcheck-run`
|
||||
- To run `make lint`
|
||||
- To squash your commits into a single commit or a series of logically separated
|
||||
commits run `git rebase -i`. It's okay to force update your pull request.
|
||||
- To run `make test` and `make all` completes.
|
||||
|
@ -89,8 +89,8 @@ $ git push origin feature/123-something_awesome
|
|||
```
|
||||
|
||||
### Create a Pull Request
|
||||
Pull requests can be created via Forgejo. Refer to [this
|
||||
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for
|
||||
Pull requests can be created via GitHub. Refer to [this
|
||||
document](https://help.github.com/articles/creating-a-pull-request/) for
|
||||
detailed steps on how to create a pull request. After a Pull Request gets peer
|
||||
reviewed and approved, it will be merged.
|
||||
|
||||
|
|
35
Makefile
35
Makefile
|
@ -7,9 +7,8 @@ VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8
|
|||
HUB_IMAGE ?= truecloudlab/frostfs
|
||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||
|
||||
GO_VERSION ?= 1.21
|
||||
LINT_VERSION ?= 1.54.0
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.2
|
||||
GO_VERSION ?= 1.19
|
||||
LINT_VERSION ?= 1.50.0
|
||||
ARCH = amd64
|
||||
|
||||
BIN = bin
|
||||
|
@ -26,10 +25,6 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
|||
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
|
||||
sed "s/-/~/")-${OS_RELEASE}
|
||||
|
||||
OUTPUT_LINT_DIR ?= $(shell pwd)/bin
|
||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||
TMP_DIR := .cache
|
||||
|
||||
.PHONY: help all images dep clean fmts fmt imports test lint docker/lint
|
||||
prepare-release debpackage pre-commit unpre-commit
|
||||
|
||||
|
@ -100,7 +95,7 @@ image-%:
|
|||
-t $(HUB_IMAGE)-$*:$(HUB_TAG) .
|
||||
|
||||
# Build all Docker images
|
||||
images: image-storage image-ir image-cli image-adm
|
||||
images: image-storage image-ir image-cli image-adm image-storage-testnet
|
||||
|
||||
# Build dirty local Docker images
|
||||
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
||||
|
@ -131,35 +126,17 @@ imports:
|
|||
# Run Unit Test with go test
|
||||
test:
|
||||
@echo "⇒ Running go test"
|
||||
@go test ./... -count=1
|
||||
@go test ./...
|
||||
|
||||
pre-commit-run:
|
||||
@pre-commit run -a --hook-stage manual
|
||||
|
||||
# Install linters
|
||||
lint-install:
|
||||
@mkdir -p $(TMP_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
@if [ ! -d "$(LINT_DIR)" ]; then \
|
||||
echo "Run make lint-install"; \
|
||||
exit 1; \
|
||||
fi
|
||||
$(LINT_DIR)/golangci-lint run
|
||||
|
||||
# Install staticcheck
|
||||
staticcheck-install:
|
||||
@go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
@golangci-lint --timeout=5m run
|
||||
|
||||
# Run staticcheck
|
||||
staticcheck-run:
|
||||
staticcheck:
|
||||
@staticcheck ./...
|
||||
|
||||
# Run linters in Docker
|
||||
|
|
|
@ -49,7 +49,7 @@ The latest version of frostfs-node works with frostfs-contract
|
|||
|
||||
# Building
|
||||
|
||||
To make all binaries you need Go 1.20+ and `make`:
|
||||
To make all binaries you need Go 1.19+ and `make`:
|
||||
```
|
||||
make all
|
||||
```
|
||||
|
|
|
@ -36,7 +36,9 @@ alphabet-wallets: /path # path to consensus node / alphabet wallets s
|
|||
network:
|
||||
max_object_size: 67108864 # max size of a single FrostFS object, bytes
|
||||
epoch_duration: 240 # duration of a FrostFS epoch in blocks, consider block generation frequency in the sidechain
|
||||
basic_income_rate: 0 # basic income rate, for private consider 0
|
||||
fee:
|
||||
audit: 0 # network audit fee, for private installation consider 0
|
||||
candidate: 0 # inner ring candidate registration fee, for private installation consider 0
|
||||
container: 0 # container creation fee, for private installation consider 0
|
||||
container_alias: 0 # container nice-name registration fee, for private installation consider 0
|
||||
|
|
|
@ -18,7 +18,6 @@ To start a network, you need a set of consensus nodes, the same number of
|
|||
Alphabet nodes and any number of Storage nodes. While the number of Storage
|
||||
nodes can be scaled almost infinitely, the number of consensus and Alphabet
|
||||
nodes can't be changed so easily right now. Consider this before going any further.
|
||||
Note also that there is an upper limit on the number of alphabet nodes (currently 22).
|
||||
|
||||
It is easier to use`frostfs-adm` with a predefined configuration. First, create
|
||||
a network configuration file. In this example, there is going to be only one
|
||||
|
@ -34,7 +33,9 @@ alphabet-wallets: /home/user/deploy/alphabet-wallets
|
|||
network:
|
||||
max_object_size: 67108864
|
||||
epoch_duration: 240
|
||||
basic_income_rate: 0
|
||||
fee:
|
||||
audit: 0
|
||||
candidate: 0
|
||||
container: 0
|
||||
withdraw: 0
|
||||
|
@ -140,11 +141,13 @@ Waiting for transactions to persist...
|
|||
Stage 7: set addresses in NNS.
|
||||
Waiting for transactions to persist...
|
||||
NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
|
||||
NNS: Set audit.frostfs -> 7df847b993affb3852074345a7c2bd622171ee0d
|
||||
NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879
|
||||
NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846
|
||||
NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2
|
||||
NNS: Set netmap.frostfs -> 894eb854632f50fb124412ce7951ebc00763525e
|
||||
NNS: Set proxy.frostfs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d
|
||||
NNS: Set reputation.frostfs -> 6eda57c9d93d990573646762d1fea327ce41191f
|
||||
Waiting for transactions to persist...
|
||||
```
|
||||
|
||||
|
|
39
cmd/frostfs-adm/docs/subnetwork-creation.md
Normal file
39
cmd/frostfs-adm/docs/subnetwork-creation.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# FrostFS subnetwork creation
|
||||
|
||||
This is a short guide on how to create FrostFS subnetworks. This guide
|
||||
considers that the sidechain and the inner ring (alphabet nodes) have already been
|
||||
deployed and the sidechain contains a deployed `subnet` contract.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To follow this guide, you need:
|
||||
- neo-go sidechain RPC endpoint;
|
||||
- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases);
|
||||
- wallet with FrostFS account.
|
||||
|
||||
## Creation
|
||||
|
||||
```shell
|
||||
$ frostfs-adm morph subnet create \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/owner/wallet> \
|
||||
--notary
|
||||
Create subnet request sent successfully. ID: 4223489767.
|
||||
```
|
||||
|
||||
**NOTE:** in notary-enabled environment you should have a sufficient
|
||||
notary deposit (not expired, with enough GAS balance). Your subnet ID
|
||||
will differ from the example.
|
||||
|
||||
The default account in the wallet that has been passed with `-w` flag is the owner
|
||||
of the just created subnetwork.
|
||||
|
||||
You can check if your subnetwork was created successfully:
|
||||
|
||||
```shell
|
||||
$ frostfs-adm morph subnet get \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
--subnet <subnet_ID>
|
||||
Owner: NUc734PMJXiqa2J9jRtvskU3kCdyyuSN8Q
|
||||
```
|
||||
Your owner will differ from the example.
|
137
cmd/frostfs-adm/docs/subnetwork-usage.md
Normal file
137
cmd/frostfs-adm/docs/subnetwork-usage.md
Normal file
|
@ -0,0 +1,137 @@
|
|||
# Managing Subnetworks
|
||||
|
||||
This is a short guide on how to manage FrostFS subnetworks. This guide
|
||||
considers that the sidechain and the inner ring (alphabet nodes) have already been
|
||||
deployed, and the sidechain contains a deployed `subnet` contract.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- neo-go sidechain RPC endpoint;
|
||||
- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases);
|
||||
- [created](subnetwork-creation.md) subnetwork;
|
||||
- wallet with the account that owns the subnetwork;
|
||||
- public key of the Storage Node;
|
||||
- public keys of the node and client administrators;
|
||||
- owner IDs of the FrostFS users.
|
||||
|
||||
## Add node administrator
|
||||
|
||||
Node administrators are accounts that can manage (add and delete nodes)
|
||||
the whitelist of the nodes which can be included to a subnetwork. Only the subnet
|
||||
owner is allowed to add and remove node administrators from the subnetwork.
|
||||
|
||||
```shell
|
||||
$ frostfs-adm morph subnet admin add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/owner/wallet> \
|
||||
--admin <HEX_admin_public_key> \
|
||||
--subnet <subnet_ID>
|
||||
Add admin request sent successfully.
|
||||
```
|
||||
|
||||
## Add node
|
||||
|
||||
Adding a node to a subnetwork means that the node becomes able to service
|
||||
containers that have been created in that subnetwork. Addition only changes
|
||||
the list of the allowed nodes. Node is not required to be bootstrapped at the
|
||||
moment of its inclusion.
|
||||
|
||||
```shell
|
||||
$ frostfs-adm morph subnet node add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/node_admin/wallet> \
|
||||
--node <HEX_node_public_key> \
|
||||
--subnet <subnet_ID>
|
||||
Add node request sent successfully.
|
||||
```
|
||||
|
||||
**NOTE:** the owner of the subnetwork is also allowed to add nodes.
|
||||
|
||||
## Add client administrator
|
||||
|
||||
Client administrators are accounts that can manage (add and delete
|
||||
nodes) the whitelist of the clients that can create containers in the
|
||||
subnetwork. Only the subnet owner is allowed to add and remove client
|
||||
administrators from the subnetwork.
|
||||
|
||||
```shell
|
||||
$ frostfs-adm morph subnet admin add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/owner/wallet> \
|
||||
--admin <HEX_admin_public_key> \
|
||||
--subnet <subnet_ID> \
|
||||
--client \
|
||||
--group <group_ID>
|
||||
Add admin request sent successfully.
|
||||
```
|
||||
|
||||
**NOTE:** you do not need to create a group explicitly, it will be created
|
||||
right after the first client admin is added. Group ID is a 4-byte
|
||||
positive integer number.
|
||||
|
||||
## Add client
|
||||
|
||||
```shell
|
||||
$ frostfs-adm morph subnet client add \
|
||||
-r <side_chain_RPC_endpoint> \
|
||||
-w </path/to/client_admin/wallet> \
|
||||
--client <client_ownerID> \
|
||||
--subnet <subnet_ID> \
|
||||
--group <group_ID>
|
||||
Add client request sent successfully.
|
||||
```
|
||||
|
||||
**NOTE:** the owner of the subnetwork is also allowed to add clients. This is
|
||||
the only one command that accepts `ownerID`, not the public key.
|
||||
Administrator can manage only their group (a group where that administrator
|
||||
has been added by the subnet owner).
|
||||
|
||||
# Bootstrapping Storage Node
|
||||
|
||||
After a subnetwork [is created](subnetwork-creation.md) and a node is included into it, the
|
||||
node could be bootstrapped and service subnetwork containers.
|
||||
|
||||
For bootstrapping, you need to specify the ID of the subnetwork in the node's
|
||||
configuration:
|
||||
|
||||
```yaml
|
||||
...
|
||||
node:
|
||||
...
|
||||
subnet:
|
||||
entries: # list of IDs of subnets to enter in a text format of FrostFS API protocol (overrides corresponding attributes)
|
||||
- <subnetwork_ID>
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
**NOTE:** specifying subnetwork that is denied for the node is not an error:
|
||||
that configuration value would be ignored. You do not need to specify zero
|
||||
(with 0 ID) subnetwork: its inclusion is implicit. On the contrary, to exclude
|
||||
a node from the default zero subnetwork, you need to specify it explicitly:
|
||||
|
||||
```yaml
|
||||
...
|
||||
node:
|
||||
...
|
||||
subnet:
|
||||
exit_zero: true # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in `entries`)
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
# Creating container in non-zero subnetwork
|
||||
|
||||
Creating containers without using `--subnet` flag is equivalent to
|
||||
creating container in the zero subnetwork.
|
||||
|
||||
To create a container in a private network, your wallet must be added to
|
||||
the client whitelist by the client admins or the subnet owners:
|
||||
|
||||
```shell
|
||||
$ frostfs-cli container create \
|
||||
--policy 'REP 1' \
|
||||
-w </path/to/wallet> \
|
||||
-r s01.frostfs.devenv:8080 \
|
||||
--subnet <subnet_ID>
|
||||
```
|
|
@ -18,6 +18,8 @@ type configTemplate struct {
|
|||
AlphabetDir string
|
||||
MaxObjectSize int
|
||||
EpochDuration int
|
||||
BasicIncomeRate int
|
||||
AuditFee int
|
||||
CandidateFee int
|
||||
ContainerFee int
|
||||
ContainerAliasFee int
|
||||
|
@ -31,8 +33,10 @@ alphabet-wallets: {{ .AlphabetDir}}
|
|||
network:
|
||||
max_object_size: {{ .MaxObjectSize}}
|
||||
epoch_duration: {{ .EpochDuration}}
|
||||
basic_income_rate: {{ .BasicIncomeRate}}
|
||||
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
|
||||
fee:
|
||||
audit: {{ .AuditFee}}
|
||||
candidate: {{ .CandidateFee}}
|
||||
container: {{ .ContainerFee}}
|
||||
container_alias: {{ .ContainerAliasFee }}
|
||||
|
@ -43,7 +47,7 @@ credentials:
|
|||
{{.}}: password{{end}}
|
||||
`
|
||||
|
||||
func initConfig(cmd *cobra.Command, _ []string) error {
|
||||
func initConfig(cmd *cobra.Command, args []string) error {
|
||||
configPath, err := readConfigPathFromArgs(cmd)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -107,7 +111,9 @@ func generateConfigExample(appDir string, credSize int) (string, error) {
|
|||
Endpoint: "https://neo.rpc.node:30333",
|
||||
MaxObjectSize: 67108864, // 64 MiB
|
||||
EpochDuration: 240, // 1 hour with 15s per block
|
||||
BasicIncomeRate: 1_0000_0000, // 0.0001 GAS per GiB (Fixed12)
|
||||
HomomorphicHashDisabled: false, // object homomorphic hash is enabled
|
||||
AuditFee: 1_0000, // 0.00000001 GAS per audit (Fixed12)
|
||||
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
|
||||
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
|
||||
ContainerAliasFee: 500, // ContainerFee / 2
|
||||
|
|
|
@ -28,6 +28,8 @@ func TestGenerateConfigExample(t *testing.T) {
|
|||
require.Equal(t, filepath.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets"))
|
||||
require.Equal(t, 67108864, v.GetInt("network.max_object_size"))
|
||||
require.Equal(t, 240, v.GetInt("network.epoch_duration"))
|
||||
require.Equal(t, 100000000, v.GetInt("network.basic_income_rate"))
|
||||
require.Equal(t, 10000, v.GetInt("network.fee.audit"))
|
||||
require.Equal(t, 10000000000, v.GetInt("network.fee.candidate"))
|
||||
require.Equal(t, 1000, v.GetInt("network.fee.container"))
|
||||
require.Equal(t, 100000000, v.GetInt("network.fee.withdraw"))
|
||||
|
|
|
@ -37,6 +37,11 @@ const (
|
|||
dumpBalancesAlphabetFlag = "alphabet"
|
||||
dumpBalancesProxyFlag = "proxy"
|
||||
dumpBalancesUseScriptHashFlag = "script-hash"
|
||||
|
||||
// notaryEnabled signifies whether contracts were deployed in a notary-enabled environment.
|
||||
// The setting is here to simplify testing and building the command for testnet (notary currently disabled).
|
||||
// It will be removed eventually.
|
||||
notaryEnabled = true
|
||||
)
|
||||
|
||||
func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||
|
@ -55,7 +60,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if dumpStorage || dumpAlphabet || dumpProxy {
|
||||
if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy {
|
||||
nnsCs, err = c.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
|
@ -67,7 +72,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
irList, err := fetchIRNodes(c, rolemgmt.Hash)
|
||||
irList, err := fetchIRNodes(c, nmHash, rolemgmt.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -182,22 +187,40 @@ func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.In
|
|||
return nil
|
||||
}
|
||||
|
||||
func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
var irList []accBalancePair
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
if notaryEnabled {
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
|
||||
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
irList := make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||
irList = make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||
}
|
||||
} else {
|
||||
arr, err := unwrap.ArrayOfBytes(inv.Call(nmHash, "innerRingList"))
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
irList = make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
pub, err := keys.NewPublicKeyFromBytes(arr[i], elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse IR node public key: %w", err)
|
||||
}
|
||||
irList[i].scriptHash = pub.GetScriptHash()
|
||||
}
|
||||
}
|
||||
return irList, nil
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ import (
|
|||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"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/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -48,24 +48,41 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
|
|||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
m, err := parseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
switch k {
|
||||
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
|
||||
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
|
||||
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
v, err := tuple[1].TryBytes()
|
||||
if err != nil {
|
||||
return invalidConfigValueErr(k)
|
||||
}
|
||||
|
||||
switch string(k) {
|
||||
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
|
||||
netmapContainerFeeKey, netmapContainerAliasFeeKey,
|
||||
netmapEigenTrustIterationsKey,
|
||||
netmapEpochKey, netmapInnerRingCandidateFeeKey,
|
||||
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
|
||||
nbuf := make([]byte, 8)
|
||||
copy(nbuf[:], v)
|
||||
n := binary.LittleEndian.Uint64(nbuf)
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
|
||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||
if len(v) == 0 || len(v) > 1 {
|
||||
case netmapEigenTrustAlphaKey:
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (str)\n", k, v)))
|
||||
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
|
||||
vBool, err := tuple[1].TryBool()
|
||||
if err != nil {
|
||||
return invalidConfigValueErr(k)
|
||||
}
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, v[0] == 1)))
|
||||
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%t (bool)\n", k, vBool)))
|
||||
default:
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v))))
|
||||
}
|
||||
|
@ -133,14 +150,25 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
|
|||
valRaw := v
|
||||
|
||||
switch key {
|
||||
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig,
|
||||
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig,
|
||||
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig:
|
||||
case netmapAuditFeeKey, netmapBasicIncomeRateKey,
|
||||
netmapContainerFeeKey, netmapContainerAliasFeeKey,
|
||||
netmapEigenTrustIterationsKey,
|
||||
netmapEpochKey, netmapInnerRingCandidateFeeKey,
|
||||
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
|
||||
val, err = strconv.ParseInt(valRaw, 10, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err)
|
||||
}
|
||||
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig:
|
||||
case netmapEigenTrustAlphaKey:
|
||||
// just check that it could
|
||||
// be parsed correctly
|
||||
_, err = strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as float: %w", key, valRaw, err)
|
||||
}
|
||||
|
||||
val = valRaw
|
||||
case netmapHomomorphicHashDisabledKey, netmapMaintenanceAllowedKey:
|
||||
val, err = strconv.ParseBool(valRaw)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err)
|
||||
|
@ -159,6 +187,6 @@ func parseConfigPair(kvStr string, force bool) (key string, val any, err error)
|
|||
return
|
||||
}
|
||||
|
||||
func invalidConfigValueErr(key string) error {
|
||||
func invalidConfigValueErr(key []byte) error {
|
||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||
}
|
||||
|
|
|
@ -145,12 +145,12 @@ func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domai
|
|||
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
zone, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
} else {
|
||||
s, ok, err := c.nnsRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)
|
||||
|
|
40
cmd/frostfs-adm/internal/modules/morph/download.go
Normal file
40
cmd/frostfs-adm/internal/modules/morph/download.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func downloadContractsFromGithub(cmd *cobra.Command) (io.ReadCloser, error) {
|
||||
gcl := github.NewClient(nil)
|
||||
release, _, err := gcl.Repositories.GetLatestRelease(context.Background(), "nspcc-dev", "frostfs-contract")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch release info: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Found %s (%s), downloading...\n", release.GetTagName(), release.GetName())
|
||||
|
||||
var url string
|
||||
for _, a := range release.Assets {
|
||||
if strings.HasPrefix(a.GetName(), "frostfs-contract") {
|
||||
url = a.GetBrowserDownloadURL()
|
||||
break
|
||||
}
|
||||
}
|
||||
if url == "" {
|
||||
return nil, errors.New("can't find contracts archive in release assets")
|
||||
}
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch contracts archive: %w", err)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
|
@ -3,7 +3,6 @@ package morph
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
|
@ -14,7 +13,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||
func forceNewEpochCmd(cmd *cobra.Command, args []string) error {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
|
@ -39,14 +38,7 @@ func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := wCtx.awaitTx(); err != nil {
|
||||
if strings.Contains(err.Error(), "invalid epoch") {
|
||||
cmd.Println("Epoch has already ticked.")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return wCtx.awaitTx()
|
||||
}
|
||||
|
||||
func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error {
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -30,7 +29,7 @@ const (
|
|||
consensusAccountName = "consensus"
|
||||
)
|
||||
|
||||
func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
|
||||
func generateAlphabetCreds(cmd *cobra.Command, args []string) error {
|
||||
// alphabet size is not part of the config
|
||||
size, err := cmd.Flags().GetUint(alphabetSizeFlag)
|
||||
if err != nil {
|
||||
|
@ -39,9 +38,6 @@ func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
|
|||
if size == 0 {
|
||||
return errors.New("size must be > 0")
|
||||
}
|
||||
if size > maxAlphabetNodes {
|
||||
return ErrTooManyAlphabetNodes
|
||||
}
|
||||
|
||||
v := viper.GetViper()
|
||||
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
|
||||
|
@ -96,31 +92,28 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
|||
pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
|
||||
}
|
||||
|
||||
var errG errgroup.Group
|
||||
|
||||
// Create committee account with N/2+1 multi-signature.
|
||||
majCount := smartcontract.GetMajorityHonestNodeCount(size)
|
||||
for i, w := range wallets {
|
||||
if err := addMultisigAccount(w, majCount, committeeAccountName, passwords[i], pubs); err != nil {
|
||||
return nil, fmt.Errorf("can't create committee account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create consensus account with 2*N/3+1 multi-signature.
|
||||
bftCount := smartcontract.GetDefaultHonestNodeCount(size)
|
||||
for i := range wallets {
|
||||
i := i
|
||||
ps := pubs.Copy()
|
||||
errG.Go(func() error {
|
||||
if err := addMultisigAccount(wallets[i], majCount, committeeAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create committee account: %w", err)
|
||||
}
|
||||
if err := addMultisigAccount(wallets[i], bftCount, consensusAccountName, passwords[i], ps); err != nil {
|
||||
return fmt.Errorf("can't create consentus account: %w", err)
|
||||
}
|
||||
if err := wallets[i].SavePretty(); err != nil {
|
||||
return fmt.Errorf("can't save wallet: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for i, w := range wallets {
|
||||
if err := addMultisigAccount(w, bftCount, consensusAccountName, passwords[i], pubs); err != nil {
|
||||
return nil, fmt.Errorf("can't create consensus account: %w", err)
|
||||
}
|
||||
}
|
||||
if err := errG.Wait(); err != nil {
|
||||
return nil, err
|
||||
|
||||
for _, w := range wallets {
|
||||
if err := w.SavePretty(); err != nil {
|
||||
return nil, fmt.Errorf("can't save wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return passwords, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
|
@ -72,32 +71,24 @@ func TestGenerateAlphabet(t *testing.T) {
|
|||
buf.WriteString(testContractPassword + "\r")
|
||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := uint64(0); i < size; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "wallet doesn't exist")
|
||||
require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
|
||||
|
||||
for _, a := range w.Accounts {
|
||||
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||
require.NoError(t, err, "can't decrypt account")
|
||||
switch a.Label {
|
||||
case consensusAccountName:
|
||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
case committeeAccountName:
|
||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
default:
|
||||
require.Equal(t, singleAccountName, a.Label)
|
||||
}
|
||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
require.NoError(t, err, "wallet doesn't exist")
|
||||
require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
|
||||
for _, a := range w.Accounts {
|
||||
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
|
||||
require.NoError(t, err, "can't decrypt account")
|
||||
switch a.Label {
|
||||
case consensusAccountName:
|
||||
require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
case committeeAccountName:
|
||||
require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
|
||||
default:
|
||||
require.Equal(t, singleAccountName, a.Label)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
t.Run("check contract group wallet", func(t *testing.T) {
|
||||
p := filepath.Join(walletDir, contractWalletFilename)
|
||||
|
|
|
@ -23,13 +23,6 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
|
||||
// of the invocation script.
|
||||
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
|
||||
maxAlphabetNodes = 22
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
nnsCs *state.Contract
|
||||
groupKey *keys.PublicKey
|
||||
|
@ -52,9 +45,7 @@ type initializeContext struct {
|
|||
ContractPath string
|
||||
}
|
||||
|
||||
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes)
|
||||
|
||||
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
||||
func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
|
||||
initCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialization error: %w", err)
|
||||
|
@ -100,7 +91,11 @@ func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
|
|||
}
|
||||
|
||||
cmd.Println("Stage 7: set addresses in NNS.")
|
||||
return initCtx.setNNS()
|
||||
if err := initCtx.setNNS(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) close() {
|
||||
|
@ -120,10 +115,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(wallets) > maxAlphabetNodes {
|
||||
return nil, ErrTooManyAlphabetNodes
|
||||
}
|
||||
|
||||
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
|
||||
|
||||
var w *wallet.Wallet
|
||||
|
@ -151,7 +142,7 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctrPath, err := getContractsPath(cmd, needContracts)
|
||||
ctrPath, err := getContractsPath(cmd, v, needContracts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -210,11 +201,11 @@ func validateInit(cmd *cobra.Command) error {
|
|||
func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) {
|
||||
var c Client
|
||||
var err error
|
||||
if ldf := cmd.Flags().Lookup(localDumpFlag); ldf != nil && ldf.Changed {
|
||||
if cmd.Flags().Changed(endpointFlag) {
|
||||
if v.GetString(localDumpFlag) != "" {
|
||||
if v.GetString(endpointFlag) != "" {
|
||||
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
|
||||
}
|
||||
c, err = newLocalClient(cmd, v, wallets, ldf.Value.String())
|
||||
c, err = newLocalClient(cmd, v, wallets)
|
||||
} else {
|
||||
c, err = getN3Client(v)
|
||||
}
|
||||
|
@ -231,7 +222,7 @@ func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir
|
|||
return openContractWallet(v, cmd, walletDir)
|
||||
}
|
||||
|
||||
func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
|
||||
func getContractsPath(cmd *cobra.Command, v *viper.Viper, needContracts bool) (string, error) {
|
||||
if !needContracts {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"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/encoding/address"
|
||||
|
@ -24,7 +23,6 @@ import (
|
|||
"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/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
|
@ -32,8 +30,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -41,20 +39,44 @@ const (
|
|||
frostfsContract = "frostfs" // not deployed in side-chain.
|
||||
processingContract = "processing" // not deployed in side-chain.
|
||||
alphabetContract = "alphabet"
|
||||
auditContract = "audit"
|
||||
balanceContract = "balance"
|
||||
containerContract = "container"
|
||||
frostfsIDContract = "frostfsid"
|
||||
netmapContract = "netmap"
|
||||
proxyContract = "proxy"
|
||||
reputationContract = "reputation"
|
||||
subnetContract = "subnet"
|
||||
)
|
||||
|
||||
const (
|
||||
netmapEpochKey = "EpochDuration"
|
||||
netmapMaxObjectSizeKey = "MaxObjectSize"
|
||||
netmapAuditFeeKey = "AuditFee"
|
||||
netmapContainerFeeKey = "ContainerFee"
|
||||
netmapContainerAliasFeeKey = "ContainerAliasFee"
|
||||
netmapEigenTrustIterationsKey = "EigenTrustIterations"
|
||||
netmapEigenTrustAlphaKey = "EigenTrustAlpha"
|
||||
netmapBasicIncomeRateKey = "BasicIncomeRate"
|
||||
netmapInnerRingCandidateFeeKey = "InnerRingCandidateFee"
|
||||
netmapWithdrawFeeKey = "WithdrawFee"
|
||||
netmapHomomorphicHashDisabledKey = "HomomorphicHashingDisabled"
|
||||
netmapMaintenanceAllowedKey = "MaintenanceModeAllowed"
|
||||
|
||||
defaultEigenTrustIterations = 4
|
||||
defaultEigenTrustAlpha = "0.1"
|
||||
)
|
||||
|
||||
var (
|
||||
contractList = []string{
|
||||
auditContract,
|
||||
balanceContract,
|
||||
containerContract,
|
||||
frostfsIDContract,
|
||||
netmapContract,
|
||||
proxyContract,
|
||||
reputationContract,
|
||||
subnetContract,
|
||||
}
|
||||
|
||||
fullContractList = append([]string{
|
||||
|
@ -63,17 +85,6 @@ var (
|
|||
nnsContract,
|
||||
alphabetContract,
|
||||
}, contractList...)
|
||||
|
||||
netmapConfigKeys = []string{
|
||||
netmap.EpochDurationConfig,
|
||||
netmap.MaxObjectSizeConfig,
|
||||
netmap.ContainerFeeConfig,
|
||||
netmap.ContainerAliasFeeConfig,
|
||||
netmap.IrCandidateFeeConfig,
|
||||
netmap.WithdrawFeeConfig,
|
||||
netmap.HomomorphicHashingDisabledKey,
|
||||
netmap.MaintenanceModeAllowedConfig,
|
||||
}
|
||||
)
|
||||
|
||||
type contractState struct {
|
||||
|
@ -228,7 +239,7 @@ func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash
|
|||
invokeHash = ctrHash
|
||||
}
|
||||
|
||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, updateMethodName))
|
||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
|
||||
res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
|
||||
if err != nil {
|
||||
if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
|
||||
|
@ -351,7 +362,7 @@ func (c *initializeContext) deployContracts() error {
|
|||
return fmt.Errorf("can't sign manifest group: %v", err)
|
||||
}
|
||||
|
||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam, deployMethodName))
|
||||
params := getContractDeployParameters(cs, c.getContractDeployData(ctrName, keysParam))
|
||||
res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
|
||||
|
@ -397,9 +408,11 @@ func (c *initializeContext) readContracts(names []string) error {
|
|||
} else {
|
||||
var r io.ReadCloser
|
||||
if c.ContractPath == "" {
|
||||
return errors.New("contracts flag is missing")
|
||||
c.Command.Println("Contracts flag is missing, latest release will be fetched from Github.")
|
||||
r, err = downloadContractsFromGithub(c.Command)
|
||||
} else {
|
||||
r, err = os.Open(c.ContractPath)
|
||||
}
|
||||
r, err = os.Open(c.ContractPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open contracts archive: %w", err)
|
||||
}
|
||||
|
@ -516,7 +529,7 @@ func getContractDeployParameters(cs *contractState, deployData []any) []any {
|
|||
return []any{cs.RawNEF, cs.RawManifest, deployData}
|
||||
}
|
||||
|
||||
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any, method string) []any {
|
||||
func (c *initializeContext) getContractDeployData(ctrName string, keysParam []any) []any {
|
||||
items := make([]any, 1, 6)
|
||||
items[0] = false // notaryDisabled is false
|
||||
|
||||
|
@ -529,6 +542,8 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
|
|||
case processingContract:
|
||||
items = append(items, c.Contracts[frostfsContract].Hash)
|
||||
return items[1:] // no notary info
|
||||
case auditContract:
|
||||
items = append(items, c.Contracts[netmapContract].Hash)
|
||||
case balanceContract:
|
||||
items = append(items,
|
||||
c.Contracts[netmapContract].Hash,
|
||||
|
@ -551,31 +566,20 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
|
|||
c.Contracts[netmapContract].Hash,
|
||||
c.Contracts[containerContract].Hash)
|
||||
case netmapContract:
|
||||
md := getDefaultNetmapContractConfigMap()
|
||||
if method == updateMethodName {
|
||||
arr, err := c.getNetConfigFromNetmapContract()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m, err := parseConfigFromNetmapContract(arr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for k, v := range m {
|
||||
for _, key := range netmapConfigKeys {
|
||||
if k == key {
|
||||
md[k] = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
configParam := []any{
|
||||
netmapEpochKey, viper.GetInt64(epochDurationInitFlag),
|
||||
netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag),
|
||||
netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag),
|
||||
netmapContainerFeeKey, viper.GetInt64(containerFeeInitFlag),
|
||||
netmapContainerAliasFeeKey, viper.GetInt64(containerAliasFeeInitFlag),
|
||||
netmapEigenTrustIterationsKey, int64(defaultEigenTrustIterations),
|
||||
netmapEigenTrustAlphaKey, defaultEigenTrustAlpha,
|
||||
netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag),
|
||||
netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag),
|
||||
netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag),
|
||||
netmapHomomorphicHashDisabledKey, viper.GetBool(homomorphicHashDisabledInitFlag),
|
||||
netmapMaintenanceAllowedKey, viper.GetBool(maintenanceModeAllowedInitFlag),
|
||||
}
|
||||
|
||||
var configParam []any
|
||||
for k, v := range md {
|
||||
configParam = append(configParam, k, v)
|
||||
}
|
||||
|
||||
items = append(items,
|
||||
c.Contracts[balanceContract].Hash,
|
||||
c.Contracts[containerContract].Hash,
|
||||
|
@ -583,28 +587,14 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
|
|||
configParam)
|
||||
case proxyContract:
|
||||
items = nil
|
||||
case reputationContract:
|
||||
case subnetContract:
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid contract name: %s", ctrName))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (c *initializeContext) getNetConfigFromNetmapContract() ([]stackitem.Item, error) {
|
||||
cs, err := c.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NNS is not yet deployed: %w", err)
|
||||
}
|
||||
nmHash, err := nnsResolveHash(c.ReadOnlyInvoker, cs.Hash, netmapContract+".frostfs")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
arr, err := unwrap.Array(c.ReadOnlyInvoker.Call(nmHash, "listConfig"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch list of network config keys from the netmap contract")
|
||||
}
|
||||
return arr, err
|
||||
}
|
||||
|
||||
func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
|
||||
items := make([]any, 6)
|
||||
items[0] = false
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
nnsClient "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -27,8 +26,6 @@ import (
|
|||
|
||||
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second
|
||||
|
||||
const frostfsOpsEmail = "ops@frostfs.info"
|
||||
|
||||
func (c *initializeContext) setNNS() error {
|
||||
nnsCs, err := c.Client.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
|
@ -42,7 +39,7 @@ func (c *initializeContext) setNNS() error {
|
|||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
|
||||
"frostfs", c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
if err := c.sendCommitteeTx(bw.Bytes(), true); err != nil {
|
||||
return fmt.Errorf("can't add domain root to NNS: %w", err)
|
||||
|
@ -124,7 +121,7 @@ func (c *initializeContext) emitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHas
|
|||
if isAvail {
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
morphClient.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
|
||||
|
@ -172,7 +169,7 @@ func (c *initializeContext) nnsRegisterDomainScript(nnsHash, expectedHash util.U
|
|||
bw := io.NewBufBinWriter()
|
||||
emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All,
|
||||
domain, c.CommitteeAcc.Contract.ScriptHash(),
|
||||
frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
"ops@nspcc.ru", int64(3600), int64(600), int64(defaultExpirationTime), int64(3600))
|
||||
emit.Opcodes(bw.BinWriter, opcode.ASSERT)
|
||||
|
||||
if bw.Err != nil {
|
||||
|
@ -287,11 +284,10 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
|
|||
}
|
||||
|
||||
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
|
||||
switch c.(type) {
|
||||
switch ct := c.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c, nil)
|
||||
reader := nnsClient.NewReader(inv, nnsHash)
|
||||
return reader.IsAvailable(name)
|
||||
//lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
||||
return ct.NNSIsAvailable(nnsHash, name)
|
||||
default:
|
||||
b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
|
||||
if err != nil {
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
|
@ -19,24 +18,33 @@ import (
|
|||
)
|
||||
|
||||
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
|
||||
const (
|
||||
initialAlphabetNEOAmount = native.NEOTotalSupply
|
||||
registerBatchSize = transaction.MaxAttributes - 1
|
||||
)
|
||||
const initialAlphabetNEOAmount = native.NEOTotalSupply
|
||||
|
||||
func (c *initializeContext) registerCandidates() error {
|
||||
neoHash := neo.Hash
|
||||
|
||||
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neoHash, "getCandidates"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("`getCandidates`: %w", err)
|
||||
}
|
||||
|
||||
if len(cc) > 0 {
|
||||
c.Command.Println("Candidates are already registered.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) registerCandidateRange(start, end int) error {
|
||||
regPrice, err := c.getCandidateRegisterPrice()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch registration price: %w", err)
|
||||
}
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, 1)
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
||||
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, 1)
|
||||
for _, acc := range c.Accounts {
|
||||
emit.AppCall(w.BinWriter, neoHash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
|
||||
emit.Opcodes(w.BinWriter, opcode.ASSERT)
|
||||
}
|
||||
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, regPrice)
|
||||
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, regPrice)
|
||||
if w.Err != nil {
|
||||
panic(fmt.Sprintf("BUG: %v", w.Err))
|
||||
}
|
||||
|
@ -45,14 +53,14 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
|||
Signer: c.getSigner(false, c.CommitteeAcc),
|
||||
Account: c.CommitteeAcc,
|
||||
}}
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
for i := range c.Accounts {
|
||||
signers = append(signers, rpcclient.SignerAccount{
|
||||
Signer: transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Account: c.Accounts[i].Contract.ScriptHash(),
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{neo.Hash},
|
||||
AllowedContracts: []util.Uint160{neoHash},
|
||||
},
|
||||
Account: acc,
|
||||
Account: c.Accounts[i],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -65,8 +73,8 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
|||
}
|
||||
|
||||
network := c.CommitteeAct.GetNetwork()
|
||||
for _, acc := range c.Accounts[start:end] {
|
||||
if err := acc.SignTx(network, tx); err != nil {
|
||||
for i := range c.Accounts {
|
||||
if err := c.Accounts[i].SignTx(network, tx); err != nil {
|
||||
return fmt.Errorf("can't sign a transaction: %w", err)
|
||||
}
|
||||
}
|
||||
|
@ -74,39 +82,6 @@ func (c *initializeContext) registerCandidateRange(start, end int) error {
|
|||
return c.sendTx(tx, c.Command, true)
|
||||
}
|
||||
|
||||
func (c *initializeContext) registerCandidates() error {
|
||||
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("`getCandidates`: %w", err)
|
||||
}
|
||||
|
||||
need := len(c.Accounts)
|
||||
have := len(cc)
|
||||
|
||||
if need == have {
|
||||
c.Command.Println("Candidates are already registered.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register candidates in batches in order to overcome the signers amount limit.
|
||||
// See: https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/transaction/transaction.go#L27
|
||||
for i := 0; i < need; i += registerBatchSize {
|
||||
start, end := i, i+registerBatchSize
|
||||
if end > need {
|
||||
end = need
|
||||
}
|
||||
// This check is sound because transactions are accepted/rejected atomically.
|
||||
if have >= end {
|
||||
continue
|
||||
}
|
||||
if err := c.registerCandidateRange(start, end); err != nil {
|
||||
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *initializeContext) transferNEOToAlphabetContracts() error {
|
||||
neoHash := neo.Hash
|
||||
|
||||
|
@ -141,11 +116,10 @@ func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, err
|
|||
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
|
||||
|
||||
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
|
||||
switch c.Client.(type) {
|
||||
switch ct := c.Client.(type) {
|
||||
case *rpcclient.Client:
|
||||
inv := invoker.New(c.Client, nil)
|
||||
reader := neo.NewReader(inv)
|
||||
return reader.GetRegisterPrice()
|
||||
//lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
||||
return ct.GetCandidateRegisterPrice()
|
||||
default:
|
||||
neoHash := neo.Hash
|
||||
res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)
|
||||
|
|
|
@ -2,12 +2,10 @@ package morph
|
|||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
|
@ -38,22 +36,13 @@ func TestInitialize(t *testing.T) {
|
|||
t.Run("7 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 7)
|
||||
})
|
||||
t.Run("16 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 16)
|
||||
})
|
||||
t.Run("max nodes", func(t *testing.T) {
|
||||
testInitialize(t, maxAlphabetNodes)
|
||||
})
|
||||
t.Run("too many nodes", func(t *testing.T) {
|
||||
require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes)
|
||||
})
|
||||
}
|
||||
|
||||
func testInitialize(t *testing.T, committeeSize int) {
|
||||
testdataDir := t.TempDir()
|
||||
v := viper.GetViper()
|
||||
|
||||
require.NoError(t, generateTestData(t, testdataDir, committeeSize))
|
||||
generateTestData(t, testdataDir, committeeSize)
|
||||
v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
|
||||
|
||||
// Set to the path or remove the next statement to download from the network.
|
||||
|
@ -84,33 +73,25 @@ func testInitialize(t *testing.T, committeeSize int) {
|
|||
})
|
||||
}
|
||||
|
||||
func generateTestData(t *testing.T, dir string, size int) error {
|
||||
func generateTestData(t *testing.T, dir string, size int) {
|
||||
v := viper.GetViper()
|
||||
v.Set(alphabetWalletsFlag, dir)
|
||||
|
||||
sizeStr := strconv.FormatUint(uint64(size), 10)
|
||||
if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil {
|
||||
return err
|
||||
}
|
||||
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr))
|
||||
|
||||
setTestCredentials(v, size)
|
||||
if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
|
||||
|
||||
var pubs []string
|
||||
for i := 0; i < size; i++ {
|
||||
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wallet doesn't exist: %w", err)
|
||||
}
|
||||
require.NoError(t, err, "wallet doesn't exist")
|
||||
for _, acc := range w.Accounts {
|
||||
if acc.Label == singleAccountName {
|
||||
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not parse signature script for %s", acc.Address)
|
||||
}
|
||||
require.True(t, ok)
|
||||
pubs = append(pubs, hex.EncodeToString(pub))
|
||||
continue
|
||||
}
|
||||
|
@ -119,18 +100,17 @@ func generateTestData(t *testing.T, dir string, size int) error {
|
|||
|
||||
cfg := config.Config{}
|
||||
cfg.ProtocolConfiguration.Magic = 12345
|
||||
cfg.ProtocolConfiguration.ValidatorsCount = uint32(size)
|
||||
cfg.ProtocolConfiguration.TimePerBlock = time.Second
|
||||
cfg.ProtocolConfiguration.ValidatorsCount = size
|
||||
cfg.ProtocolConfiguration.SecondsPerBlock = 1 //lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
||||
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
|
||||
cfg.ProtocolConfiguration.P2PSigExtensions = true
|
||||
cfg.ProtocolConfiguration.VerifyTransactions = true
|
||||
cfg.ProtocolConfiguration.VerifyBlocks = true //lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
protoPath := filepath.Join(dir, protoFileName)
|
||||
return os.WriteFile(protoPath, data, os.ModePerm)
|
||||
require.NoError(t, os.WriteFile(protoPath, data, os.ModePerm))
|
||||
}
|
||||
|
||||
func setTestCredentials(v *viper.Viper, size int) {
|
||||
|
|
65
cmd/frostfs-adm/internal/modules/morph/internal/types.go
Normal file
65
cmd/frostfs-adm/internal/modules/morph/internal/types.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// StringifySubnetClientGroupID returns string representation of SubnetClientGroupID using MarshalText.
|
||||
// Returns a string with a message on error.
|
||||
func StringifySubnetClientGroupID(id *SubnetClientGroupID) string {
|
||||
text, err := id.MarshalText()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("<invalid> %v", err)
|
||||
}
|
||||
|
||||
return string(text)
|
||||
}
|
||||
|
||||
// MarshalText encodes SubnetClientGroupID into text format according to FrostFS API V2 protocol:
|
||||
// value in base-10 integer string format.
|
||||
//
|
||||
// It implements encoding.TextMarshaler.
|
||||
func (x *SubnetClientGroupID) MarshalText() ([]byte, error) {
|
||||
num := x.GetValue() // NPE safe, returns zero on nil
|
||||
|
||||
return []byte(strconv.FormatUint(uint64(num), 10)), nil
|
||||
}
|
||||
|
||||
// UnmarshalText decodes the SubnetID from the text according to FrostFS API V2 protocol:
|
||||
// should be base-10 integer string format with bitsize = 32.
|
||||
//
|
||||
// Returns strconv.ErrRange if integer overflows uint32.
|
||||
//
|
||||
// Must not be called on nil.
|
||||
//
|
||||
// Implements encoding.TextUnmarshaler.
|
||||
func (x *SubnetClientGroupID) UnmarshalText(txt []byte) error {
|
||||
num, err := strconv.ParseUint(string(txt), 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid numeric value: %w", err)
|
||||
}
|
||||
|
||||
x.SetNumber(uint32(num))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal encodes the SubnetClientGroupID into a binary format of FrostFS API V2 protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
func (x *SubnetClientGroupID) Marshal() ([]byte, error) {
|
||||
return proto.Marshal(x)
|
||||
}
|
||||
|
||||
// Unmarshal decodes the SubnetClientGroupID from FrostFS API V2 binary format (see Marshal). Must not be called on nil.
|
||||
func (x *SubnetClientGroupID) Unmarshal(data []byte) error {
|
||||
return proto.Unmarshal(data, x)
|
||||
}
|
||||
|
||||
// SetNumber sets SubnetClientGroupID value in uint32 format. Must not be called on nil.
|
||||
// By default, number is 0.
|
||||
func (x *SubnetClientGroupID) SetNumber(num uint32) {
|
||||
x.Value = num
|
||||
}
|
BIN
cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go
generated
Normal file
BIN
cmd/frostfs-adm/internal/modules/morph/internal/types.pb.go
generated
Normal file
Binary file not shown.
15
cmd/frostfs-adm/internal/modules/morph/internal/types.proto
Normal file
15
cmd/frostfs-adm/internal/modules/morph/internal/types.proto
Normal file
|
@ -0,0 +1,15 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package neo.fs.v2.refs;
|
||||
|
||||
option go_package = "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/internal";
|
||||
|
||||
// Client group identifier in the FrostFS subnet.
|
||||
//
|
||||
// String representation of a value is base-10 integer.
|
||||
//
|
||||
// JSON representation is an object containing single `value` number field.
|
||||
message SubnetClientGroupID {
|
||||
// 4-byte integer identifier of the subnet client group.
|
||||
fixed32 value = 1 [json_name = "value"];
|
||||
}
|
|
@ -51,7 +51,7 @@ type localClient struct {
|
|||
maxGasInvoke int64
|
||||
}
|
||||
|
||||
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*localClient, error) {
|
||||
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) {
|
||||
cfg, err := config.LoadFile(v.GetString(protoConfigPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -62,7 +62,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
|||
return nil, err
|
||||
}
|
||||
|
||||
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
|
||||
m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount)
|
||||
accounts := make([]*wallet.Account, len(wallets))
|
||||
for i := range accounts {
|
||||
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
|
||||
|
@ -87,6 +87,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
|
|||
|
||||
go bc.Run()
|
||||
|
||||
dumpPath := v.GetString(localDumpFlag)
|
||||
if cmd.Name() != "init" {
|
||||
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
|
@ -247,7 +248,7 @@ func (l *localClient) GetVersion() (*result.Version, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Parameter, []transaction.Signer, ...transaction.Witness) (*result.Invoke, error) {
|
||||
func (l *localClient) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
// not used by `morph init` command
|
||||
panic("unexpected call")
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getDefaultNetmapContractConfigMap() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag)
|
||||
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag)
|
||||
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
|
||||
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
|
||||
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)
|
||||
m[netmap.WithdrawFeeConfig] = viper.GetInt64(withdrawFeeInitFlag)
|
||||
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(homomorphicHashDisabledInitFlag)
|
||||
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(maintenanceModeAllowedInitFlag)
|
||||
return m
|
||||
}
|
||||
|
||||
func parseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
|
||||
m := make(map[string][]byte, len(arr))
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return nil, errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
v, err := tuple[1].TryBytes()
|
||||
if err != nil {
|
||||
return nil, invalidConfigValueErr(string(k))
|
||||
}
|
||||
m[string(k)] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
|
@ -18,6 +18,10 @@ const (
|
|||
maxObjectSizeCLIFlag = "max-object-size"
|
||||
epochDurationInitFlag = "network.epoch_duration"
|
||||
epochDurationCLIFlag = "epoch-duration"
|
||||
incomeRateInitFlag = "network.basic_income_rate"
|
||||
incomeRateCLIFlag = "basic-income-rate"
|
||||
auditFeeInitFlag = "network.fee.audit"
|
||||
auditFeeCLIFlag = "audit-fee"
|
||||
containerFeeInitFlag = "network.fee.container"
|
||||
containerAliasFeeInitFlag = "network.fee.container_alias"
|
||||
containerFeeCLIFlag = "container-fee"
|
||||
|
@ -65,12 +69,15 @@ var (
|
|||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
|
||||
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
|
||||
_ = viper.BindPFlag(incomeRateInitFlag, cmd.Flags().Lookup(incomeRateCLIFlag))
|
||||
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
|
||||
_ = viper.BindPFlag(auditFeeInitFlag, cmd.Flags().Lookup(auditFeeCLIFlag))
|
||||
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
|
||||
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
|
||||
_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
|
||||
_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
|
||||
_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
|
||||
_ = viper.BindPFlag(localDumpFlag, cmd.Flags().Lookup(localDumpFlag))
|
||||
},
|
||||
RunE: initializeSideChainCmd,
|
||||
}
|
||||
|
@ -248,6 +255,7 @@ func init() {
|
|||
initRestoreContainersCmd()
|
||||
initListContainersCmd()
|
||||
initRefillGasCmd()
|
||||
initSubnetCmd()
|
||||
initDepositoryNotaryCmd()
|
||||
initNetmapCandidatesCmd()
|
||||
}
|
||||
|
@ -266,6 +274,10 @@ func initDepositoryNotaryCmd() {
|
|||
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
|
||||
}
|
||||
|
||||
func initSubnetCmd() {
|
||||
RootCmd.AddCommand(cmdSubnet)
|
||||
}
|
||||
|
||||
func initRefillGasCmd() {
|
||||
RootCmd.AddCommand(refillGasCmd)
|
||||
refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||
|
@ -302,8 +314,7 @@ func initUpdateContractsCmd() {
|
|||
RootCmd.AddCommand(updateContractsCmd)
|
||||
updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||
updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts")
|
||||
_ = updateContractsCmd.MarkFlagRequired(contractsInitFlag)
|
||||
updateContractsCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts (default fetched from latest github release)")
|
||||
}
|
||||
|
||||
func initDumpBalancesCmd() {
|
||||
|
@ -364,8 +375,7 @@ func initInitCmd() {
|
|||
RootCmd.AddCommand(initCmd)
|
||||
initCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
|
||||
initCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts")
|
||||
_ = initCmd.MarkFlagRequired(contractsInitFlag)
|
||||
initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts (default fetched from latest github release)")
|
||||
initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch")
|
||||
initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
|
||||
initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
|
||||
|
|
1101
cmd/frostfs-adm/internal/modules/morph/subnet.go
Normal file
1101
cmd/frostfs-adm/internal/modules/morph/subnet.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -45,14 +45,14 @@ func init() {
|
|||
rootCmd.AddCommand(storagecfg.RootCmd)
|
||||
|
||||
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd))
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, _ []string) error {
|
||||
func entryPoint(cmd *cobra.Command, args []string) error {
|
||||
printVersion, _ := cmd.Flags().GetBool("version")
|
||||
if printVersion {
|
||||
cmd.Print(misc.BuildInfo("FrostFS Adm"))
|
||||
|
|
|
@ -12,6 +12,9 @@ node:
|
|||
- {{ .AnnouncedAddress }}
|
||||
attribute_0: UN-LOCODE:{{ .Attribute.Locode }}
|
||||
relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map
|
||||
subnet:
|
||||
exit_zero: false # toggle entrance to zero subnet (overrides corresponding attribute and occurrence in entries)
|
||||
entries: [] # list of IDs of subnets to enter in a text format of FrostFS API protocol (overrides corresponding attributes)
|
||||
|
||||
grpc:
|
||||
num: 1 # total number of listener endpoints
|
||||
|
|
|
@ -26,6 +26,7 @@ If set to '0' or not set, only the current epoch is used.
|
|||
List of commands with support of extended headers:
|
||||
* `container list-objects`
|
||||
* `object delete/get/hash/head/lock/put/range/search`
|
||||
* `storagegroup delete/get/list/put`
|
||||
|
||||
Example:
|
||||
```shell
|
||||
|
|
|
@ -8,13 +8,12 @@ import (
|
|||
"io"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
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/eacl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
)
|
||||
|
@ -38,8 +37,8 @@ func (x BalanceOfRes) Balance() accounting.Decimal {
|
|||
// BalanceOf requests the current balance of a FrostFS user.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func BalanceOf(ctx context.Context, prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
||||
res.cliRes, err = prm.cli.BalanceGet(ctx, prm.PrmBalanceGet)
|
||||
func BalanceOf(prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
||||
res.cliRes, err = prm.cli.BalanceGet(context.Background(), prm.PrmBalanceGet)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -63,16 +62,16 @@ func (x ListContainersRes) IDList() []cid.ID {
|
|||
// ListContainers requests a list of FrostFS user's containers.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContainersRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerList(ctx, prm.PrmContainerList)
|
||||
func ListContainers(prm ListContainersPrm) (res ListContainersRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerList(context.Background(), prm.PrmContainerList)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PutContainerPrm groups parameters of PutContainer operation.
|
||||
type PutContainerPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerPut
|
||||
commonPrm
|
||||
client.PrmContainerPut
|
||||
}
|
||||
|
||||
// PutContainerRes groups the resulting values of PutContainer operation.
|
||||
|
@ -93,8 +92,8 @@ func (x PutContainerRes) ID() cid.ID {
|
|||
// Success can be verified by reading by identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||
cliRes, err := prm.Client.ContainerPut(ctx, prm.ClientParams)
|
||||
func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||
cliRes, err := prm.cli.ContainerPut(context.Background(), prm.PrmContainerPut)
|
||||
if err == nil {
|
||||
res.cnr = cliRes.ID()
|
||||
}
|
||||
|
@ -104,15 +103,13 @@ func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes
|
|||
|
||||
// GetContainerPrm groups parameters of GetContainer operation.
|
||||
type GetContainerPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerGet
|
||||
commonPrm
|
||||
cliPrm client.PrmContainerGet
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the container to be read.
|
||||
//
|
||||
// Deprecated: Use GetContainerPrm.ClientParams.ContainerID instead.
|
||||
func (x *GetContainerPrm) SetContainer(id cid.ID) {
|
||||
x.ClientParams.ContainerID = &id
|
||||
x.cliPrm.SetContainer(id)
|
||||
}
|
||||
|
||||
// GetContainerRes groups the resulting values of GetContainer operation.
|
||||
|
@ -128,23 +125,20 @@ func (x GetContainerRes) Container() containerSDK.Container {
|
|||
// GetContainer reads a container from FrostFS by ID.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func GetContainer(ctx context.Context, prm GetContainerPrm) (res GetContainerRes, err error) {
|
||||
res.cliRes, err = prm.Client.ContainerGet(ctx, prm.ClientParams)
|
||||
func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerGet(context.Background(), prm.cliPrm)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsACLExtendable checks if ACL of the container referenced by the given identifier
|
||||
// can be extended. Client connection MUST BE correctly established in advance.
|
||||
func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, error) {
|
||||
prm := GetContainerPrm{
|
||||
Client: c,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &cnr,
|
||||
},
|
||||
}
|
||||
func IsACLExtendable(c *client.Client, cnr cid.ID) (bool, error) {
|
||||
var prm GetContainerPrm
|
||||
prm.SetClient(c)
|
||||
prm.SetContainer(cnr)
|
||||
|
||||
res, err := GetContainer(ctx, prm)
|
||||
res, err := GetContainer(prm)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get container from the FrostFS: %w", err)
|
||||
}
|
||||
|
@ -154,8 +148,8 @@ func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, e
|
|||
|
||||
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
|
||||
type DeleteContainerPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerDelete
|
||||
commonPrm
|
||||
client.PrmContainerDelete
|
||||
}
|
||||
|
||||
// DeleteContainerRes groups the resulting values of DeleteContainer operation.
|
||||
|
@ -169,16 +163,16 @@ type DeleteContainerRes struct{}
|
|||
// Success can be verified by reading by identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
|
||||
_, err = prm.Client.ContainerDelete(ctx, prm.ClientParams)
|
||||
func DeleteContainer(prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
|
||||
_, err = prm.cli.ContainerDelete(context.Background(), prm.PrmContainerDelete)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EACLPrm groups parameters of EACL operation.
|
||||
type EACLPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerEACL
|
||||
commonPrm
|
||||
client.PrmContainerEACL
|
||||
}
|
||||
|
||||
// EACLRes groups the resulting values of EACL operation.
|
||||
|
@ -194,16 +188,16 @@ func (x EACLRes) EACL() eacl.Table {
|
|||
// EACL reads eACL table from FrostFS by container ID.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) {
|
||||
res.cliRes, err = prm.Client.ContainerEACL(ctx, prm.ClientParams)
|
||||
func EACL(prm EACLPrm) (res EACLRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerEACL(context.Background(), prm.PrmContainerEACL)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetEACLPrm groups parameters of SetEACL operation.
|
||||
type SetEACLPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmContainerSetEACL
|
||||
commonPrm
|
||||
client.PrmContainerSetEACL
|
||||
}
|
||||
|
||||
// SetEACLRes groups the resulting values of SetEACL operation.
|
||||
|
@ -217,16 +211,16 @@ type SetEACLRes struct{}
|
|||
// Success can be verified by reading by container identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func SetEACL(ctx context.Context, prm SetEACLPrm) (res SetEACLRes, err error) {
|
||||
_, err = prm.Client.ContainerSetEACL(ctx, prm.ClientParams)
|
||||
func SetEACL(prm SetEACLPrm) (res SetEACLRes, err error) {
|
||||
_, err = prm.cli.ContainerSetEACL(context.Background(), prm.PrmContainerSetEACL)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
||||
type NetworkInfoPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmNetworkInfo
|
||||
commonPrm
|
||||
client.PrmNetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfoRes groups the resulting values of NetworkInfo operation.
|
||||
|
@ -242,16 +236,16 @@ func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
|
|||
// NetworkInfo reads information about the FrostFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetworkInfo(ctx context.Context, prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
||||
res.cliRes, err = prm.Client.NetworkInfo(ctx, prm.ClientParams)
|
||||
func NetworkInfo(prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetworkInfo(context.Background(), prm.PrmNetworkInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NodeInfoPrm groups parameters of NodeInfo operation.
|
||||
type NodeInfoPrm struct {
|
||||
Client *client.Client
|
||||
ClientParams client.PrmEndpointInfo
|
||||
commonPrm
|
||||
client.PrmEndpointInfo
|
||||
}
|
||||
|
||||
// NodeInfoRes groups the resulting values of NodeInfo operation.
|
||||
|
@ -272,8 +266,8 @@ func (x NodeInfoRes) LatestVersion() version.Version {
|
|||
// NodeInfo requests information about the remote server from FrostFS netmap.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NodeInfo(ctx context.Context, prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
||||
res.cliRes, err = prm.Client.EndpointInfo(ctx, prm.ClientParams)
|
||||
func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
||||
res.cliRes, err = prm.cli.EndpointInfo(context.Background(), prm.PrmEndpointInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -296,8 +290,8 @@ func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
|||
// NetMapSnapshot requests current network view of the remote server.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetMapSnapshot(ctx context.Context, prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetMapSnapshot(ctx, client.PrmNetMapSnapshot{})
|
||||
func NetMapSnapshot(prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetMapSnapshot(context.Background(), client.PrmNetMapSnapshot{})
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -325,8 +319,8 @@ func (x CreateSessionRes) SessionKey() []byte {
|
|||
// CreateSession opens a new unlimited session with the remote node.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func CreateSession(ctx context.Context, prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
||||
res.cliRes, err = prm.cli.SessionCreate(ctx, prm.PrmSessionCreate)
|
||||
func CreateSession(prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
||||
res.cliRes, err = prm.cli.SessionCreate(context.Background(), prm.PrmSessionCreate)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -335,19 +329,17 @@ func CreateSession(ctx context.Context, prm CreateSessionPrm) (res CreateSession
|
|||
type PutObjectPrm struct {
|
||||
commonObjectPrm
|
||||
|
||||
copyNum []uint32
|
||||
copyNum uint32
|
||||
|
||||
hdr *objectSDK.Object
|
||||
hdr *object.Object
|
||||
|
||||
rdr io.Reader
|
||||
|
||||
headerCallback func(*objectSDK.Object)
|
||||
|
||||
prepareLocally bool
|
||||
headerCallback func(*object.Object)
|
||||
}
|
||||
|
||||
// SetHeader sets object header.
|
||||
func (x *PutObjectPrm) SetHeader(hdr *objectSDK.Object) {
|
||||
func (x *PutObjectPrm) SetHeader(hdr *object.Object) {
|
||||
x.hdr = hdr
|
||||
}
|
||||
|
||||
|
@ -358,51 +350,15 @@ func (x *PutObjectPrm) SetPayloadReader(rdr io.Reader) {
|
|||
|
||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||
// but before the payload is written.
|
||||
func (x *PutObjectPrm) SetHeaderCallback(f func(*objectSDK.Object)) {
|
||||
func (x *PutObjectPrm) SetHeaderCallback(f func(*object.Object)) {
|
||||
x.headerCallback = f
|
||||
}
|
||||
|
||||
// SetCopiesNumberByVectors sets ordered list of minimal required object copies numbers
|
||||
// per placement vector.
|
||||
func (x *PutObjectPrm) SetCopiesNumberByVectors(copiesNumbers []uint32) {
|
||||
// SetCopiesNumber sets number of object copies that is enough to consider put successful.
|
||||
func (x *PutObjectPrm) SetCopiesNumber(copiesNumbers uint32) {
|
||||
x.copyNum = copiesNumbers
|
||||
}
|
||||
|
||||
// PrepareLocally generate object header on the client side.
|
||||
// For big object - split locally too.
|
||||
func (x *PutObjectPrm) PrepareLocally() {
|
||||
x.prepareLocally = true
|
||||
}
|
||||
|
||||
func (x *PutObjectPrm) convertToSDKPrm(ctx context.Context) (client.PrmObjectPutInit, error) {
|
||||
var putPrm client.PrmObjectPutInit
|
||||
if !x.prepareLocally && x.sessionToken != nil {
|
||||
putPrm.WithinSession(*x.sessionToken)
|
||||
}
|
||||
|
||||
if x.bearerToken != nil {
|
||||
putPrm.WithBearerToken(*x.bearerToken)
|
||||
}
|
||||
|
||||
if x.local {
|
||||
putPrm.MarkLocal()
|
||||
}
|
||||
|
||||
putPrm.WithXHeaders(x.xHeaders...)
|
||||
putPrm.SetCopiesNumberByVectors(x.copyNum)
|
||||
|
||||
if x.prepareLocally {
|
||||
res, err := x.cli.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||
if err != nil {
|
||||
return client.PrmObjectPutInit{}, err
|
||||
}
|
||||
putPrm.WithObjectMaxSize(res.Info().MaxObjectSize())
|
||||
putPrm.WithEpochSource(epochSource(res.Info().CurrentEpoch()))
|
||||
putPrm.WithoutHomomorphicHash(res.Info().HomomorphicHashingDisabled())
|
||||
}
|
||||
return putPrm, nil
|
||||
}
|
||||
|
||||
// PutObjectRes groups the resulting values of PutObject operation.
|
||||
type PutObjectRes struct {
|
||||
id oid.ID
|
||||
|
@ -413,26 +369,33 @@ func (x PutObjectRes) ID() oid.ID {
|
|||
return x.id
|
||||
}
|
||||
|
||||
type epochSource uint64
|
||||
|
||||
func (s epochSource) CurrentEpoch() uint64 {
|
||||
return uint64(s)
|
||||
}
|
||||
|
||||
// PutObject saves the object in FrostFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) {
|
||||
sdkPrm, err := prm.convertToSDKPrm(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create parameters of object put operation: %w", err)
|
||||
func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
||||
var putPrm client.PrmObjectPutInit
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
putPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
wrt, err := prm.cli.ObjectPutInit(ctx, sdkPrm)
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
putPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
putPrm.MarkLocal()
|
||||
}
|
||||
|
||||
putPrm.WithXHeaders(prm.xHeaders...)
|
||||
putPrm.SetCopiesNumber(prm.copyNum)
|
||||
|
||||
wrt, err := prm.cli.ObjectPutInit(context.Background(), putPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object writing: %w", err)
|
||||
}
|
||||
|
||||
if wrt.WriteHeader(ctx, *prm.hdr) {
|
||||
if wrt.WriteHeader(*prm.hdr) {
|
||||
if prm.headerCallback != nil {
|
||||
prm.headerCallback(prm.hdr)
|
||||
}
|
||||
|
@ -462,7 +425,7 @@ func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) {
|
|||
for {
|
||||
n, err = prm.rdr.Read(buf)
|
||||
if n > 0 {
|
||||
if !wrt.WritePayloadChunk(ctx, buf[:n]) {
|
||||
if !wrt.WritePayloadChunk(buf[:n]) {
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -478,7 +441,7 @@ func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) {
|
|||
}
|
||||
}
|
||||
|
||||
cliRes, err := wrt.Close(ctx)
|
||||
cliRes, err := wrt.Close()
|
||||
if err != nil { // here err already carries both status and client errors
|
||||
return nil, fmt.Errorf("client failure: %w", err)
|
||||
}
|
||||
|
@ -507,19 +470,22 @@ func (x DeleteObjectRes) Tombstone() oid.ID {
|
|||
// DeleteObject marks an object to be removed from FrostFS through tombstone placement.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||
var delPrm client.PrmObjectDelete
|
||||
delPrm.FromContainer(prm.objAddr.Container())
|
||||
delPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
delPrm := client.PrmObjectDelete{
|
||||
XHeaders: prm.xHeaders,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
Session: prm.sessionToken,
|
||||
BearerToken: prm.bearerToken,
|
||||
if prm.sessionToken != nil {
|
||||
delPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
cliRes, err := prm.cli.ObjectDelete(ctx, delPrm)
|
||||
if prm.bearerToken != nil {
|
||||
delPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
delPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
cliRes, err := prm.cli.ObjectDelete(context.Background(), delPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("remove object via client: %w", err)
|
||||
}
|
||||
|
@ -535,22 +501,22 @@ type GetObjectPrm struct {
|
|||
objectAddressPrm
|
||||
rawPrm
|
||||
payloadWriterPrm
|
||||
headerCallback func(*objectSDK.Object)
|
||||
headerCallback func(*object.Object)
|
||||
}
|
||||
|
||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||
// but before the payload is written.
|
||||
func (p *GetObjectPrm) SetHeaderCallback(f func(*objectSDK.Object)) {
|
||||
func (p *GetObjectPrm) SetHeaderCallback(f func(*object.Object)) {
|
||||
p.headerCallback = f
|
||||
}
|
||||
|
||||
// GetObjectRes groups the resulting values of GetObject operation.
|
||||
type GetObjectRes struct {
|
||||
hdr *objectSDK.Object
|
||||
hdr *object.Object
|
||||
}
|
||||
|
||||
// Header returns the header of the request object.
|
||||
func (x GetObjectRes) Header() *objectSDK.Object {
|
||||
func (x GetObjectRes) Header() *object.Object {
|
||||
return x.hdr
|
||||
}
|
||||
|
||||
|
@ -560,26 +526,35 @@ func (x GetObjectRes) Header() *objectSDK.Object {
|
|||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
func GetObject(prm GetObjectPrm) (*GetObjectRes, error) {
|
||||
var getPrm client.PrmObjectGet
|
||||
getPrm.FromContainer(prm.objAddr.Container())
|
||||
getPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
getPrm := client.PrmObjectGet{
|
||||
XHeaders: prm.xHeaders,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
Raw: prm.raw,
|
||||
Local: prm.local,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
if prm.sessionToken != nil {
|
||||
getPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
rdr, err := prm.cli.ObjectGetInit(ctx, getPrm)
|
||||
if prm.bearerToken != nil {
|
||||
getPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.raw {
|
||||
getPrm.MarkRaw()
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
getPrm.MarkLocal()
|
||||
}
|
||||
|
||||
getPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
rdr, err := prm.cli.ObjectGetInit(context.Background(), getPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object reading on client: %w", err)
|
||||
}
|
||||
|
||||
var hdr objectSDK.Object
|
||||
var hdr object.Object
|
||||
|
||||
if !rdr.ReadHeader(&hdr) {
|
||||
_, err = rdr.Close()
|
||||
|
@ -615,11 +590,11 @@ func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
|
|||
|
||||
// HeadObjectRes groups the resulting values of HeadObject operation.
|
||||
type HeadObjectRes struct {
|
||||
hdr *objectSDK.Object
|
||||
hdr *object.Object
|
||||
}
|
||||
|
||||
// Header returns the requested object header.
|
||||
func (x HeadObjectRes) Header() *objectSDK.Object {
|
||||
func (x HeadObjectRes) Header() *object.Object {
|
||||
return x.hdr
|
||||
}
|
||||
|
||||
|
@ -627,26 +602,35 @@ func (x HeadObjectRes) Header() *objectSDK.Object {
|
|||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) {
|
||||
var cliPrm client.PrmObjectHead
|
||||
cliPrm.FromContainer(prm.objAddr.Container())
|
||||
cliPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
headPrm := client.PrmObjectHead{
|
||||
XHeaders: prm.xHeaders,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
Raw: prm.raw,
|
||||
Local: prm.local,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
res, err := prm.cli.ObjectHead(ctx, headPrm)
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.raw {
|
||||
cliPrm.MarkRaw()
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
res, err := prm.cli.ObjectHead(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read object header via client: %w", err)
|
||||
}
|
||||
|
||||
var hdr objectSDK.Object
|
||||
var hdr object.Object
|
||||
|
||||
if !res.ReadHeader(&hdr) {
|
||||
return nil, fmt.Errorf("missing header in response")
|
||||
|
@ -662,11 +646,11 @@ type SearchObjectsPrm struct {
|
|||
commonObjectPrm
|
||||
containerIDPrm
|
||||
|
||||
filters objectSDK.SearchFilters
|
||||
filters object.SearchFilters
|
||||
}
|
||||
|
||||
// SetFilters sets search filters.
|
||||
func (x *SearchObjectsPrm) SetFilters(filters objectSDK.SearchFilters) {
|
||||
func (x *SearchObjectsPrm) SetFilters(filters object.SearchFilters) {
|
||||
x.filters = filters
|
||||
}
|
||||
|
||||
|
@ -683,7 +667,7 @@ func (x SearchObjectsRes) IDList() []oid.ID {
|
|||
// SearchObjects selects objects from the container which match the filters.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
||||
func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
||||
var cliPrm client.PrmObjectSearch
|
||||
cliPrm.InContainer(prm.cnrID)
|
||||
cliPrm.SetFilters(prm.filters)
|
||||
|
@ -702,7 +686,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes
|
|||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
rdr, err := prm.cli.ObjectSearchInit(ctx, cliPrm)
|
||||
rdr, err := prm.cli.ObjectSearchInit(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object search: %w", err)
|
||||
}
|
||||
|
@ -739,7 +723,7 @@ type HashPayloadRangesPrm struct {
|
|||
|
||||
tz bool
|
||||
|
||||
rngs []objectSDK.Range
|
||||
rngs []*object.Range
|
||||
|
||||
salt []byte
|
||||
}
|
||||
|
@ -750,7 +734,7 @@ func (x *HashPayloadRangesPrm) TZ() {
|
|||
}
|
||||
|
||||
// SetRanges sets a list of payload ranges to hash.
|
||||
func (x *HashPayloadRangesPrm) SetRanges(rngs []objectSDK.Range) {
|
||||
func (x *HashPayloadRangesPrm) SetRanges(rngs []*object.Range) {
|
||||
x.rngs = rngs
|
||||
}
|
||||
|
||||
|
@ -773,27 +757,41 @@ func (x HashPayloadRangesRes) HashList() [][]byte {
|
|||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// Returns an error if number of received hashes differs with the number of requested ranges.
|
||||
func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
||||
cs := checksum.SHA256
|
||||
func HashPayloadRanges(prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
||||
var cliPrm client.PrmObjectHash
|
||||
cliPrm.FromContainer(prm.objAddr.Container())
|
||||
cliPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.UseSalt(prm.salt)
|
||||
|
||||
rngs := make([]uint64, 2*len(prm.rngs))
|
||||
|
||||
for i := range prm.rngs {
|
||||
rngs[2*i] = prm.rngs[i].GetOffset()
|
||||
rngs[2*i+1] = prm.rngs[i].GetLength()
|
||||
}
|
||||
|
||||
cliPrm.SetRangeList(rngs...)
|
||||
|
||||
if prm.tz {
|
||||
cs = checksum.TZ
|
||||
cliPrm.TillichZemorAlgo()
|
||||
}
|
||||
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
cliPrm := client.PrmObjectHash{
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
Local: prm.local,
|
||||
Salt: prm.salt,
|
||||
Ranges: prm.rngs,
|
||||
ChecksumType: cs,
|
||||
Session: prm.sessionToken,
|
||||
BearerToken: prm.bearerToken,
|
||||
XHeaders: prm.xHeaders,
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
res, err := prm.cli.ObjectHash(ctx, cliPrm)
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
res, err := prm.cli.ObjectHash(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read payload hashes via client: %w", err)
|
||||
}
|
||||
|
@ -810,11 +808,11 @@ type PayloadRangePrm struct {
|
|||
rawPrm
|
||||
payloadWriterPrm
|
||||
|
||||
rng *objectSDK.Range
|
||||
rng *object.Range
|
||||
}
|
||||
|
||||
// SetRange sets payload range to read.
|
||||
func (x *PayloadRangePrm) SetRange(rng *objectSDK.Range) {
|
||||
func (x *PayloadRangePrm) SetRange(rng *object.Range) {
|
||||
x.rng = rng
|
||||
}
|
||||
|
||||
|
@ -827,23 +825,33 @@ type PayloadRangeRes struct{}
|
|||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
||||
cnr := prm.objAddr.Container()
|
||||
obj := prm.objAddr.Object()
|
||||
func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
||||
var cliPrm client.PrmObjectRange
|
||||
cliPrm.FromContainer(prm.objAddr.Container())
|
||||
cliPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
rangePrm := client.PrmObjectRange{
|
||||
XHeaders: prm.xHeaders,
|
||||
BearerToken: prm.bearerToken,
|
||||
Session: prm.sessionToken,
|
||||
Raw: prm.raw,
|
||||
Local: prm.local,
|
||||
ContainerID: &cnr,
|
||||
ObjectID: &obj,
|
||||
Offset: prm.rng.GetOffset(),
|
||||
Length: prm.rng.GetLength(),
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
rdr, err := prm.cli.ObjectRangeInit(ctx, rangePrm)
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.raw {
|
||||
cliPrm.MarkRaw()
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.SetOffset(prm.rng.GetOffset())
|
||||
cliPrm.SetLength(prm.rng.GetLength())
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
rdr, err := prm.cli.ObjectRangeInit(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init payload reading: %w", err)
|
||||
}
|
||||
|
@ -877,12 +885,12 @@ type SyncContainerRes struct{}
|
|||
// Interrupts on any writer error.
|
||||
//
|
||||
// Panics if a container passed as a parameter is nil.
|
||||
func SyncContainerSettings(ctx context.Context, prm SyncContainerPrm) (*SyncContainerRes, error) {
|
||||
func SyncContainerSettings(prm SyncContainerPrm) (*SyncContainerRes, error) {
|
||||
if prm.c == nil {
|
||||
panic("sync container settings with the network: nil container")
|
||||
}
|
||||
|
||||
err := client.SyncContainerWithNetwork(ctx, prm.c, prm.cli)
|
||||
err := client.SyncContainerWithNetwork(context.Background(), prm.c, prm.cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -12,11 +12,9 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
|
||||
|
@ -38,11 +36,11 @@ func getSDKClientByFlag(cmd *cobra.Command, key *ecdsa.PrivateKey, endpointFlag
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", errInvalidEndpoint, err)
|
||||
}
|
||||
return GetSDKClient(cmd.Context(), cmd, key, addr)
|
||||
return GetSDKClient(cmd, key, addr)
|
||||
}
|
||||
|
||||
// GetSDKClient returns default frostfs-sdk-go client.
|
||||
func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) {
|
||||
func GetSDKClient(cmd *cobra.Command, key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) {
|
||||
var (
|
||||
c client.Client
|
||||
prmInit client.PrmInit
|
||||
|
@ -61,13 +59,10 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
|
|||
|
||||
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
|
||||
}
|
||||
prmDial.SetGRPCDialOptions(
|
||||
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
||||
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()))
|
||||
|
||||
c.Init(prmInit)
|
||||
|
||||
if err := c.Dial(ctx, prmDial); err != nil {
|
||||
if err := c.Dial(prmDial); err != nil {
|
||||
return nil, fmt.Errorf("can't init SDK client: %w", err)
|
||||
}
|
||||
|
||||
|
@ -87,7 +82,7 @@ func GetCurrentEpoch(ctx context.Context, cmd *cobra.Command, endpoint string) (
|
|||
return 0, fmt.Errorf("can't generate key to sign query: %w", err)
|
||||
}
|
||||
|
||||
c, err := GetSDKClient(ctx, cmd, key, addr)
|
||||
c, err := GetSDKClient(cmd, key, addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type spanKey struct{}
|
||||
|
||||
// StopClientCommandSpan stops tracing span for the command and prints trace ID on the standard output.
|
||||
func StopClientCommandSpan(cmd *cobra.Command, _ []string) {
|
||||
span, ok := cmd.Context().Value(spanKey{}).(trace.Span)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
span.End()
|
||||
|
||||
// Noop provider cannot fail on flush.
|
||||
_ = tracing.Shutdown(cmd.Context())
|
||||
|
||||
cmd.PrintErrf("Trace ID: %s\n", span.SpanContext().TraceID())
|
||||
}
|
||||
|
||||
// StartClientCommandSpan starts tracing span for the command.
|
||||
func StartClientCommandSpan(cmd *cobra.Command) {
|
||||
enableTracing, err := cmd.Flags().GetBool(commonflags.TracingFlag)
|
||||
if err != nil || !enableTracing {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = tracing.Setup(cmd.Context(), tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.NoOpExporter,
|
||||
Service: "frostfs-cli",
|
||||
Version: misc.Version,
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "init tracing: %w", err)
|
||||
|
||||
var components sort.StringSlice
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
components = append(components, c.Name())
|
||||
}
|
||||
for i, j := 0, len(components)-1; i < j; {
|
||||
components.Swap(i, j)
|
||||
i++
|
||||
j--
|
||||
}
|
||||
|
||||
operation := strings.Join(components, ".")
|
||||
ctx, span := tracing.StartSpanFromContext(cmd.Context(), operation)
|
||||
ctx = context.WithValue(ctx, spanKey{}, span)
|
||||
cmd.SetContext(ctx)
|
||||
}
|
|
@ -47,9 +47,6 @@ const (
|
|||
|
||||
OIDFlag = "oid"
|
||||
OIDFlagUsage = "Object ID."
|
||||
|
||||
TracingFlag = "trace"
|
||||
TracingFlagUsage = "Generate trace ID and print it."
|
||||
)
|
||||
|
||||
// Init adds common flags to the command:
|
||||
|
@ -57,14 +54,12 @@ const (
|
|||
// - WalletPath,
|
||||
// - Account,
|
||||
// - RPC,
|
||||
// - Tracing,
|
||||
// - Timeout.
|
||||
func Init(cmd *cobra.Command) {
|
||||
InitWithoutRPC(cmd)
|
||||
|
||||
ff := cmd.Flags()
|
||||
ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage)
|
||||
ff.Bool(TracingFlag, false, TracingFlagUsage)
|
||||
ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage)
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ var accountingBalanceCmd = &cobra.Command{
|
|||
prm.SetClient(cli)
|
||||
prm.SetAccount(idUser)
|
||||
|
||||
res, err := internalclient.BalanceOf(cmd.Context(), prm)
|
||||
res, err := internalclient.BalanceOf(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
// print to stdout
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package accounting
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -18,9 +17,7 @@ var Cmd = &cobra.Command{
|
|||
_ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath))
|
||||
_ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account))
|
||||
_ = viper.BindPFlag(commonflags.RPC, flags.Lookup(commonflags.RPC))
|
||||
common.StartClientCommandSpan(cmd)
|
||||
},
|
||||
PersistentPostRun: common.StopClientCommandSpan,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -24,7 +24,6 @@ const (
|
|||
ownerFlag = "owner"
|
||||
outFlag = "out"
|
||||
jsonFlag = commonflags.JSON
|
||||
impersonateFlag = "impersonate"
|
||||
)
|
||||
|
||||
var createCmd = &cobra.Command{
|
||||
|
@ -40,20 +39,19 @@ is set to current epoch + n.
|
|||
}
|
||||
|
||||
func init() {
|
||||
createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table (mutually exclusive with --impersonate flag)")
|
||||
createCmd.Flags().StringP(issuedAtFlag, "i", "+0", "Epoch to issue token at")
|
||||
createCmd.Flags().StringP(notValidBeforeFlag, "n", "+0", "Not valid before epoch")
|
||||
createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table")
|
||||
createCmd.Flags().StringP(issuedAtFlag, "i", "", "Epoch to issue token at")
|
||||
createCmd.Flags().StringP(notValidBeforeFlag, "n", "", "Not valid before epoch")
|
||||
createCmd.Flags().StringP(commonflags.ExpireAt, "x", "", "The last active epoch for the token")
|
||||
createCmd.Flags().StringP(ownerFlag, "o", "", "Token owner")
|
||||
createCmd.Flags().String(outFlag, "", "File to write token to")
|
||||
createCmd.Flags().Bool(jsonFlag, false, "Output token in JSON")
|
||||
createCmd.Flags().Bool(impersonateFlag, false, "Mark token as impersonate to consider the token signer as the request owner (mutually exclusive with --eacl flag)")
|
||||
createCmd.Flags().StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
|
||||
|
||||
createCmd.MarkFlagsMutuallyExclusive(eaclFlag, impersonateFlag)
|
||||
|
||||
_ = cobra.MarkFlagFilename(createCmd.Flags(), eaclFlag)
|
||||
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), issuedAtFlag)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), notValidBeforeFlag)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), commonflags.ExpireAt)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), ownerFlag)
|
||||
_ = cobra.MarkFlagRequired(createCmd.Flags(), outFlag)
|
||||
|
@ -70,14 +68,10 @@ func createToken(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "can't parse --"+notValidBeforeFlag+" flag: %w", err)
|
||||
|
||||
if iatRelative || expRelative || nvbRelative {
|
||||
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
|
||||
if len(endpoint) == 0 {
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", fmt.Errorf("'%s' flag value must be specified", commonflags.RPC))
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
|
||||
currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint)
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", err)
|
||||
|
||||
|
@ -107,9 +101,6 @@ func createToken(cmd *cobra.Command, _ []string) {
|
|||
b.SetIat(iat)
|
||||
b.ForUser(ownerID)
|
||||
|
||||
impersonate, _ := cmd.Flags().GetBool(impersonateFlag)
|
||||
b.SetImpersonate(impersonate)
|
||||
|
||||
eaclPath, _ := cmd.Flags().GetString(eaclFlag)
|
||||
if eaclPath != "" {
|
||||
table := eaclSDK.NewTable()
|
||||
|
|
|
@ -13,10 +13,10 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
subnetid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/subnet/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -30,6 +30,7 @@ var (
|
|||
containerNnsName string
|
||||
containerNnsZone string
|
||||
containerNoTimestamp bool
|
||||
containerSubnet string
|
||||
force bool
|
||||
)
|
||||
|
||||
|
@ -49,7 +50,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
|
|||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
resmap, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
|
||||
resmap, err := internalclient.NetMapSnapshot(prm)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot to validate container placement, "+
|
||||
"use --force option to skip this check: %w", err)
|
||||
|
||||
|
@ -69,6 +70,15 @@ It will be stored in sidechain when inner ring will accepts it.`,
|
|||
}
|
||||
}
|
||||
|
||||
if containerSubnet != "" {
|
||||
var subnetID subnetid.ID
|
||||
|
||||
err = subnetID.DecodeString(containerSubnet)
|
||||
commonCmd.ExitOnErr(cmd, "could not parse subnetID: %w", err)
|
||||
|
||||
placementPolicy.RestrictSubnet(subnetID)
|
||||
}
|
||||
|
||||
var cnr container.Container
|
||||
cnr.Init()
|
||||
|
||||
|
@ -97,18 +107,18 @@ It will be stored in sidechain when inner ring will accepts it.`,
|
|||
syncContainerPrm.SetClient(cli)
|
||||
syncContainerPrm.SetContainer(&cnr)
|
||||
|
||||
_, err = internalclient.SyncContainerSettings(cmd.Context(), syncContainerPrm)
|
||||
_, err = internalclient.SyncContainerSettings(syncContainerPrm)
|
||||
commonCmd.ExitOnErr(cmd, "syncing container's settings rpc error: %w", err)
|
||||
|
||||
putPrm := internalclient.PutContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerPut{
|
||||
Container: &cnr,
|
||||
Session: tok,
|
||||
},
|
||||
var putPrm internalclient.PutContainerPrm
|
||||
putPrm.SetClient(cli)
|
||||
putPrm.SetContainer(cnr)
|
||||
|
||||
if tok != nil {
|
||||
putPrm.WithinSession(*tok)
|
||||
}
|
||||
|
||||
res, err := internalclient.PutContainer(cmd.Context(), putPrm)
|
||||
res, err := internalclient.PutContainer(putPrm)
|
||||
commonCmd.ExitOnErr(cmd, "put container rpc error: %w", err)
|
||||
|
||||
id := res.ID()
|
||||
|
@ -118,17 +128,14 @@ It will be stored in sidechain when inner ring will accepts it.`,
|
|||
if containerAwait {
|
||||
cmd.Println("awaiting...")
|
||||
|
||||
getPrm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
var getPrm internalclient.GetContainerPrm
|
||||
getPrm.SetClient(cli)
|
||||
getPrm.SetContainer(id)
|
||||
|
||||
for i := 0; i < awaitTimeout; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||
_, err := internalclient.GetContainer(getPrm)
|
||||
if err == nil {
|
||||
cmd.Println("container has been persisted on sidechain")
|
||||
return
|
||||
|
@ -145,7 +152,6 @@ func initContainerCreateCmd() {
|
|||
|
||||
// Init common flags
|
||||
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
|
||||
flags.Bool(commonflags.TracingFlag, false, commonflags.TracingFlagUsage)
|
||||
flags.DurationP(commonflags.Timeout, commonflags.TimeoutShorthand, commonflags.TimeoutDefault, commonflags.TimeoutUsage)
|
||||
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
|
||||
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
|
||||
|
@ -160,6 +166,7 @@ func initContainerCreateCmd() {
|
|||
flags.StringVar(&containerNnsName, "nns-name", "", "Container nns name attribute")
|
||||
flags.StringVar(&containerNnsZone, "nns-zone", "", "Container nns zone attribute")
|
||||
flags.BoolVar(&containerNoTimestamp, "disable-timestamp", false, "Disable timestamp container attribute")
|
||||
flags.StringVar(&containerSubnet, "subnet", "", "String representation of container subnetwork")
|
||||
flags.BoolVarP(&force, commonflags.ForceFlag, commonflags.ForceFlagShorthand, false,
|
||||
"Skip placement validity check")
|
||||
}
|
||||
|
@ -185,12 +192,12 @@ func parseContainerPolicy(cmd *cobra.Command, policyString string) (*netmap.Plac
|
|||
return &result, nil
|
||||
}
|
||||
|
||||
if err := result.UnmarshalJSON([]byte(policyString)); err == nil {
|
||||
if err = result.UnmarshalJSON([]byte(policyString)); err == nil {
|
||||
common.PrintVerbose(cmd, "Parsed JSON encoded policy")
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't parse placement policy: %w", err)
|
||||
return nil, errors.New("can't parse placement policy")
|
||||
}
|
||||
|
||||
func parseAttributes(dst *container.Container, attributes []string) error {
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -31,14 +30,11 @@ Only owner of the container has a permission to remove container.`,
|
|||
if force, _ := cmd.Flags().GetBool(commonflags.ForceFlag); !force {
|
||||
common.PrintVerbose(cmd, "Reading the container to check ownership...")
|
||||
|
||||
getPrm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
var getPrm internalclient.GetContainerPrm
|
||||
getPrm.SetClient(cli)
|
||||
getPrm.SetContainer(id)
|
||||
|
||||
resGet, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||
resGet, err := internalclient.GetContainer(getPrm)
|
||||
commonCmd.ExitOnErr(cmd, "can't get the container: %w", err)
|
||||
|
||||
owner := resGet.Container().Owner()
|
||||
|
@ -76,7 +72,7 @@ Only owner of the container has a permission to remove container.`,
|
|||
|
||||
common.PrintVerbose(cmd, "Searching for LOCK objects...")
|
||||
|
||||
res, err := internalclient.SearchObjects(cmd.Context(), searchPrm)
|
||||
res, err := internalclient.SearchObjects(searchPrm)
|
||||
commonCmd.ExitOnErr(cmd, "can't search for LOCK objects: %w", err)
|
||||
|
||||
if len(res.IDList()) != 0 {
|
||||
|
@ -87,15 +83,15 @@ Only owner of the container has a permission to remove container.`,
|
|||
}
|
||||
}
|
||||
|
||||
delPrm := internalclient.DeleteContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerDelete{
|
||||
ContainerID: &id,
|
||||
Session: tok,
|
||||
},
|
||||
var delPrm internalclient.DeleteContainerPrm
|
||||
delPrm.SetClient(cli)
|
||||
delPrm.SetContainer(id)
|
||||
|
||||
if tok != nil {
|
||||
delPrm.WithinSession(*tok)
|
||||
}
|
||||
|
||||
_, err := internalclient.DeleteContainer(cmd.Context(), delPrm)
|
||||
_, err := internalclient.DeleteContainer(delPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
cmd.Println("container delete method invoked")
|
||||
|
@ -103,17 +99,14 @@ Only owner of the container has a permission to remove container.`,
|
|||
if containerAwait {
|
||||
cmd.Println("awaiting...")
|
||||
|
||||
getPrm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
var getPrm internalclient.GetContainerPrm
|
||||
getPrm.SetClient(cli)
|
||||
getPrm.SetContainer(id)
|
||||
|
||||
for i := 0; i < awaitTimeout; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||
_, err := internalclient.GetContainer(getPrm)
|
||||
if err != nil {
|
||||
cmd.Println("container has been removed:", containerID)
|
||||
return
|
||||
|
@ -131,7 +124,6 @@ func initContainerDeleteCmd() {
|
|||
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
|
||||
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
|
||||
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
|
||||
flags.Bool(commonflags.TracingFlag, false, commonflags.TracingFlagUsage)
|
||||
|
||||
flags.StringVar(&containerID, commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
flags.BoolVar(&containerAwait, "await", false, "Block execution until container is removed")
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
|
@ -148,14 +147,11 @@ func getContainer(cmd *cobra.Command) (container.Container, *ecdsa.PrivateKey) {
|
|||
pk = key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
prm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
var prm internalclient.GetContainerPrm
|
||||
prm.SetClient(cli)
|
||||
prm.SetContainer(id)
|
||||
|
||||
res, err := internalclient.GetContainer(cmd.Context(), prm)
|
||||
res, err := internalclient.GetContainer(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
cnr = res.Container()
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -21,14 +20,11 @@ var getExtendedACLCmd = &cobra.Command{
|
|||
pk := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
eaclPrm := internalclient.EACLPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerEACL{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
var eaclPrm internalclient.EACLPrm
|
||||
eaclPrm.SetClient(cli)
|
||||
eaclPrm.SetContainer(id)
|
||||
|
||||
res, err := internalclient.EACL(cmd.Context(), eaclPrm)
|
||||
res, err := internalclient.EACL(eaclPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
eaclTable := res.EACL()
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -17,14 +16,12 @@ import (
|
|||
const (
|
||||
flagListPrintAttr = "with-attr"
|
||||
flagListContainerOwner = "owner"
|
||||
flagListName = "name"
|
||||
)
|
||||
|
||||
// flag vars of list command.
|
||||
var (
|
||||
flagVarListPrintAttr bool
|
||||
flagVarListContainerOwner string
|
||||
flagVarListName string
|
||||
)
|
||||
|
||||
var listContainersCmd = &cobra.Command{
|
||||
|
@ -49,42 +46,30 @@ var listContainersCmd = &cobra.Command{
|
|||
prm.SetClient(cli)
|
||||
prm.SetAccount(idUser)
|
||||
|
||||
res, err := internalclient.ListContainers(cmd.Context(), prm)
|
||||
res, err := internalclient.ListContainers(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
prmGet := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
}
|
||||
var prmGet internalclient.GetContainerPrm
|
||||
prmGet.SetClient(cli)
|
||||
|
||||
containerIDs := res.IDList()
|
||||
for _, cnrID := range containerIDs {
|
||||
if flagVarListName == "" && !flagVarListPrintAttr {
|
||||
cmd.Println(cnrID.String())
|
||||
continue
|
||||
}
|
||||
|
||||
cnrID := cnrID
|
||||
prmGet.ClientParams.ContainerID = &cnrID
|
||||
res, err := internalclient.GetContainer(cmd.Context(), prmGet)
|
||||
if err != nil {
|
||||
cmd.Printf(" failed to read attributes: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
cnr := res.Container()
|
||||
if cnrName := containerSDK.Name(cnr); flagVarListName != "" && cnrName != flagVarListName {
|
||||
continue
|
||||
}
|
||||
cmd.Println(cnrID.String())
|
||||
list := res.IDList()
|
||||
for i := range list {
|
||||
cmd.Println(list[i].String())
|
||||
|
||||
if flagVarListPrintAttr {
|
||||
cnr.IterateAttributes(func(key, val string) {
|
||||
if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
|
||||
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97
|
||||
// Use dedicated method to skip system attributes.
|
||||
cmd.Printf(" %s: %s\n", key, val)
|
||||
}
|
||||
})
|
||||
prmGet.SetContainer(list[i])
|
||||
|
||||
res, err := internalclient.GetContainer(prmGet)
|
||||
if err == nil {
|
||||
res.Container().IterateAttributes(func(key, val string) {
|
||||
if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
|
||||
// FIXME(@cthulhu-rider): neofs-sdk-go#314 use dedicated method to skip system attributes
|
||||
cmd.Printf(" %s: %s\n", key, val)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
cmd.Printf(" failed to read attributes: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -95,9 +80,6 @@ func initContainerListContainersCmd() {
|
|||
|
||||
flags := listContainersCmd.Flags()
|
||||
|
||||
flags.StringVar(&flagVarListName, flagListName, "",
|
||||
"List containers by the attribute name",
|
||||
)
|
||||
flags.StringVar(&flagVarListContainerOwner, flagListContainerOwner, "",
|
||||
"Owner of containers (omit to use owner from private key)",
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -31,7 +31,7 @@ var listContainerObjectsCmd = &cobra.Command{
|
|||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := parseContainerID(cmd)
|
||||
|
||||
filters := new(objectSDK.SearchFilters)
|
||||
filters := new(object.SearchFilters)
|
||||
filters.AddRootFilter() // search only user created objects
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, key.GetOrGenerate(cmd), commonflags.RPC)
|
||||
|
@ -51,7 +51,7 @@ var listContainerObjectsCmd = &cobra.Command{
|
|||
prmSearch.SetContainerID(id)
|
||||
prmSearch.SetFilters(*filters)
|
||||
|
||||
res, err := internalclient.SearchObjects(cmd.Context(), prmSearch)
|
||||
res, err := internalclient.SearchObjects(prmSearch)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
objectIDs := res.IDList()
|
||||
|
@ -65,14 +65,13 @@ var listContainerObjectsCmd = &cobra.Command{
|
|||
addr.SetObject(objectIDs[i])
|
||||
prmHead.SetAddress(addr)
|
||||
|
||||
resHead, err := internalclient.HeadObject(cmd.Context(), prmHead)
|
||||
resHead, err := internalclient.HeadObject(prmHead)
|
||||
if err == nil {
|
||||
attrs := resHead.Header().Attributes()
|
||||
for i := range attrs {
|
||||
attrKey := attrs[i].Key()
|
||||
if !strings.HasPrefix(attrKey, v2object.SysAttributePrefix) && !strings.HasPrefix(attrKey, v2object.SysAttributePrefixNeoFS) {
|
||||
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97
|
||||
// Use dedicated method to skip system attributes.
|
||||
// FIXME(@cthulhu-rider): neofs-sdk-go#226 use dedicated method to skip system attributes
|
||||
cmd.Printf(" %s: %s\n", attrKey, attrs[i].Value())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ var containerNodesCmd = &cobra.Command{
|
|||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
resmap, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
|
||||
resmap, err := internalclient.NetMapSnapshot(prm)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot", err)
|
||||
|
||||
var id cid.ID
|
||||
|
|
|
@ -1,233 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type policyPlaygroundREPL struct {
|
||||
cmd *cobra.Command
|
||||
args []string
|
||||
nodes map[string]netmap.NodeInfo
|
||||
}
|
||||
|
||||
func newPolicyPlaygroundREPL(cmd *cobra.Command, args []string) (*policyPlaygroundREPL, error) {
|
||||
return &policyPlaygroundREPL{
|
||||
cmd: cmd,
|
||||
args: args,
|
||||
nodes: map[string]netmap.NodeInfo{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleLs(args []string) error {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("too many arguments for command 'ls': got %d, want 0", len(args))
|
||||
}
|
||||
i := 1
|
||||
for id, node := range repl.nodes {
|
||||
var attrs []string
|
||||
node.IterateAttributes(func(k, v string) {
|
||||
attrs = append(attrs, fmt.Sprintf("%s:%q", k, v))
|
||||
})
|
||||
fmt.Printf("\t%2d: id=%s attrs={%v}\n", i, id, strings.Join(attrs, " "))
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleAdd(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("too few arguments for command 'add': got %d, want >0", len(args))
|
||||
}
|
||||
id := args[0]
|
||||
key, err := hex.DecodeString(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("node id must be a hex string: got %q: %v", id, err)
|
||||
}
|
||||
node := repl.nodes[id]
|
||||
node.SetPublicKey(key)
|
||||
for _, attr := range args[1:] {
|
||||
kv := strings.Split(attr, ":")
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("node attributes must be in the format 'KEY:VALUE': got %q", attr)
|
||||
}
|
||||
node.SetAttribute(kv[0], kv[1])
|
||||
}
|
||||
repl.nodes[id] = node
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleLoad(args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("too few arguments for command 'add': got %d, want 1", len(args))
|
||||
}
|
||||
|
||||
jsonNetmap := map[string]map[string]string{}
|
||||
|
||||
b, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading netmap file %q: %v", args[0], err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &jsonNetmap); err != nil {
|
||||
return fmt.Errorf("decoding json netmap: %v", err)
|
||||
}
|
||||
|
||||
repl.nodes = make(map[string]netmap.NodeInfo)
|
||||
for id, attrs := range jsonNetmap {
|
||||
key, err := hex.DecodeString(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("node id must be a hex string: got %q: %v", id, err)
|
||||
}
|
||||
|
||||
node := repl.nodes[id]
|
||||
node.SetPublicKey(key)
|
||||
for k, v := range attrs {
|
||||
node.SetAttribute(k, v)
|
||||
}
|
||||
repl.nodes[id] = node
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleRemove(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("too few arguments for command 'remove': got %d, want >0", len(args))
|
||||
}
|
||||
id := args[0]
|
||||
if _, exists := repl.nodes[id]; exists {
|
||||
delete(repl.nodes, id)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("node not found: id=%q", id)
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleEval(args []string) error {
|
||||
policyStr := strings.TrimSpace(strings.Join(args, " "))
|
||||
var nodes [][]netmap.NodeInfo
|
||||
nm := repl.netMap()
|
||||
|
||||
if strings.HasPrefix(policyStr, "CBF") || strings.HasPrefix(policyStr, "SELECT") || strings.HasPrefix(policyStr, "FILTER") {
|
||||
// Assume that the input is a partial SELECT-FILTER expression.
|
||||
// Full inline policies always start with UNIQUE or REP keywords,
|
||||
// or different prefixes when it's the case of an external file.
|
||||
sfExpr, err := netmap.DecodeSelectFilterString(policyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing select-filter expression: %v", err)
|
||||
}
|
||||
nodes, err = nm.SelectFilterNodes(sfExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building select-filter nodes: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Assume that the input is a full policy or input file otherwise.
|
||||
placementPolicy, err := parseContainerPolicy(repl.cmd, policyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing placement policy: %v", err)
|
||||
}
|
||||
nodes, err = nm.ContainerNodes(*placementPolicy, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building container nodes: %v", err)
|
||||
}
|
||||
}
|
||||
for i, ns := range nodes {
|
||||
var ids []string
|
||||
for _, node := range ns {
|
||||
ids = append(ids, hex.EncodeToString(node.PublicKey()))
|
||||
}
|
||||
fmt.Printf("\t%2d: %v\n", i+1, ids)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) netMap() netmap.NetMap {
|
||||
var nm netmap.NetMap
|
||||
var nodes []netmap.NodeInfo
|
||||
for _, node := range repl.nodes {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
nm.SetNodes(nodes)
|
||||
return nm
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) run() error {
|
||||
if len(viper.GetString(commonflags.RPC)) > 0 {
|
||||
key := key.GetOrGenerate(repl.cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(repl.cmd, key, commonflags.RPC)
|
||||
|
||||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
resp, err := internalclient.NetMapSnapshot(repl.cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(repl.cmd, "unable to get netmap snapshot to populate initial netmap: %w", err)
|
||||
|
||||
for _, node := range resp.NetMap().Nodes() {
|
||||
id := hex.EncodeToString(node.PublicKey())
|
||||
repl.nodes[id] = node
|
||||
}
|
||||
}
|
||||
|
||||
cmdHandlers := map[string]func([]string) error{
|
||||
"list": repl.handleLs,
|
||||
"ls": repl.handleLs,
|
||||
"add": repl.handleAdd,
|
||||
"load": repl.handleLoad,
|
||||
"remove": repl.handleRemove,
|
||||
"rm": repl.handleRemove,
|
||||
"eval": repl.handleEval,
|
||||
}
|
||||
for reader := bufio.NewReader(os.Stdin); ; {
|
||||
fmt.Print("> ")
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("reading line: %v", err)
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
cmd := parts[0]
|
||||
handler, exists := cmdHandlers[cmd]
|
||||
if exists {
|
||||
if err := handler(parts[1:]); err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("error: unknown command %q\n", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var policyPlaygroundCmd = &cobra.Command{
|
||||
Use: "policy-playground",
|
||||
Short: "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.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
repl, err := newPolicyPlaygroundREPL(cmd, args)
|
||||
commonCmd.ExitOnErr(cmd, "could not create policy playground: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run())
|
||||
},
|
||||
}
|
||||
|
||||
func initContainerPolicyPlaygroundCmd() {
|
||||
commonflags.Init(policyPlaygroundCmd)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -16,9 +15,7 @@ var Cmd = &cobra.Command{
|
|||
// the viper before execution
|
||||
commonflags.Bind(cmd)
|
||||
commonflags.BindAPI(cmd)
|
||||
common.StartClientCommandSpan(cmd)
|
||||
},
|
||||
PersistentPostRun: common.StopClientCommandSpan,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -31,7 +28,6 @@ func init() {
|
|||
getExtendedACLCmd,
|
||||
setExtendedACLCmd,
|
||||
containerNodesCmd,
|
||||
policyPlaygroundCmd,
|
||||
}
|
||||
|
||||
Cmd.AddCommand(containerChildCommand...)
|
||||
|
@ -44,7 +40,6 @@ func init() {
|
|||
initContainerGetEACLCmd()
|
||||
initContainerSetEACLCmd()
|
||||
initContainerNodesCmd()
|
||||
initContainerPolicyPlaygroundCmd()
|
||||
|
||||
for _, containerCommand := range containerChildCommand {
|
||||
commonflags.InitAPI(containerCommand)
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -39,7 +38,7 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
|
|||
if !flagVarsSetEACL.noPreCheck {
|
||||
cmd.Println("Checking the ability to modify access rights in the container...")
|
||||
|
||||
extendable, err := internalclient.IsACLExtendable(cmd.Context(), cli, id)
|
||||
extendable, err := internalclient.IsACLExtendable(cli, id)
|
||||
commonCmd.ExitOnErr(cmd, "Extensibility check failure: %w", err)
|
||||
|
||||
if !extendable {
|
||||
|
@ -49,15 +48,15 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
|
|||
cmd.Println("ACL extension is enabled in the container, continue processing.")
|
||||
}
|
||||
|
||||
setEACLPrm := internalclient.SetEACLPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerSetEACL{
|
||||
Table: eaclTable,
|
||||
Session: tok,
|
||||
},
|
||||
var setEACLPrm internalclient.SetEACLPrm
|
||||
setEACLPrm.SetClient(cli)
|
||||
setEACLPrm.SetTable(*eaclTable)
|
||||
|
||||
if tok != nil {
|
||||
setEACLPrm.WithinSession(*tok)
|
||||
}
|
||||
|
||||
_, err := internalclient.SetEACL(cmd.Context(), setEACLPrm)
|
||||
_, err := internalclient.SetEACL(setEACLPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
if containerAwait {
|
||||
|
@ -66,17 +65,14 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
|
|||
|
||||
cmd.Println("awaiting...")
|
||||
|
||||
getEACLPrm := internalclient.EACLPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerEACL{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
var getEACLPrm internalclient.EACLPrm
|
||||
getEACLPrm.SetClient(cli)
|
||||
getEACLPrm.SetContainer(id)
|
||||
|
||||
for i := 0; i < awaitTimeout; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
res, err := internalclient.EACL(cmd.Context(), getEACLPrm)
|
||||
res, err := internalclient.EACL(getEACLPrm)
|
||||
if err == nil {
|
||||
// compare binary values because EACL could have been set already
|
||||
table := res.EACL()
|
||||
|
|
|
@ -8,14 +8,11 @@ import (
|
|||
"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",
|
||||
Use: "evacuate",
|
||||
Short: "Evacuate objects from shard",
|
||||
Long: "Evacuate objects from shard to other shards",
|
||||
Run: evacuateShard,
|
||||
}
|
||||
|
||||
func evacuateShard(cmd *cobra.Command, _ []string) {
|
||||
|
@ -23,7 +20,7 @@ func evacuateShard(cmd *cobra.Command, _ []string) {
|
|||
|
||||
req := &control.EvacuateShardRequest{Body: new(control.EvacuateShardRequest_Body)}
|
||||
req.Body.Shard_ID = getShardIDList(cmd)
|
||||
req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(ignoreErrorsFlag)
|
||||
req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(dumpIgnoreErrorsFlag)
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
|
@ -50,7 +47,7 @@ func initControlEvacuateShardCmd() {
|
|||
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")
|
||||
flags.Bool(dumpIgnoreErrorsFlag, false, "Skip invalid/unreadable objects")
|
||||
|
||||
evacuateShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
||||
}
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"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/local_object_storage/shard"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
clientSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
awaitFlag = "await"
|
||||
noProgressFlag = "no-progress"
|
||||
)
|
||||
|
||||
var evacuationShardCmd = &cobra.Command{
|
||||
Use: "evacuation",
|
||||
Short: "Objects evacuation from shard",
|
||||
Long: "Objects evacuation from shard to other shards",
|
||||
}
|
||||
|
||||
var startEvacuationShardCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start evacuate objects from shard",
|
||||
Long: "Start evacuate objects from shard to other shards",
|
||||
Run: startEvacuateShard,
|
||||
}
|
||||
|
||||
var getEvacuationShardStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Get evacuate objects from shard status",
|
||||
Long: "Get evacuate objects from shard to other shards status",
|
||||
Run: getEvacuateShardStatus,
|
||||
}
|
||||
|
||||
var stopEvacuationShardCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "Stop running evacuate process",
|
||||
Long: "Stop running evacuate process from shard to other shards",
|
||||
Run: stopEvacuateShardStatus,
|
||||
}
|
||||
|
||||
func startEvacuateShard(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag)
|
||||
|
||||
req := &control.StartShardEvacuationRequest{
|
||||
Body: &control.StartShardEvacuationRequest_Body{
|
||||
Shard_ID: getShardIDList(cmd),
|
||||
IgnoreErrors: ignoreErrors,
|
||||
},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.StartShardEvacuationResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.StartShardEvacuation(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "Start evacuate shards failed, rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Shard evacuation has been successfully started.")
|
||||
|
||||
if awaitCompletion, _ := cmd.Flags().GetBool(awaitFlag); awaitCompletion {
|
||||
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
|
||||
waitEvacuateCompletion(cmd, pk, cli, !noProgress, true)
|
||||
}
|
||||
}
|
||||
|
||||
func getEvacuateShardStatus(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
req := &control.GetShardEvacuationStatusRequest{
|
||||
Body: &control.GetShardEvacuationStatusRequest_Body{},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.GetShardEvacuationStatusResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.GetShardEvacuationStatus(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "Get evacuate shards status failed, rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
printStatus(cmd, resp)
|
||||
}
|
||||
|
||||
func stopEvacuateShardStatus(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
req := &control.StopShardEvacuationRequest{
|
||||
Body: &control.StopShardEvacuationRequest_Body{},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.StopShardEvacuationResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.StopShardEvacuation(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "Stop evacuate shards failed, rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
waitEvacuateCompletion(cmd, pk, cli, false, false)
|
||||
|
||||
cmd.Println("Evacuation stopped.")
|
||||
}
|
||||
|
||||
func waitEvacuateCompletion(cmd *cobra.Command, pk *ecdsa.PrivateKey, cli *clientSDK.Client, printProgress, printCompleted bool) {
|
||||
const statusPollingInterval = 1 * time.Second
|
||||
const reportIntervalSeconds = 5
|
||||
var resp *control.GetShardEvacuationStatusResponse
|
||||
reportResponse := new(atomic.Pointer[control.GetShardEvacuationStatusResponse])
|
||||
pollingCompleted := make(chan struct{})
|
||||
progressReportCompleted := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(progressReportCompleted)
|
||||
if !printProgress {
|
||||
return
|
||||
}
|
||||
cmd.Printf("Progress will be reported every %d seconds.\n", reportIntervalSeconds)
|
||||
for {
|
||||
select {
|
||||
case <-pollingCompleted:
|
||||
return
|
||||
case <-time.After(reportIntervalSeconds * time.Second):
|
||||
r := reportResponse.Load()
|
||||
if r == nil || r.GetBody().GetStatus() == control.GetShardEvacuationStatusResponse_Body_COMPLETED {
|
||||
continue
|
||||
}
|
||||
printStatus(cmd, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
req := &control.GetShardEvacuationStatusRequest{
|
||||
Body: &control.GetShardEvacuationStatusRequest_Body{},
|
||||
}
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.GetShardEvacuationStatus(client, req)
|
||||
return err
|
||||
})
|
||||
|
||||
reportResponse.Store(resp)
|
||||
|
||||
if err != nil {
|
||||
commonCmd.ExitOnErr(cmd, "Failed to get evacuate status, rpc error: %w", err)
|
||||
return
|
||||
}
|
||||
if resp.GetBody().GetStatus() != control.GetShardEvacuationStatusResponse_Body_RUNNING {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(statusPollingInterval)
|
||||
}
|
||||
close(pollingCompleted)
|
||||
<-progressReportCompleted
|
||||
if printCompleted {
|
||||
printCompletedStatusMessage(cmd, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func printCompletedStatusMessage(cmd *cobra.Command, resp *control.GetShardEvacuationStatusResponse) {
|
||||
cmd.Println("Shard evacuation has been completed.")
|
||||
sb := &strings.Builder{}
|
||||
appendShardIDs(sb, resp)
|
||||
appendCounts(sb, resp)
|
||||
appendError(sb, resp)
|
||||
appendStartedAt(sb, resp)
|
||||
appendDuration(sb, resp)
|
||||
cmd.Println(sb.String())
|
||||
}
|
||||
|
||||
func printStatus(cmd *cobra.Command, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetStatus() == control.GetShardEvacuationStatusResponse_Body_EVACUATE_SHARD_STATUS_UNDEFINED {
|
||||
cmd.Println("There is no running or completed evacuation.")
|
||||
return
|
||||
}
|
||||
sb := &strings.Builder{}
|
||||
appendShardIDs(sb, resp)
|
||||
appendStatus(sb, resp)
|
||||
appendCounts(sb, resp)
|
||||
appendError(sb, resp)
|
||||
appendStartedAt(sb, resp)
|
||||
appendDuration(sb, resp)
|
||||
appendEstimation(sb, resp)
|
||||
cmd.Println(sb.String())
|
||||
}
|
||||
|
||||
func appendEstimation(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetStatus() != control.GetShardEvacuationStatusResponse_Body_RUNNING ||
|
||||
resp.GetBody().GetDuration() == nil ||
|
||||
resp.GetBody().GetTotal() == 0 ||
|
||||
resp.GetBody().GetEvacuated()+resp.GetBody().GetFailed() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
durationSeconds := float64(resp.GetBody().GetDuration().GetSeconds())
|
||||
evacuated := float64(resp.GetBody().GetEvacuated() + resp.GetBody().GetFailed())
|
||||
avgObjEvacuationTimeSeconds := durationSeconds / evacuated
|
||||
objectsLeft := float64(resp.GetBody().GetTotal()) - evacuated
|
||||
leftSeconds := avgObjEvacuationTimeSeconds * objectsLeft
|
||||
leftMinutes := int(leftSeconds / 60)
|
||||
|
||||
sb.WriteString(fmt.Sprintf(" Estimated time left: %d minutes.", leftMinutes))
|
||||
}
|
||||
|
||||
func appendDuration(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetDuration() != nil {
|
||||
duration := time.Second * time.Duration(resp.GetBody().GetDuration().GetSeconds())
|
||||
hour := int(duration.Seconds() / 3600)
|
||||
minute := int(duration.Seconds()/60) % 60
|
||||
second := int(duration.Seconds()) % 60
|
||||
sb.WriteString(fmt.Sprintf(" Duration: %02d:%02d:%02d.", hour, minute, second))
|
||||
}
|
||||
}
|
||||
|
||||
func appendStartedAt(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetStartedAt() != nil {
|
||||
startedAt := time.Unix(resp.GetBody().GetStartedAt().GetValue(), 0).UTC()
|
||||
sb.WriteString(fmt.Sprintf(" Started at: %s UTC.", startedAt.Format(time.RFC3339)))
|
||||
}
|
||||
}
|
||||
|
||||
func appendError(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if len(resp.Body.GetErrorMessage()) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" Error: %s.", resp.Body.GetErrorMessage()))
|
||||
}
|
||||
}
|
||||
|
||||
func appendStatus(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
var status string
|
||||
switch resp.GetBody().GetStatus() {
|
||||
case control.GetShardEvacuationStatusResponse_Body_COMPLETED:
|
||||
status = "completed"
|
||||
case control.GetShardEvacuationStatusResponse_Body_RUNNING:
|
||||
status = "running"
|
||||
default:
|
||||
status = "undefined"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" Status: %s.", status))
|
||||
}
|
||||
|
||||
func appendShardIDs(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
sb.WriteString("Shard IDs: ")
|
||||
for idx, shardID := range resp.GetBody().GetShard_ID() {
|
||||
shardIDStr := shard.NewIDFromBytes(shardID).String()
|
||||
if idx > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(shardIDStr)
|
||||
if idx == len(resp.GetBody().GetShard_ID())-1 {
|
||||
sb.WriteString(".")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendCounts(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
sb.WriteString(fmt.Sprintf(" Evacuated %d object out of %d, failed to evacuate %d objects.",
|
||||
resp.GetBody().GetEvacuated(),
|
||||
resp.Body.GetTotal(),
|
||||
resp.Body.GetFailed()))
|
||||
}
|
||||
|
||||
func initControlEvacuationShardCmd() {
|
||||
evacuationShardCmd.AddCommand(startEvacuationShardCmd)
|
||||
evacuationShardCmd.AddCommand(getEvacuationShardStatusCmd)
|
||||
evacuationShardCmd.AddCommand(stopEvacuationShardCmd)
|
||||
|
||||
initControlStartEvacuationShardCmd()
|
||||
initControlFlags(getEvacuationShardStatusCmd)
|
||||
initControlFlags(stopEvacuationShardCmd)
|
||||
}
|
||||
|
||||
func initControlStartEvacuationShardCmd() {
|
||||
initControlFlags(startEvacuationShardCmd)
|
||||
|
||||
flags := startEvacuationShardCmd.Flags()
|
||||
flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding")
|
||||
flags.Bool(shardAllFlag, false, "Process all shards")
|
||||
flags.Bool(ignoreErrorsFlag, true, "Skip invalid/unreadable objects")
|
||||
flags.Bool(awaitFlag, false, "Block execution until evacuation is completed")
|
||||
flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag))
|
||||
|
||||
startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"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"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -14,8 +19,8 @@ const (
|
|||
|
||||
var healthCheckCmd = &cobra.Command{
|
||||
Use: "healthcheck",
|
||||
Short: "Health check for FrostFS storage nodes",
|
||||
Long: "Health check for FrostFS storage nodes.",
|
||||
Short: "Health check of the FrostFS node",
|
||||
Long: "Health check of the FrostFS node. Checks storage node by default, use --ir flag to work with Inner Ring.",
|
||||
Run: healthCheck,
|
||||
}
|
||||
|
||||
|
@ -24,18 +29,18 @@ func initControlHealthCheckCmd() {
|
|||
|
||||
flags := healthCheckCmd.Flags()
|
||||
flags.Bool(healthcheckIRFlag, false, "Communicate with IR node")
|
||||
_ = flags.MarkDeprecated(healthcheckIRFlag, "for health check of inner ring nodes, use the 'control ir healthcheck' command instead.")
|
||||
}
|
||||
|
||||
func healthCheck(cmd *cobra.Command, args []string) {
|
||||
func healthCheck(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
if isIR, _ := cmd.Flags().GetBool(healthcheckIRFlag); isIR {
|
||||
irHealthCheck(cmd, args)
|
||||
healthCheckIR(cmd, pk, cli)
|
||||
return
|
||||
}
|
||||
|
||||
pk := key.Get(cmd)
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
req := new(control.HealthCheckRequest)
|
||||
req.SetBody(new(control.HealthCheckRequest_Body))
|
||||
|
||||
|
@ -54,3 +59,23 @@ func healthCheck(cmd *cobra.Command, args []string) {
|
|||
cmd.Printf("Network status: %s\n", resp.GetBody().GetNetmapStatus())
|
||||
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
|
||||
}
|
||||
|
||||
func healthCheckIR(cmd *cobra.Command, key *ecdsa.PrivateKey, c *client.Client) {
|
||||
req := new(ircontrol.HealthCheckRequest)
|
||||
|
||||
req.SetBody(new(ircontrol.HealthCheckRequest_Body))
|
||||
|
||||
err := ircontrolsrv.SignMessage(key, req)
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
|
||||
|
||||
var resp *ircontrol.HealthCheckResponse
|
||||
err = c.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = ircontrol.HealthCheck(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
package control
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var irCmd = &cobra.Command{
|
||||
Use: "ir",
|
||||
Short: "Operations with inner ring nodes",
|
||||
Long: "Operations with inner ring nodes",
|
||||
}
|
||||
|
||||
func initControlIRCmd() {
|
||||
irCmd.AddCommand(tickEpochCmd)
|
||||
irCmd.AddCommand(removeNodeCmd)
|
||||
irCmd.AddCommand(irHealthCheckCmd)
|
||||
|
||||
initControlIRTickEpochCmd()
|
||||
initControlIRRemoveNodeCmd()
|
||||
initControlIRHealthCheckCmd()
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var irHealthCheckCmd = &cobra.Command{
|
||||
Use: "healthcheck",
|
||||
Short: "Health check for FrostFS inner ring nodes",
|
||||
Long: "Health check for FrostFS inner ring nodes.",
|
||||
Run: irHealthCheck,
|
||||
}
|
||||
|
||||
func initControlIRHealthCheckCmd() {
|
||||
initControlFlags(irHealthCheckCmd)
|
||||
}
|
||||
|
||||
func irHealthCheck(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
req := new(ircontrol.HealthCheckRequest)
|
||||
|
||||
req.SetBody(new(ircontrol.HealthCheckRequest_Body))
|
||||
|
||||
err := ircontrolsrv.SignMessage(pk, req)
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
|
||||
|
||||
var resp *ircontrol.HealthCheckResponse
|
||||
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = ircontrol.HealthCheck(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var removeNodeCmd = &cobra.Command{
|
||||
Use: "remove-node",
|
||||
Short: "Forces a node removal from netmap",
|
||||
Long: "Forces a node removal from netmap via a notary request. It should be executed on other IR nodes as well.",
|
||||
Run: removeNode,
|
||||
}
|
||||
|
||||
func initControlIRRemoveNodeCmd() {
|
||||
initControlFlags(removeNodeCmd)
|
||||
|
||||
flags := removeNodeCmd.Flags()
|
||||
flags.String("node", "", "Node public key as a hex string")
|
||||
_ = removeNodeCmd.MarkFlagRequired("node")
|
||||
}
|
||||
|
||||
func removeNode(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
c := getClient(cmd, pk)
|
||||
|
||||
nodeKeyStr, _ := cmd.Flags().GetString("node")
|
||||
if len(nodeKeyStr) == 0 {
|
||||
commonCmd.ExitOnErr(cmd, "parsing node public key: ", errors.New("key cannot be empty"))
|
||||
}
|
||||
nodeKey, err := hex.DecodeString(nodeKeyStr)
|
||||
commonCmd.ExitOnErr(cmd, "can't decode node public key: %w", err)
|
||||
|
||||
req := new(ircontrol.RemoveNodeRequest)
|
||||
req.SetBody(&ircontrol.RemoveNodeRequest_Body{
|
||||
Key: nodeKey,
|
||||
})
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", ircontrolsrv.SignMessage(pk, req))
|
||||
|
||||
var resp *ircontrol.RemoveNodeResponse
|
||||
err = c.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = ircontrol.RemoveNode(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Node removed")
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var tickEpochCmd = &cobra.Command{
|
||||
Use: "tick-epoch",
|
||||
Short: "Forces a new epoch",
|
||||
Long: "Forces a new epoch via a notary request. It should be executed on other IR nodes as well.",
|
||||
Run: tickEpoch,
|
||||
}
|
||||
|
||||
func initControlIRTickEpochCmd() {
|
||||
initControlFlags(tickEpochCmd)
|
||||
}
|
||||
|
||||
func tickEpoch(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
c := getClient(cmd, pk)
|
||||
|
||||
req := new(ircontrol.TickEpochRequest)
|
||||
req.SetBody(new(ircontrol.TickEpochRequest_Body))
|
||||
|
||||
err := ircontrolsrv.SignMessage(pk, req)
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
|
||||
|
||||
var resp *ircontrol.TickEpochResponse
|
||||
err = c.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = ircontrol.TickEpoch(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Epoch tick requested")
|
||||
}
|
|
@ -33,7 +33,6 @@ func init() {
|
|||
dropObjectsCmd,
|
||||
shardsCmd,
|
||||
synchronizeTreeCmd,
|
||||
irCmd,
|
||||
)
|
||||
|
||||
initControlHealthCheckCmd()
|
||||
|
@ -41,5 +40,4 @@ func init() {
|
|||
initControlDropObjectsCmd()
|
||||
initControlShardsCmd()
|
||||
initControlSynchronizeTreeCmd()
|
||||
initControlIRCmd()
|
||||
}
|
||||
|
|
|
@ -13,15 +13,17 @@ var shardsCmd = &cobra.Command{
|
|||
func initControlShardsCmd() {
|
||||
shardsCmd.AddCommand(listShardsCmd)
|
||||
shardsCmd.AddCommand(setShardModeCmd)
|
||||
shardsCmd.AddCommand(dumpShardCmd)
|
||||
shardsCmd.AddCommand(restoreShardCmd)
|
||||
shardsCmd.AddCommand(evacuateShardCmd)
|
||||
shardsCmd.AddCommand(evacuationShardCmd)
|
||||
shardsCmd.AddCommand(flushCacheCmd)
|
||||
shardsCmd.AddCommand(doctorCmd)
|
||||
|
||||
initControlShardsListCmd()
|
||||
initControlSetShardModeCmd()
|
||||
initControlDumpShardCmd()
|
||||
initControlRestoreShardCmd()
|
||||
initControlEvacuateShardCmd()
|
||||
initControlEvacuationShardCmd()
|
||||
initControlFlushCacheCmd()
|
||||
initControlDoctorCmd()
|
||||
}
|
||||
|
|
66
cmd/frostfs-cli/modules/control/shards_dump.go
Normal file
66
cmd/frostfs-cli/modules/control/shards_dump.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
dumpFilepathFlag = "path"
|
||||
dumpIgnoreErrorsFlag = "no-errors"
|
||||
)
|
||||
|
||||
var dumpShardCmd = &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: "Dump objects from shard",
|
||||
Long: "Dump objects from shard to a file",
|
||||
Run: dumpShard,
|
||||
}
|
||||
|
||||
func dumpShard(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
body := new(control.DumpShardRequest_Body)
|
||||
body.SetShardID(getShardID(cmd))
|
||||
|
||||
p, _ := cmd.Flags().GetString(dumpFilepathFlag)
|
||||
body.SetFilepath(p)
|
||||
|
||||
ignore, _ := cmd.Flags().GetBool(dumpIgnoreErrorsFlag)
|
||||
body.SetIgnoreErrors(ignore)
|
||||
|
||||
req := new(control.DumpShardRequest)
|
||||
req.SetBody(body)
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.DumpShardResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.DumpShard(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Shard has been dumped successfully.")
|
||||
}
|
||||
|
||||
func initControlDumpShardCmd() {
|
||||
initControlFlags(dumpShardCmd)
|
||||
|
||||
flags := dumpShardCmd.Flags()
|
||||
flags.String(shardIDFlag, "", "Shard ID in base58 encoding")
|
||||
flags.String(dumpFilepathFlag, "", "File to write objects to")
|
||||
flags.Bool(dumpIgnoreErrorsFlag, false, "Skip invalid/unreadable objects")
|
||||
|
||||
_ = dumpShardCmd.MarkFlagRequired(shardIDFlag)
|
||||
_ = dumpShardCmd.MarkFlagRequired(dumpFilepathFlag)
|
||||
_ = dumpShardCmd.MarkFlagRequired(controlRPC)
|
||||
}
|
66
cmd/frostfs-cli/modules/control/shards_restore.go
Normal file
66
cmd/frostfs-cli/modules/control/shards_restore.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
restoreFilepathFlag = "path"
|
||||
restoreIgnoreErrorsFlag = "no-errors"
|
||||
)
|
||||
|
||||
var restoreShardCmd = &cobra.Command{
|
||||
Use: "restore",
|
||||
Short: "Restore objects from shard",
|
||||
Long: "Restore objects from shard to a file",
|
||||
Run: restoreShard,
|
||||
}
|
||||
|
||||
func restoreShard(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
body := new(control.RestoreShardRequest_Body)
|
||||
body.SetShardID(getShardID(cmd))
|
||||
|
||||
p, _ := cmd.Flags().GetString(restoreFilepathFlag)
|
||||
body.SetFilepath(p)
|
||||
|
||||
ignore, _ := cmd.Flags().GetBool(restoreIgnoreErrorsFlag)
|
||||
body.SetIgnoreErrors(ignore)
|
||||
|
||||
req := new(control.RestoreShardRequest)
|
||||
req.SetBody(body)
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.RestoreShardResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.RestoreShard(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Shard has been restored successfully.")
|
||||
}
|
||||
|
||||
func initControlRestoreShardCmd() {
|
||||
initControlFlags(restoreShardCmd)
|
||||
|
||||
flags := restoreShardCmd.Flags()
|
||||
flags.String(shardIDFlag, "", "Shard ID in base58 encoding")
|
||||
flags.String(restoreFilepathFlag, "", "File to read objects from")
|
||||
flags.Bool(restoreIgnoreErrorsFlag, false, "Skip invalid/unreadable objects")
|
||||
|
||||
_ = restoreShardCmd.MarkFlagRequired(shardIDFlag)
|
||||
_ = restoreShardCmd.MarkFlagRequired(restoreFilepathFlag)
|
||||
_ = restoreShardCmd.MarkFlagRequired(controlRPC)
|
||||
}
|
|
@ -137,6 +137,13 @@ func setShardMode(cmd *cobra.Command, _ []string) {
|
|||
cmd.Println("Shard mode update request successfully sent.")
|
||||
}
|
||||
|
||||
func getShardID(cmd *cobra.Command) []byte {
|
||||
sid, _ := cmd.Flags().GetString(shardIDFlag)
|
||||
raw, err := base58.Decode(sid)
|
||||
commonCmd.ExitOnErr(cmd, "incorrect shard ID encoding: %w", err)
|
||||
return raw
|
||||
}
|
||||
|
||||
func getShardIDList(cmd *cobra.Command) [][]byte {
|
||||
all, _ := cmd.Flags().GetBool(shardAllFlag)
|
||||
if all {
|
||||
|
|
|
@ -40,7 +40,7 @@ func verifyResponse(cmd *cobra.Command,
|
|||
commonCmd.ExitOnErr(cmd, "", errors.New("missing response signature"))
|
||||
}
|
||||
|
||||
// TODO(@cthulhu-rider): #468 use Signature message from FrostFS API to avoid conversion
|
||||
// TODO(@cthulhu-rider): #1387 use Signature message from NeoFS API to avoid conversion
|
||||
var sigV2 refs.Signature
|
||||
sigV2.SetScheme(refs.ECDSA_SHA512)
|
||||
sigV2.SetKey(sigControl.GetKey())
|
||||
|
|
|
@ -16,11 +16,10 @@ var getEpochCmd = &cobra.Command{
|
|||
p := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, p, commonflags.RPC)
|
||||
|
||||
prm := internalclient.NetworkInfoPrm{
|
||||
Client: cli,
|
||||
}
|
||||
var prm internalclient.NetworkInfoPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
res, err := internalclient.NetworkInfo(cmd.Context(), prm)
|
||||
res, err := internalclient.NetworkInfo(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
netInfo := res.NetworkInfo()
|
||||
|
|
|
@ -20,11 +20,10 @@ var netInfoCmd = &cobra.Command{
|
|||
p := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, p, commonflags.RPC)
|
||||
|
||||
prm := internalclient.NetworkInfoPrm{
|
||||
Client: cli,
|
||||
}
|
||||
var prm internalclient.NetworkInfoPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
res, err := internalclient.NetworkInfo(cmd.Context(), prm)
|
||||
res, err := internalclient.NetworkInfo(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
netInfo := res.NetworkInfo()
|
||||
|
@ -39,7 +38,11 @@ var netInfoCmd = &cobra.Command{
|
|||
const format = " %s: %v\n"
|
||||
|
||||
cmd.Println("FrostFS network configuration (system)")
|
||||
cmd.Printf(format, "Audit fee", netInfo.AuditFee())
|
||||
cmd.Printf(format, "Storage price", netInfo.StoragePrice())
|
||||
cmd.Printf(format, "Container fee", netInfo.ContainerFee())
|
||||
cmd.Printf(format, "EigenTrust alpha", netInfo.EigenTrustAlpha())
|
||||
cmd.Printf(format, "Number of EigenTrust iterations", netInfo.NumberOfEigenTrustIterations())
|
||||
cmd.Printf(format, "Epoch duration", netInfo.EpochDuration())
|
||||
cmd.Printf(format, "Inner Ring candidate fee", netInfo.IRCandidateFee())
|
||||
cmd.Printf(format, "Maximum object size", netInfo.MaxObjectSize())
|
||||
|
|
|
@ -22,11 +22,10 @@ var nodeInfoCmd = &cobra.Command{
|
|||
p := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, p, commonflags.RPC)
|
||||
|
||||
prm := internalclient.NodeInfoPrm{
|
||||
Client: cli,
|
||||
}
|
||||
var prm internalclient.NodeInfoPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
res, err := internalclient.NodeInfo(cmd.Context(), prm)
|
||||
res, err := internalclient.NodeInfo(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
prettyPrintNodeInfo(cmd, res.NodeInfo())
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -15,9 +14,7 @@ var Cmd = &cobra.Command{
|
|||
// the viper before execution
|
||||
commonflags.Bind(cmd)
|
||||
commonflags.BindAPI(cmd)
|
||||
common.StartClientCommandSpan(cmd)
|
||||
},
|
||||
PersistentPostRun: common.StopClientCommandSpan,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -19,7 +19,7 @@ var snapshotCmd = &cobra.Command{
|
|||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
res, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
|
||||
res, err := internalclient.NetMapSnapshot(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
commonCmd.PrettyPrintNetMap(cmd, res.NetMap(), false)
|
||||
|
|
|
@ -65,7 +65,7 @@ func deleteObject(cmd *cobra.Command, _ []string) {
|
|||
Prepare(cmd, &prm)
|
||||
prm.SetAddress(objAddr)
|
||||
|
||||
res, err := internalclient.DeleteObject(cmd.Context(), prm)
|
||||
res, err := internalclient.DeleteObject(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
tomb := res.Tombstone()
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -84,13 +84,13 @@ func getObject(cmd *cobra.Command, _ []string) {
|
|||
p = pb.New64(0)
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadWriter(p.NewProxyWriter(payloadWriter))
|
||||
prm.SetHeaderCallback(func(o *objectSDK.Object) {
|
||||
prm.SetHeaderCallback(func(o *object.Object) {
|
||||
p.SetTotal64(int64(o.PayloadSize()))
|
||||
p.Start()
|
||||
})
|
||||
}
|
||||
|
||||
res, err := internalclient.GetObject(cmd.Context(), prm)
|
||||
res, err := internalclient.GetObject(prm)
|
||||
if p != nil {
|
||||
p.Finish()
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
|||
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)
|
||||
res, err := internalclient.HeadObject(headPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
var cs checksum.Checksum
|
||||
|
@ -108,7 +108,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
|
|||
hashPrm.TZ()
|
||||
}
|
||||
|
||||
res, err := internalclient.HashPayloadRanges(cmd.Context(), hashPrm)
|
||||
res, err := internalclient.HashPayloadRanges(hashPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
hs := res.HashList()
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -64,7 +64,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
|||
prm.SetAddress(objAddr)
|
||||
prm.SetMainOnlyFlag(mainOnly)
|
||||
|
||||
res, err := internalclient.HeadObject(cmd.Context(), prm)
|
||||
res, err := internalclient.HeadObject(prm)
|
||||
if err != nil {
|
||||
if ok := printSplitInfoErr(cmd, err); ok {
|
||||
return
|
||||
|
@ -77,7 +77,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
}
|
||||
|
||||
func saveAndPrintHeader(cmd *cobra.Command, obj *objectSDK.Object, filename string) error {
|
||||
func saveAndPrintHeader(cmd *cobra.Command, obj *object.Object, filename string) error {
|
||||
bs, err := marshalHeader(cmd, obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not marshal header: %w", err)
|
||||
|
@ -97,7 +97,7 @@ func saveAndPrintHeader(cmd *cobra.Command, obj *objectSDK.Object, filename stri
|
|||
return printHeader(cmd, obj)
|
||||
}
|
||||
|
||||
func marshalHeader(cmd *cobra.Command, hdr *objectSDK.Object) ([]byte, error) {
|
||||
func marshalHeader(cmd *cobra.Command, hdr *object.Object) ([]byte, error) {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
switch {
|
||||
|
@ -138,7 +138,7 @@ func printContainerID(cmd *cobra.Command, recv func() (cid.ID, bool)) {
|
|||
cmd.Printf("CID: %s\n", strID)
|
||||
}
|
||||
|
||||
func printHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
|
||||
func printHeader(cmd *cobra.Command, obj *object.Object) error {
|
||||
printObjectID(cmd, obj.ID)
|
||||
printContainerID(cmd, obj.ContainerID)
|
||||
cmd.Printf("Owner: %s\n", obj.OwnerID())
|
||||
|
@ -150,7 +150,7 @@ func printHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
|
|||
|
||||
cmd.Println("Attributes:")
|
||||
for _, attr := range obj.Attributes() {
|
||||
if attr.Key() == objectSDK.AttributeTimestamp {
|
||||
if attr.Key() == object.AttributeTimestamp {
|
||||
cmd.Printf(" %s=%s (%s)\n",
|
||||
attr.Key(),
|
||||
attr.Value(),
|
||||
|
@ -163,7 +163,7 @@ func printHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
|
|||
if signature := obj.Signature(); signature != nil {
|
||||
cmd.Print("ID signature:\n")
|
||||
|
||||
// TODO(@carpawell): #468 implement and use another approach to avoid conversion
|
||||
// TODO(@carpawell): #1387 implement and use another approach to avoid conversion
|
||||
var sigV2 refs.Signature
|
||||
signature.WriteToV2(&sigV2)
|
||||
|
||||
|
@ -174,7 +174,7 @@ func printHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
|
|||
return printSplitHeader(cmd, obj)
|
||||
}
|
||||
|
||||
func printSplitHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
|
||||
func printSplitHeader(cmd *cobra.Command, obj *object.Object) error {
|
||||
if splitID := obj.SplitID(); splitID != nil {
|
||||
cmd.Printf("Split ID: %s\n", splitID)
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ var objectLockCmd = &cobra.Command{
|
|||
Prepare(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
||||
res, err := internalclient.PutObject(cmd.Context(), prm)
|
||||
res, err := internalclient.PutObject(prm)
|
||||
commonCmd.ExitOnErr(cmd, "Store lock object in FrostFS: %w", err)
|
||||
|
||||
cmd.Printf("Lock object ID: %s\n", res.ID())
|
||||
|
|
|
@ -1,351 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object_manager/placement"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
verifyPresenceAllFlag = "verify-presence-all"
|
||||
)
|
||||
|
||||
type objectNodesInfo struct {
|
||||
containerID cid.ID
|
||||
objectID oid.ID
|
||||
relatedObjectIDs []oid.ID
|
||||
isLock bool
|
||||
}
|
||||
|
||||
type boolError struct {
|
||||
value bool
|
||||
err error
|
||||
}
|
||||
|
||||
var objectNodesCmd = &cobra.Command{
|
||||
Use: "nodes",
|
||||
Short: "List of nodes where the object is stored",
|
||||
Long: `List of nodes where the object should be stored and where it is actually stored.
|
||||
Lock objects must exist on all nodes of the container.
|
||||
For complex objects, a node is considered to store an object if the node stores at least one part of the complex object.
|
||||
By default, the actual storage of the object is checked only on the nodes that should store the object. To check all nodes, use the flag --verify-presence-all.`,
|
||||
Run: objectNodes,
|
||||
}
|
||||
|
||||
func initObjectNodesCmd() {
|
||||
commonflags.Init(objectNodesCmd)
|
||||
|
||||
flags := objectNodesCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectGetCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectGetCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.Bool("verify-presence-all", false, "Verify the actual presence of the object on all netmap nodes")
|
||||
}
|
||||
|
||||
func objectNodes(cmd *cobra.Command, _ []string) {
|
||||
var cnrID cid.ID
|
||||
var objID oid.ID
|
||||
readObjectAddress(cmd, &cnrID, &objID)
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
objectInfo := getObjectInfo(cmd, cnrID, objID, cli, pk)
|
||||
|
||||
placementPolicy, netmap := getPlacementPolicyAndNetmap(cmd, cnrID, cli)
|
||||
|
||||
requiredPlacement := getRequiredPlacement(cmd, objectInfo, placementPolicy, netmap)
|
||||
|
||||
actualPlacement := getActualPlacement(cmd, netmap, requiredPlacement, pk, objectInfo)
|
||||
|
||||
printPlacement(cmd, netmap, requiredPlacement, actualPlacement)
|
||||
}
|
||||
|
||||
func getObjectInfo(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) *objectNodesInfo {
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnrID)
|
||||
addrObj.SetObject(objID)
|
||||
|
||||
var prmHead internalclient.HeadObjectPrm
|
||||
prmHead.SetClient(cli)
|
||||
prmHead.SetAddress(addrObj)
|
||||
prmHead.SetRawFlag(true)
|
||||
|
||||
Prepare(cmd, &prmHead)
|
||||
readSession(cmd, &prmHead, pk, cnrID, objID)
|
||||
|
||||
res, err := internalclient.HeadObject(cmd.Context(), prmHead)
|
||||
if err == nil {
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
isLock: res.Header().Type() == objectSDK.TypeLock,
|
||||
}
|
||||
}
|
||||
|
||||
var errSplitInfo *objectSDK.SplitInfoError
|
||||
|
||||
if !errors.As(err, &errSplitInfo) {
|
||||
commonCmd.ExitOnErr(cmd, "failed to get object info: %w", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
splitInfo := errSplitInfo.SplitInfo()
|
||||
|
||||
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnrID); ok {
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
relatedObjectIDs: members,
|
||||
}
|
||||
}
|
||||
|
||||
if members, ok := tryGetSplitMembersBySplitID(cmd, splitInfo, cli, cnrID); ok {
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
relatedObjectIDs: members,
|
||||
}
|
||||
}
|
||||
|
||||
members := tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnrID, objID)
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
relatedObjectIDs: members,
|
||||
}
|
||||
}
|
||||
|
||||
func getPlacementPolicyAndNetmap(cmd *cobra.Command, cnrID cid.ID, cli *client.Client) (placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) {
|
||||
eg, egCtx := errgroup.WithContext(cmd.Context())
|
||||
eg.Go(func() (e error) {
|
||||
placementPolicy, e = getPlacementPolicy(egCtx, cnrID, cli)
|
||||
return
|
||||
})
|
||||
eg.Go(func() (e error) {
|
||||
netmap, e = getNetMap(egCtx, cli)
|
||||
return
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", eg.Wait())
|
||||
return
|
||||
}
|
||||
|
||||
func getPlacementPolicy(ctx context.Context, cnrID cid.ID, cli *client.Client) (netmapSDK.PlacementPolicy, error) {
|
||||
prm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &cnrID,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := internalclient.GetContainer(ctx, prm)
|
||||
if err != nil {
|
||||
return netmapSDK.PlacementPolicy{}, err
|
||||
}
|
||||
|
||||
return res.Container().PlacementPolicy(), nil
|
||||
}
|
||||
|
||||
func getNetMap(ctx context.Context, cli *client.Client) (*netmapSDK.NetMap, error) {
|
||||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
res, err := internalclient.NetMapSnapshot(ctx, prm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm := res.NetMap()
|
||||
return &nm, nil
|
||||
}
|
||||
|
||||
func getRequiredPlacement(cmd *cobra.Command, objInfo *objectNodesInfo, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) map[uint64]netmapSDK.NodeInfo {
|
||||
nodes := make(map[uint64]netmapSDK.NodeInfo)
|
||||
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
||||
placement, err := placementBuilder.BuildPlacement(objInfo.containerID, &objInfo.objectID, placementPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get required placement: %w", err)
|
||||
for repIdx, rep := range placement {
|
||||
numOfReplicas := placementPolicy.ReplicaNumberByIndex(repIdx)
|
||||
var nodeIdx uint32
|
||||
for _, n := range rep {
|
||||
if !objInfo.isLock && nodeIdx == numOfReplicas { //lock object should be on all container nodes
|
||||
break
|
||||
}
|
||||
nodes[n.Hash()] = n
|
||||
nodeIdx++
|
||||
}
|
||||
}
|
||||
|
||||
for _, relatedObjID := range objInfo.relatedObjectIDs {
|
||||
placement, err = placementBuilder.BuildPlacement(objInfo.containerID, &relatedObjID, placementPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get required placement for related object: %w", err)
|
||||
for _, rep := range placement {
|
||||
for _, n := range rep {
|
||||
nodes[n.Hash()] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo,
|
||||
pk *ecdsa.PrivateKey, objInfo *objectNodesInfo) map[uint64]boolError {
|
||||
result := make(map[uint64]boolError)
|
||||
resultMtx := &sync.Mutex{}
|
||||
|
||||
var candidates []netmapSDK.NodeInfo
|
||||
checkAllNodes, _ := cmd.Flags().GetBool(verifyPresenceAllFlag)
|
||||
if checkAllNodes {
|
||||
candidates = netmap.Nodes()
|
||||
} else {
|
||||
for _, n := range requiredPlacement {
|
||||
candidates = append(candidates, n)
|
||||
}
|
||||
}
|
||||
|
||||
eg, egCtx := errgroup.WithContext(cmd.Context())
|
||||
for _, cand := range candidates {
|
||||
cand := cand
|
||||
|
||||
eg.Go(func() error {
|
||||
cli, err := createClient(egCtx, cmd, cand, pk)
|
||||
if err != nil {
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
result[cand.Hash()] = boolError{err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
var v boolError
|
||||
v.value, v.err = isObjectStoredOnNode(egCtx, cmd, objInfo.containerID, objInfo.objectID, cli, pk)
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) {
|
||||
return nil
|
||||
}
|
||||
result[cand.Hash()] = v
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, rObjID := range objInfo.relatedObjectIDs {
|
||||
rObjID := rObjID
|
||||
eg.Go(func() error {
|
||||
var v boolError
|
||||
v.value, v.err = isObjectStoredOnNode(egCtx, cmd, objInfo.containerID, rObjID, cli, pk)
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) {
|
||||
return nil
|
||||
}
|
||||
result[cand.Hash()] = v
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "failed to get actual placement: %w", eg.Wait())
|
||||
return result
|
||||
}
|
||||
|
||||
func createClient(ctx context.Context, cmd *cobra.Command, candidate netmapSDK.NodeInfo, pk *ecdsa.PrivateKey) (*client.Client, error) {
|
||||
var cli *client.Client
|
||||
var addresses []string
|
||||
candidate.IterateNetworkEndpoints(func(s string) bool {
|
||||
addresses = append(addresses, s)
|
||||
return false
|
||||
})
|
||||
addresses = append(addresses, candidate.ExternalAddresses()...)
|
||||
var lastErr error
|
||||
for _, address := range addresses {
|
||||
var networkAddr network.Address
|
||||
lastErr = networkAddr.FromString(address)
|
||||
if lastErr != nil {
|
||||
continue
|
||||
}
|
||||
cli, lastErr = internalclient.GetSDKClient(ctx, cmd, pk, networkAddr)
|
||||
if lastErr == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
if cli == nil {
|
||||
return nil, fmt.Errorf("failed to create client: no available endpoint")
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) (bool, error) {
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnrID)
|
||||
addrObj.SetObject(objID)
|
||||
|
||||
var prmHead internalclient.HeadObjectPrm
|
||||
prmHead.SetClient(cli)
|
||||
prmHead.SetAddress(addrObj)
|
||||
|
||||
Prepare(cmd, &prmHead)
|
||||
prmHead.SetTTL(1)
|
||||
readSession(cmd, &prmHead, pk, cnrID, objID)
|
||||
|
||||
res, err := internalclient.HeadObject(ctx, prmHead)
|
||||
if err == nil && res != nil {
|
||||
return true, nil
|
||||
}
|
||||
var notFound *apistatus.ObjectNotFound
|
||||
var removed *apistatus.ObjectAlreadyRemoved
|
||||
if errors.As(err, ¬Found) || errors.As(err, &removed) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func printPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo, actualPlacement map[uint64]boolError) {
|
||||
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 1, ' ', tabwriter.AlignRight|tabwriter.Debug)
|
||||
defer func() {
|
||||
commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", w.Flush())
|
||||
}()
|
||||
fmt.Fprintln(w, "Node ID\tShould contain object\tActually contains object\t")
|
||||
for _, n := range netmap.Nodes() {
|
||||
nodeID := hex.EncodeToString(n.PublicKey())
|
||||
_, required := requiredPlacement[n.Hash()]
|
||||
actual, actualExists := actualPlacement[n.Hash()]
|
||||
actualStr := ""
|
||||
if actualExists {
|
||||
if actual.err != nil {
|
||||
actualStr = fmt.Sprintf("error: %v", actual.err)
|
||||
} else {
|
||||
actualStr = strconv.FormatBool(actual.value)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t\n", nodeID, strconv.FormatBool(required), actualStr)
|
||||
}
|
||||
}
|
|
@ -16,17 +16,16 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
noProgressFlag = "no-progress"
|
||||
notificationFlag = "notify"
|
||||
copiesNumberFlag = "copies-number"
|
||||
prepareLocallyFlag = "prepare-locally"
|
||||
noProgressFlag = "no-progress"
|
||||
notificationFlag = "notify"
|
||||
copiesNumberFlag = "copies-number"
|
||||
)
|
||||
|
||||
var putExpiredOn uint64
|
||||
|
@ -55,12 +54,11 @@ func initObjectPutCmd() {
|
|||
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.Bool(noProgressFlag, false, "Do not show progress bar")
|
||||
flags.Bool(prepareLocallyFlag, false, "Generate object header on the client side (for big object - split locally too)")
|
||||
|
||||
flags.String(notificationFlag, "", "Object notification in the form of *epoch*:*topic*; '-' topic means using default")
|
||||
flags.Bool(binaryFlag, false, "Deserialize object structure from given file.")
|
||||
|
||||
flags.String(copiesNumberFlag, "", "Number of copies of the object to store within the RPC call")
|
||||
flags.Uint32(copiesNumberFlag, 0, "Number of copies of the object to store within the RPC call")
|
||||
}
|
||||
|
||||
func putObject(cmd *cobra.Command, _ []string) {
|
||||
|
@ -81,7 +79,7 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
||||
}
|
||||
var payloadReader io.Reader = f
|
||||
obj := objectSDK.New()
|
||||
obj := object.New()
|
||||
|
||||
if binary {
|
||||
payloadReader, cnr, ownerID = readFilePayload(filename, cmd)
|
||||
|
@ -104,12 +102,7 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
var prm internalclient.PutObjectPrm
|
||||
if prepareLocally, _ := cmd.Flags().GetBool(prepareLocallyFlag); prepareLocally {
|
||||
prm.SetClient(internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC))
|
||||
prm.PrepareLocally()
|
||||
} else {
|
||||
ReadOrOpenSession(cmd, &prm, pk, cnr, nil)
|
||||
}
|
||||
ReadOrOpenSession(cmd, &prm, pk, cnr, nil)
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
||||
|
@ -126,11 +119,13 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
}
|
||||
|
||||
copyNum, err := cmd.Flags().GetString(copiesNumberFlag)
|
||||
copyNum, err := cmd.Flags().GetUint32(copiesNumberFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse object copies numbers information: %w", err)
|
||||
prm.SetCopiesNumberByVectors(parseCopyNumber(cmd, copyNum))
|
||||
if copyNum > 0 {
|
||||
prm.SetCopiesNumber(copyNum)
|
||||
}
|
||||
|
||||
res, err := internalclient.PutObject(cmd.Context(), prm)
|
||||
res, err := internalclient.PutObject(prm)
|
||||
if p != nil {
|
||||
p.Finish()
|
||||
}
|
||||
|
@ -140,22 +135,10 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
cmd.Printf(" OID: %s\n CID: %s\n", res.ID(), cnr)
|
||||
}
|
||||
|
||||
func parseCopyNumber(cmd *cobra.Command, copyNum string) []uint32 {
|
||||
var cn []uint32
|
||||
if len(copyNum) > 0 {
|
||||
for _, num := range strings.Split(copyNum, ",") {
|
||||
val, err := strconv.ParseUint(num, 10, 32)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse object copies numbers information: %w", err)
|
||||
cn = append(cn, uint32(val))
|
||||
}
|
||||
}
|
||||
return cn
|
||||
}
|
||||
|
||||
func readFilePayload(filename string, cmd *cobra.Command) (io.Reader, cid.ID, user.ID) {
|
||||
buf, err := os.ReadFile(filename)
|
||||
commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err)
|
||||
objTemp := objectSDK.New()
|
||||
objTemp := object.New()
|
||||
// TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload
|
||||
commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
|
||||
payloadReader := bytes.NewReader(objTemp.Payload())
|
||||
|
@ -174,19 +157,19 @@ func setFilePayloadReader(cmd *cobra.Command, f *os.File, prm *internalclient.Pu
|
|||
p := pb.New64(fi.Size())
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadReader(p.NewProxyReader(f))
|
||||
prm.SetHeaderCallback(func(o *objectSDK.Object) { p.Start() })
|
||||
prm.SetHeaderCallback(func(o *object.Object) { p.Start() })
|
||||
return p
|
||||
}
|
||||
|
||||
func setBinaryPayloadReader(cmd *cobra.Command, obj *objectSDK.Object, prm *internalclient.PutObjectPrm, payloadReader io.Reader) *pb.ProgressBar {
|
||||
func setBinaryPayloadReader(cmd *cobra.Command, obj *object.Object, prm *internalclient.PutObjectPrm, payloadReader io.Reader) *pb.ProgressBar {
|
||||
p := pb.New(len(obj.Payload()))
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadReader(p.NewProxyReader(payloadReader))
|
||||
prm.SetHeaderCallback(func(o *objectSDK.Object) { p.Start() })
|
||||
prm.SetHeaderCallback(func(o *object.Object) { p.Start() })
|
||||
return p
|
||||
}
|
||||
|
||||
func getAllObjectAttributes(cmd *cobra.Command) []objectSDK.Attribute {
|
||||
func getAllObjectAttributes(cmd *cobra.Command) []object.Attribute {
|
||||
attrs, err := parseObjectAttrs(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse object attributes: %w", err)
|
||||
|
||||
|
@ -205,7 +188,7 @@ func getAllObjectAttributes(cmd *cobra.Command) []objectSDK.Attribute {
|
|||
|
||||
if !expAttrFound {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, objectSDK.Attribute{})
|
||||
attrs = append(attrs, object.Attribute{})
|
||||
attrs[index].SetKey(objectV2.SysAttributeExpEpoch)
|
||||
attrs[index].SetValue(expAttrValue)
|
||||
}
|
||||
|
@ -213,7 +196,7 @@ func getAllObjectAttributes(cmd *cobra.Command) []objectSDK.Attribute {
|
|||
return attrs
|
||||
}
|
||||
|
||||
func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||
func parseObjectAttrs(cmd *cobra.Command) ([]object.Attribute, error) {
|
||||
var rawAttrs []string
|
||||
|
||||
raw := cmd.Flag("attributes").Value.String()
|
||||
|
@ -221,7 +204,7 @@ func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
|||
rawAttrs = strings.Split(raw, ",")
|
||||
}
|
||||
|
||||
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||
attrs := make([]object.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||
for i := range rawAttrs {
|
||||
k, v, found := strings.Cut(rawAttrs[i], "=")
|
||||
if !found {
|
||||
|
@ -235,23 +218,23 @@ func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
|||
if !disableFilename {
|
||||
filename := filepath.Base(cmd.Flag(fileFlag).Value.String())
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, objectSDK.Attribute{})
|
||||
attrs[index].SetKey(objectSDK.AttributeFileName)
|
||||
attrs = append(attrs, object.Attribute{})
|
||||
attrs[index].SetKey(object.AttributeFileName)
|
||||
attrs[index].SetValue(filename)
|
||||
}
|
||||
|
||||
disableTime, _ := cmd.Flags().GetBool("disable-timestamp")
|
||||
if !disableTime {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, objectSDK.Attribute{})
|
||||
attrs[index].SetKey(objectSDK.AttributeTimestamp)
|
||||
attrs = append(attrs, object.Attribute{})
|
||||
attrs[index].SetKey(object.AttributeTimestamp)
|
||||
attrs[index].SetValue(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func parseObjectNotifications(cmd *cobra.Command) (*objectSDK.NotificationInfo, error) {
|
||||
func parseObjectNotifications(cmd *cobra.Command) (*object.NotificationInfo, error) {
|
||||
const (
|
||||
separator = ":"
|
||||
useDefaultTopic = "-"
|
||||
|
@ -267,7 +250,7 @@ func parseObjectNotifications(cmd *cobra.Command) (*objectSDK.NotificationInfo,
|
|||
return nil, fmt.Errorf("notification must be in the form of: *epoch*%s*topic*, got %s", separator, raw)
|
||||
}
|
||||
|
||||
ni := new(objectSDK.NotificationInfo)
|
||||
ni := new(object.NotificationInfo)
|
||||
|
||||
epoch, err := strconv.ParseUint(before, 10, 64)
|
||||
if err != nil {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -84,10 +84,10 @@ func getObjectRange(cmd *cobra.Command, _ []string) {
|
|||
raw, _ := cmd.Flags().GetBool(rawFlag)
|
||||
prm.SetRawFlag(raw)
|
||||
prm.SetAddress(objAddr)
|
||||
prm.SetRange(&ranges[0])
|
||||
prm.SetRange(ranges[0])
|
||||
prm.SetPayloadWriter(out)
|
||||
|
||||
_, err = internalclient.PayloadRange(cmd.Context(), prm)
|
||||
_, err = internalclient.PayloadRange(prm)
|
||||
if err != nil {
|
||||
if ok := printSplitInfoErr(cmd, err); ok {
|
||||
return
|
||||
|
@ -102,7 +102,7 @@ func getObjectRange(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func printSplitInfoErr(cmd *cobra.Command, err error) bool {
|
||||
var errSplitInfo *objectSDK.SplitInfoError
|
||||
var errSplitInfo *object.SplitInfoError
|
||||
|
||||
ok := errors.As(err, &errSplitInfo)
|
||||
|
||||
|
@ -114,14 +114,14 @@ func printSplitInfoErr(cmd *cobra.Command, err error) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
func printSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) {
|
||||
func printSplitInfo(cmd *cobra.Command, info *object.SplitInfo) {
|
||||
bs, err := marshalSplitInfo(cmd, info)
|
||||
commonCmd.ExitOnErr(cmd, "can't marshal split info: %w", err)
|
||||
|
||||
cmd.Println(string(bs))
|
||||
}
|
||||
|
||||
func marshalSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) ([]byte, error) {
|
||||
func marshalSplitInfo(cmd *cobra.Command, info *object.SplitInfo) ([]byte, error) {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
switch {
|
||||
|
@ -146,13 +146,13 @@ func marshalSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) ([]byte, er
|
|||
}
|
||||
}
|
||||
|
||||
func getRangeList(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
||||
func getRangeList(cmd *cobra.Command) ([]*object.Range, error) {
|
||||
v := cmd.Flag("range").Value.String()
|
||||
if len(v) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
vs := strings.Split(v, ",")
|
||||
rs := make([]objectSDK.Range, len(vs))
|
||||
rs := make([]*object.Range, len(vs))
|
||||
for i := range vs {
|
||||
before, after, found := strings.Cut(vs[i], rangeSep)
|
||||
if !found {
|
||||
|
@ -176,6 +176,7 @@ func getRangeList(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
|||
return nil, fmt.Errorf("invalid '%s' range: uint64 overflow", vs[i])
|
||||
}
|
||||
|
||||
rs[i] = object.NewRange()
|
||||
rs[i].SetOffset(offset)
|
||||
rs[i].SetLength(length)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -16,9 +15,7 @@ var Cmd = &cobra.Command{
|
|||
// the viper before execution
|
||||
commonflags.Bind(cmd)
|
||||
commonflags.BindAPI(cmd)
|
||||
common.StartClientCommandSpan(cmd)
|
||||
},
|
||||
PersistentPostRun: common.StopClientCommandSpan,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -30,8 +27,7 @@ func init() {
|
|||
objectHeadCmd,
|
||||
objectHashCmd,
|
||||
objectRangeCmd,
|
||||
objectLockCmd,
|
||||
objectNodesCmd}
|
||||
objectLockCmd}
|
||||
|
||||
Cmd.AddCommand(objectChildCommands...)
|
||||
|
||||
|
@ -48,5 +44,4 @@ func init() {
|
|||
initObjectHashCmd()
|
||||
initObjectRangeCmd()
|
||||
initCommandObjectLock()
|
||||
initObjectNodesCmd()
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -61,7 +61,7 @@ func searchObject(cmd *cobra.Command, _ []string) {
|
|||
prm.SetContainerID(cnr)
|
||||
prm.SetFilters(sf)
|
||||
|
||||
res, err := internalclient.SearchObjects(cmd.Context(), prm)
|
||||
res, err := internalclient.SearchObjects(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
ids := res.IDList()
|
||||
|
@ -72,18 +72,18 @@ func searchObject(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
}
|
||||
|
||||
var searchUnaryOpVocabulary = map[string]objectSDK.SearchMatchType{
|
||||
"NOPRESENT": objectSDK.MatchNotPresent,
|
||||
var searchUnaryOpVocabulary = map[string]object.SearchMatchType{
|
||||
"NOPRESENT": object.MatchNotPresent,
|
||||
}
|
||||
|
||||
var searchBinaryOpVocabulary = map[string]objectSDK.SearchMatchType{
|
||||
"EQ": objectSDK.MatchStringEqual,
|
||||
"NE": objectSDK.MatchStringNotEqual,
|
||||
"COMMON_PREFIX": objectSDK.MatchCommonPrefix,
|
||||
var searchBinaryOpVocabulary = map[string]object.SearchMatchType{
|
||||
"EQ": object.MatchStringEqual,
|
||||
"NE": object.MatchStringNotEqual,
|
||||
"COMMON_PREFIX": object.MatchCommonPrefix,
|
||||
}
|
||||
|
||||
func parseSearchFilters(cmd *cobra.Command) (objectSDK.SearchFilters, error) {
|
||||
var fs objectSDK.SearchFilters
|
||||
func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) {
|
||||
var fs object.SearchFilters
|
||||
|
||||
for i := range searchFilters {
|
||||
words := strings.Fields(searchFilters[i])
|
||||
|
@ -97,7 +97,7 @@ func parseSearchFilters(cmd *cobra.Command) (objectSDK.SearchFilters, error) {
|
|||
return nil, fmt.Errorf("could not read attributes filter from file: %w", err)
|
||||
}
|
||||
|
||||
subFs := objectSDK.NewSearchFilters()
|
||||
subFs := object.NewSearchFilters()
|
||||
|
||||
if err := subFs.UnmarshalJSON(data); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal attributes filter from file: %w", err)
|
||||
|
@ -138,7 +138,7 @@ func parseSearchFilters(cmd *cobra.Command) (objectSDK.SearchFilters, error) {
|
|||
return nil, fmt.Errorf("could not parse object ID: %w", err)
|
||||
}
|
||||
|
||||
fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id)
|
||||
fs.AddObjectIDFilter(object.MatchStringEqual, id)
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -87,7 +87,7 @@ func readObjectAddress(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID) oid.Address
|
|||
func readObjectAddressBin(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID, filename string) oid.Address {
|
||||
buf, err := os.ReadFile(filename)
|
||||
commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err)
|
||||
objTemp := objectSDK.New()
|
||||
objTemp := object.New()
|
||||
commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
|
||||
|
||||
var addr oid.Address
|
||||
|
@ -278,7 +278,7 @@ func OpenSessionViaClient(cmd *cobra.Command, dst SessionPrm, cli *client.Client
|
|||
|
||||
common.PrintVerbose(cmd, "Opening remote session with the node...")
|
||||
|
||||
err := sessionCli.CreateSession(cmd.Context(), &tok, cli, sessionLifetime)
|
||||
err := sessionCli.CreateSession(&tok, cli, sessionLifetime)
|
||||
commonCmd.ExitOnErr(cmd, "open remote session: %w", err)
|
||||
|
||||
common.PrintVerbose(cmd, "Session successfully opened.")
|
||||
|
@ -354,9 +354,9 @@ func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID,
|
|||
|
||||
Prepare(cmd, &prmHead)
|
||||
|
||||
_, err := internal.HeadObject(cmd.Context(), prmHead)
|
||||
_, err := internal.HeadObject(prmHead)
|
||||
|
||||
var errSplit *objectSDK.SplitInfoError
|
||||
var errSplit *object.SplitInfoError
|
||||
|
||||
switch {
|
||||
default:
|
||||
|
@ -381,7 +381,7 @@ func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID,
|
|||
return tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnr, obj)
|
||||
}
|
||||
|
||||
func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, prmHead internal.HeadObjectPrm, cnr cid.ID) ([]oid.ID, bool) {
|
||||
func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *object.SplitInfo, prmHead internal.HeadObjectPrm, cnr cid.ID) ([]oid.ID, bool) {
|
||||
// collect split chain by the descending ease of operations (ease is evaluated heuristically).
|
||||
// If any approach fails, we don't try the next since we assume that it will fail too.
|
||||
|
||||
|
@ -396,7 +396,7 @@ func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *objectSDK.
|
|||
prmHead.SetRawFlag(false)
|
||||
// client is already set
|
||||
|
||||
res, err := internal.HeadObject(cmd.Context(), prmHead)
|
||||
res, err := internal.HeadObject(prmHead)
|
||||
if err == nil {
|
||||
children := res.Header().Children()
|
||||
|
||||
|
@ -413,19 +413,19 @@ func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *objectSDK.
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func tryGetSplitMembersBySplitID(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, cli *client.Client, cnr cid.ID) ([]oid.ID, bool) {
|
||||
func tryGetSplitMembersBySplitID(cmd *cobra.Command, splitInfo *object.SplitInfo, cli *client.Client, cnr cid.ID) ([]oid.ID, bool) {
|
||||
if idSplit := splitInfo.SplitID(); idSplit != nil {
|
||||
common.PrintVerbose(cmd, "Collecting split members by split ID...")
|
||||
|
||||
var query objectSDK.SearchFilters
|
||||
query.AddSplitIDFilter(objectSDK.MatchStringEqual, idSplit)
|
||||
var query object.SearchFilters
|
||||
query.AddSplitIDFilter(object.MatchStringEqual, idSplit)
|
||||
|
||||
var prm internal.SearchObjectsPrm
|
||||
prm.SetContainerID(cnr)
|
||||
prm.SetClient(cli)
|
||||
prm.SetFilters(query)
|
||||
|
||||
res, err := internal.SearchObjects(cmd.Context(), prm)
|
||||
res, err := internal.SearchObjects(prm)
|
||||
commonCmd.ExitOnErr(cmd, "failed to search objects by split ID: %w", err)
|
||||
|
||||
parts := res.IDList()
|
||||
|
@ -437,7 +437,7 @@ func tryGetSplitMembersBySplitID(cmd *cobra.Command, splitInfo *objectSDK.SplitI
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, prmHead internal.HeadObjectPrm, cli *client.Client, cnr cid.ID, obj oid.ID) []oid.ID {
|
||||
func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *object.SplitInfo, prmHead internal.HeadObjectPrm, cli *client.Client, cnr cid.ID, obj oid.ID) []oid.ID {
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnr)
|
||||
|
||||
|
@ -463,7 +463,7 @@ func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo
|
|||
addrObj.SetObject(idMember)
|
||||
prmHead.SetAddress(addrObj)
|
||||
|
||||
res, err = internal.HeadObject(cmd.Context(), prmHead)
|
||||
res, err = internal.HeadObject(prmHead)
|
||||
commonCmd.ExitOnErr(cmd, "failed to read split chain member's header: %w", err)
|
||||
|
||||
idMember, ok = res.Header().PreviousID()
|
||||
|
@ -482,15 +482,15 @@ func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo
|
|||
|
||||
common.PrintVerbose(cmd, "Looking for a linking object...")
|
||||
|
||||
var query objectSDK.SearchFilters
|
||||
query.AddParentIDFilter(objectSDK.MatchStringEqual, obj)
|
||||
var query object.SearchFilters
|
||||
query.AddParentIDFilter(object.MatchStringEqual, obj)
|
||||
|
||||
var prmSearch internal.SearchObjectsPrm
|
||||
prmSearch.SetClient(cli)
|
||||
prmSearch.SetContainerID(cnr)
|
||||
prmSearch.SetFilters(query)
|
||||
|
||||
resSearch, err := internal.SearchObjects(cmd.Context(), prmSearch)
|
||||
resSearch, err := internal.SearchObjects(prmSearch)
|
||||
commonCmd.ExitOnErr(cmd, "failed to find object children: %w", err)
|
||||
|
||||
list := resSearch.IDList()
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
netmapCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/netmap"
|
||||
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
sessionCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/session"
|
||||
sgCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/storagegroup"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/tree"
|
||||
utilCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
|
@ -83,9 +84,10 @@ func init() {
|
|||
rootCmd.AddCommand(utilCli.Cmd)
|
||||
rootCmd.AddCommand(netmapCli.Cmd)
|
||||
rootCmd.AddCommand(objectCli.Cmd)
|
||||
rootCmd.AddCommand(sgCli.Cmd)
|
||||
rootCmd.AddCommand(containerCli.Cmd)
|
||||
rootCmd.AddCommand(tree.Cmd)
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd))
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, _ []string) {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
|
@ -30,12 +28,10 @@ var createCmd = &cobra.Command{
|
|||
Use: "create",
|
||||
Short: "Create session token",
|
||||
Run: createSession,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
|
||||
_ = viper.BindPFlag(commonflags.Account, cmd.Flags().Lookup(commonflags.Account))
|
||||
common.StartClientCommandSpan(cmd)
|
||||
},
|
||||
PersistentPostRun: common.StopClientCommandSpan,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -58,7 +54,7 @@ func createSession(cmd *cobra.Command, _ []string) {
|
|||
addrStr, _ := cmd.Flags().GetString(commonflags.RPC)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse endpoint: %w", netAddr.FromString(addrStr))
|
||||
|
||||
c, err := internalclient.GetSDKClient(cmd.Context(), cmd, privKey, netAddr)
|
||||
c, err := internalclient.GetSDKClient(cmd, privKey, netAddr)
|
||||
commonCmd.ExitOnErr(cmd, "can't create client: %w", err)
|
||||
|
||||
lifetime := uint64(defaultLifetime)
|
||||
|
@ -68,7 +64,7 @@ func createSession(cmd *cobra.Command, _ []string) {
|
|||
|
||||
var tok session.Object
|
||||
|
||||
err = CreateSession(cmd.Context(), &tok, c, lifetime)
|
||||
err = CreateSession(&tok, c, lifetime)
|
||||
commonCmd.ExitOnErr(cmd, "can't create session: %w", err)
|
||||
|
||||
var data []byte
|
||||
|
@ -90,12 +86,11 @@ func createSession(cmd *cobra.Command, _ []string) {
|
|||
// number of epochs.
|
||||
//
|
||||
// Fills ID, lifetime and session key.
|
||||
func CreateSession(ctx context.Context, dst *session.Object, c *client.Client, lifetime uint64) error {
|
||||
netInfoPrm := internalclient.NetworkInfoPrm{
|
||||
Client: c,
|
||||
}
|
||||
func CreateSession(dst *session.Object, c *client.Client, lifetime uint64) error {
|
||||
var netInfoPrm internalclient.NetworkInfoPrm
|
||||
netInfoPrm.SetClient(c)
|
||||
|
||||
ni, err := internalclient.NetworkInfo(ctx, netInfoPrm)
|
||||
ni, err := internalclient.NetworkInfo(netInfoPrm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch network info: %w", err)
|
||||
}
|
||||
|
@ -105,9 +100,9 @@ func CreateSession(ctx context.Context, dst *session.Object, c *client.Client, l
|
|||
|
||||
var sessionPrm internalclient.CreateSessionPrm
|
||||
sessionPrm.SetClient(c)
|
||||
sessionPrm.Expiration = exp
|
||||
sessionPrm.SetExp(exp)
|
||||
|
||||
sessionRes, err := internalclient.CreateSession(ctx, sessionPrm)
|
||||
sessionRes, err := internalclient.CreateSession(sessionPrm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open session: %w", err)
|
||||
}
|
||||
|
|
53
cmd/frostfs-cli/modules/storagegroup/delete.go
Normal file
53
cmd/frostfs-cli/modules/storagegroup/delete.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package storagegroup
|
||||
|
||||
import (
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var sgDelCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete storage group from FrostFS",
|
||||
Long: "Delete storage group from FrostFS",
|
||||
Run: delSG,
|
||||
}
|
||||
|
||||
func initSGDeleteCmd() {
|
||||
commonflags.Init(sgDelCmd)
|
||||
|
||||
flags := sgDelCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = sgDelCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.StringVarP(&sgID, sgIDFlag, "", "", "Storage group identifier")
|
||||
_ = sgDelCmd.MarkFlagRequired(sgIDFlag)
|
||||
}
|
||||
|
||||
func delSG(cmd *cobra.Command, _ []string) {
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
addr := readObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
var prm internalclient.DeleteObjectPrm
|
||||
objectCli.OpenSession(cmd, &prm, pk, cnr, &obj)
|
||||
objectCli.Prepare(cmd, &prm)
|
||||
prm.SetAddress(addr)
|
||||
|
||||
res, err := internalclient.DeleteObject(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
tombstone := res.Tombstone()
|
||||
|
||||
cmd.Println("Storage group removed successfully.")
|
||||
cmd.Printf(" Tombstone: %s\n", tombstone)
|
||||
}
|
82
cmd/frostfs-cli/modules/storagegroup/get.go
Normal file
82
cmd/frostfs-cli/modules/storagegroup/get.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package storagegroup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/key"
|
||||
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
storagegroupSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/storagegroup"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var sgID string
|
||||
|
||||
var sgGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get storage group from FrostFS",
|
||||
Long: "Get storage group from FrostFS",
|
||||
Run: getSG,
|
||||
}
|
||||
|
||||
func initSGGetCmd() {
|
||||
commonflags.Init(sgGetCmd)
|
||||
|
||||
flags := sgGetCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = sgGetCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.StringVarP(&sgID, sgIDFlag, "", "", "Storage group identifier")
|
||||
_ = sgGetCmd.MarkFlagRequired(sgIDFlag)
|
||||
|
||||
flags.Bool(sgRawFlag, false, "Set raw request option")
|
||||
}
|
||||
|
||||
func getSG(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
addr := readObjectAddress(cmd, &cnr, &obj)
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.GetObjectPrm
|
||||
objectCli.Prepare(cmd, &prm)
|
||||
prm.SetClient(cli)
|
||||
|
||||
raw, _ := cmd.Flags().GetBool(sgRawFlag)
|
||||
prm.SetRawFlag(raw)
|
||||
prm.SetAddress(addr)
|
||||
prm.SetPayloadWriter(buf)
|
||||
|
||||
res, err := internalclient.GetObject(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
rawObj := res.Header()
|
||||
rawObj.SetPayload(buf.Bytes())
|
||||
|
||||
var sg storagegroupSDK.StorageGroup
|
||||
|
||||
err = storagegroupSDK.ReadFromObject(&sg, *rawObj)
|
||||
commonCmd.ExitOnErr(cmd, "could not read storage group from the obj: %w", err)
|
||||
|
||||
cmd.Printf("The last active epoch: %d\n", sg.ExpirationEpoch())
|
||||
cmd.Printf("Group size: %d\n", sg.ValidationDataSize())
|
||||
common.PrintChecksum(cmd, "Group hash", sg.ValidationDataHash)
|
||||
|
||||
if members := sg.Members(); len(members) > 0 {
|
||||
cmd.Println("Members:")
|
||||
|
||||
for i := range members {
|
||||
cmd.Printf("\t%s\n", members[i].String())
|
||||
}
|
||||
}
|
||||
}
|
52
cmd/frostfs-cli/modules/storagegroup/list.go
Normal file
52
cmd/frostfs-cli/modules/storagegroup/list.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package storagegroup
|
||||
|
||||
import (
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
objectCli "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/object_manager/storagegroup"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var sgListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List storage groups in FrostFS container",
|
||||
Long: "List storage groups in FrostFS container",
|
||||
Run: listSG,
|
||||
}
|
||||
|
||||
func initSGListCmd() {
|
||||
commonflags.Init(sgListCmd)
|
||||
|
||||
sgListCmd.Flags().String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = sgListCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
}
|
||||
|
||||
func listSG(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
readCID(cmd, &cnr)
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.SearchObjectsPrm
|
||||
objectCli.Prepare(cmd, &prm)
|
||||
prm.SetClient(cli)
|
||||
prm.SetContainerID(cnr)
|
||||
prm.SetFilters(storagegroup.SearchQuery())
|
||||
|
||||
res, err := internalclient.SearchObjects(prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
ids := res.IDList()
|
||||
|
||||
cmd.Printf("Found %d storage groups.\n", len(ids))
|
||||
|
||||
for i := range ids {
|
||||
cmd.Println(ids[i].String())
|
||||
}
|
||||
}
|
145
cmd/frostfs-cli/modules/storagegroup/put.go
Normal file
145
cmd/frostfs-cli/modules/storagegroup/put.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package storagegroup
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
objectCli "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/object_manager/storagegroup"
|
||||
"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/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
storagegroupSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/storagegroup"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const sgMembersFlag = "members"
|
||||
|
||||
var sgMembers []string
|
||||
|
||||
var sgPutCmd = &cobra.Command{
|
||||
Use: "put",
|
||||
Short: "Put storage group to FrostFS",
|
||||
Long: "Put storage group to FrostFS",
|
||||
Run: putSG,
|
||||
}
|
||||
|
||||
func initSGPutCmd() {
|
||||
commonflags.Init(sgPutCmd)
|
||||
|
||||
flags := sgPutCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = sgPutCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.StringSliceVarP(&sgMembers, sgMembersFlag, "m", nil, "ID list of storage group members")
|
||||
_ = sgPutCmd.MarkFlagRequired(sgMembersFlag)
|
||||
|
||||
flags.Uint64(commonflags.Lifetime, 0, "Storage group lifetime in epochs")
|
||||
_ = sgPutCmd.MarkFlagRequired(commonflags.Lifetime)
|
||||
}
|
||||
|
||||
func putSG(cmd *cobra.Command, _ []string) {
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
var ownerID user.ID
|
||||
user.IDFromKey(&ownerID, pk.PublicKey)
|
||||
|
||||
var cnr cid.ID
|
||||
readCID(cmd, &cnr)
|
||||
|
||||
members := make([]oid.ID, len(sgMembers))
|
||||
uniqueFilter := make(map[oid.ID]struct{}, len(sgMembers))
|
||||
|
||||
for i := range sgMembers {
|
||||
err := members[i].DecodeString(sgMembers[i])
|
||||
commonCmd.ExitOnErr(cmd, "could not parse object ID: %w", err)
|
||||
|
||||
if _, alreadyExists := uniqueFilter[members[i]]; alreadyExists {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("%s member in not unique", members[i]))
|
||||
}
|
||||
|
||||
uniqueFilter[members[i]] = struct{}{}
|
||||
}
|
||||
|
||||
var (
|
||||
headPrm internalclient.HeadObjectPrm
|
||||
putPrm internalclient.PutObjectPrm
|
||||
getCnrPrm internalclient.GetContainerPrm
|
||||
)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
getCnrPrm.SetClient(cli)
|
||||
getCnrPrm.SetContainer(cnr)
|
||||
|
||||
resGetCnr, err := internalclient.GetContainer(getCnrPrm)
|
||||
commonCmd.ExitOnErr(cmd, "get container RPC call: %w", err)
|
||||
|
||||
objectCli.OpenSessionViaClient(cmd, &putPrm, cli, pk, cnr, nil)
|
||||
objectCli.Prepare(cmd, &headPrm, &putPrm)
|
||||
|
||||
headPrm.SetRawFlag(true)
|
||||
headPrm.SetClient(cli)
|
||||
|
||||
sg, err := storagegroup.CollectMembers(sgHeadReceiver{
|
||||
cmd: cmd,
|
||||
key: pk,
|
||||
ownerID: &ownerID,
|
||||
prm: headPrm,
|
||||
}, cnr, members, !container.IsHomomorphicHashingDisabled(resGetCnr.Container()))
|
||||
commonCmd.ExitOnErr(cmd, "could not collect storage group members: %w", err)
|
||||
|
||||
var netInfoPrm internalclient.NetworkInfoPrm
|
||||
netInfoPrm.SetClient(cli)
|
||||
|
||||
ni, err := internalclient.NetworkInfo(netInfoPrm)
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch network info: %w", err)
|
||||
|
||||
lifetime, _ := cmd.Flags().GetUint64(commonflags.Lifetime)
|
||||
sg.SetExpirationEpoch(ni.NetworkInfo().CurrentEpoch() + lifetime)
|
||||
|
||||
obj := object.New()
|
||||
obj.SetContainerID(cnr)
|
||||
obj.SetOwnerID(&ownerID)
|
||||
|
||||
storagegroupSDK.WriteToObject(*sg, obj)
|
||||
|
||||
putPrm.SetHeader(obj)
|
||||
|
||||
res, err := internalclient.PutObject(putPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
cmd.Println("Storage group successfully stored")
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cnr)
|
||||
}
|
||||
|
||||
type sgHeadReceiver struct {
|
||||
cmd *cobra.Command
|
||||
key *ecdsa.PrivateKey
|
||||
ownerID *user.ID
|
||||
prm internalclient.HeadObjectPrm
|
||||
}
|
||||
|
||||
func (c sgHeadReceiver) Head(addr oid.Address) (any, error) {
|
||||
c.prm.SetAddress(addr)
|
||||
|
||||
res, err := internalclient.HeadObject(c.prm)
|
||||
|
||||
var errSplitInfo *object.SplitInfoError
|
||||
|
||||
switch {
|
||||
default:
|
||||
return nil, err
|
||||
case err == nil:
|
||||
return res.Header(), nil
|
||||
case errors.As(err, &errSplitInfo):
|
||||
return errSplitInfo.SplitInfo(), nil
|
||||
}
|
||||
}
|
46
cmd/frostfs-cli/modules/storagegroup/root.go
Normal file
46
cmd/frostfs-cli/modules/storagegroup/root.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package storagegroup
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd represents the storagegroup command.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "storagegroup",
|
||||
Short: "Operations with Storage Groups",
|
||||
Long: `Operations with Storage Groups`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
// bind exactly that cmd's flags to
|
||||
// the viper before execution
|
||||
commonflags.Bind(cmd)
|
||||
commonflags.BindAPI(cmd)
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
sgIDFlag = "id"
|
||||
sgRawFlag = "raw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
storageGroupChildCommands := []*cobra.Command{
|
||||
sgPutCmd,
|
||||
sgGetCmd,
|
||||
sgListCmd,
|
||||
sgDelCmd,
|
||||
}
|
||||
|
||||
Cmd.AddCommand(storageGroupChildCommands...)
|
||||
|
||||
for _, sgCommand := range storageGroupChildCommands {
|
||||
objectCli.InitBearer(sgCommand)
|
||||
commonflags.InitAPI(sgCommand)
|
||||
}
|
||||
|
||||
initSGPutCmd()
|
||||
initSGGetCmd()
|
||||
initSGListCmd()
|
||||
initSGDeleteCmd()
|
||||
}
|
45
cmd/frostfs-cli/modules/storagegroup/util.go
Normal file
45
cmd/frostfs-cli/modules/storagegroup/util.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package storagegroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func readObjectAddress(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID) oid.Address {
|
||||
readCID(cmd, cnr)
|
||||
readSGID(cmd, obj)
|
||||
|
||||
var addr oid.Address
|
||||
addr.SetContainer(*cnr)
|
||||
addr.SetObject(*obj)
|
||||
return addr
|
||||
}
|
||||
|
||||
func readCID(cmd *cobra.Command, id *cid.ID) {
|
||||
f := cmd.Flag(commonflags.CIDFlag)
|
||||
if f == nil {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("missing container flag (%s)", commonflags.CIDFlag))
|
||||
return
|
||||
}
|
||||
|
||||
err := id.DecodeString(f.Value.String())
|
||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||
}
|
||||
|
||||
func readSGID(cmd *cobra.Command, id *oid.ID) {
|
||||
const flag = "id"
|
||||
|
||||
f := cmd.Flag(flag)
|
||||
if f == nil {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("missing storage group flag (%s)", flag))
|
||||
return
|
||||
}
|
||||
|
||||
err := id.DecodeString(f.Value.String())
|
||||
commonCmd.ExitOnErr(cmd, "decode storage group ID string: %w", err)
|
||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
|
@ -50,29 +49,24 @@ func add(cmd *cobra.Command, _ []string) {
|
|||
ctx := cmd.Context()
|
||||
|
||||
cli, err := _client(ctx)
|
||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "client: %w", err)
|
||||
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
cnr.Encode(rawCID)
|
||||
|
||||
var bt []byte
|
||||
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
|
||||
bt = t.Marshal()
|
||||
}
|
||||
|
||||
req := new(tree.AddRequest)
|
||||
req.Body = &tree.AddRequest_Body{
|
||||
ContainerId: rawCID,
|
||||
TreeId: tid,
|
||||
ParentId: pid,
|
||||
Meta: meta,
|
||||
BearerToken: bt,
|
||||
BearerToken: nil, // TODO: #1891 add token handling
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
|
||||
commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk))
|
||||
|
||||
resp, err := cli.Add(ctx, req)
|
||||
commonCmd.ExitOnErr(cmd, "failed to cal add: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "rpc call: %w", err)
|
||||
|
||||
cmd.Println("Node ID: ", resp.Body.NodeId)
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue