Compare commits

..

No commits in common. "feature/wc-err-miss" and "master" have entirely different histories.

1135 changed files with 36444 additions and 36837 deletions

View file

@ -1,4 +1,4 @@
FROM golang:1.21 as builder FROM golang:1.18 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -1,25 +0,0 @@
FROM golang:1.21
WORKDIR /tmp
# Install apt packages
RUN apt-get update && apt-get install --no-install-recommends -y \
pip \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*
# Dash → Bash
RUN echo "dash dash/sh boolean false" | debconf-set-selections
RUN DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
RUN useradd -u 1234 -d /home/ci -m ci
USER ci
ENV PATH="$PATH:/home/ci/.local/bin"
COPY .pre-commit-config.yaml .
RUN pip install "pre-commit==3.1.1" \
&& git init . \
&& pre-commit install-hooks \
&& rm -rf /tmp/*

View file

@ -1,4 +1,4 @@
FROM golang:1.21 as builder FROM golang:1.18 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -1,4 +1,4 @@
FROM golang:1.21 as builder FROM golang:1.18 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -1,4 +1,4 @@
FROM golang:1.21 as builder FROM golang:1.18 as builder
ARG BUILD=now ARG BUILD=now
ARG VERSION=dev ARG VERSION=dev
ARG REPO=repository ARG REPO=repository

View file

@ -0,0 +1,19 @@
FROM golang:1.18 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"]

View file

@ -5,5 +5,4 @@ docker-compose.yml
Dockerfile Dockerfile
temp temp
.dockerignore .dockerignore
docker docker
.cache

View file

@ -1,38 +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
- name: Build NODE
run: make bin/frostfs-node
- name: Build IR
run: make bin/frostfs-ir
- name: Build ADM
run: make bin/frostfs-adm
- name: Build LENS
run: make bin/frostfs-lens

View file

@ -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 }}'

View file

@ -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

View file

@ -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
View file

@ -1,3 +1,2 @@
/**/*.pb.go -diff -merge /**/*.pb.go -diff -merge
/**/*.pb.go linguist-generated=true /**/*.pb.go linguist-generated=true
/go.sum -diff

1
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
* @TrueCloudLab/storage-core @TrueCloudLab/committers

View file

@ -2,7 +2,7 @@
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ''
labels: community, triage, bug labels: community, triage
assignees: '' assignees: ''
--- ---
@ -18,11 +18,8 @@ assignees: ''
If suggesting a change/improvement, explain the difference from current behavior --> If suggesting a change/improvement, explain the difference from current behavior -->
## Possible Solution ## Possible Solution
<!-- Not obligatory <!-- Not obligatory, but suggest a fix/reason for the bug,
If no reason/fix/additions for the bug can be suggested, or ideas how to implement the addition or change -->
uncomment the following phrase:
No fix can be suggested by a QA engineer. Further solutions shall be up to developers. -->
## Steps to Reproduce (for bugs) ## Steps to Reproduce (for bugs)
<!-- Provide a link to a live example, or an unambiguous set of steps <!-- Provide a link to a live example, or an unambiguous set of steps
@ -44,3 +41,10 @@ assignees: ''
* Version used: * Version used:
* Server setup and configuration: * Server setup and configuration:
* Operating System and version (`uname -a`): * Operating System and version (`uname -a`):
## Don't forget to add labels!
- component label (`frostfs-adm`, `frostfs-storage`, ...)
- `goodfirstissue`, `helpwanted` if needed
- does this issue belong to an epic?
- priority (`P0`-`P4`) if already triaged
- quarter label (`202XQY`) if possible

29
.github/workflows/changelog.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: CHANGELOG check
on:
pull_request:
branches:
- master
- support/**
jobs:
build:
runs-on: ubuntu-latest
name: Check for updates
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get changed CHANGELOG
id: changelog-diff
uses: tj-actions/changed-files@v29
with:
files: CHANGELOG.md
- name: Fail if changelog not updated
if: steps.changelog-diff.outputs.any_changed == 'false'
uses: actions/github-script@v3
with:
script: |
core.setFailed('CHANGELOG.md has not been updated')

37
.github/workflows/config-update.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Configuration check
on:
pull_request:
branches:
- master
- support/**
jobs:
build:
runs-on: ubuntu-latest
name: config-check
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get changed config-related files
id: config-diff
uses: tj-actions/changed-files@v29
with:
files: |
config/**
cmd/neofs-node/config/**
- name: Get changed doc files
id: docs-diff
uses: tj-actions/changed-files@v29
with:
files: docs/**
- name: Fail if config files are changed but the documentation is not updated
if: steps.config-diff.outputs.any_changed == 'true' && steps.docs-diff.outputs.any_changed == 'false'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Documentation has not been updated')

22
.github/workflows/dco.yml vendored Normal file
View file

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

60
.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,60 @@
name: frostfs-node tests
on:
push:
branches:
- master
- support/**
paths-ignore:
- '*.md'
pull_request:
branches:
- master
- support/**
paths-ignore:
- '*.md'
jobs:
test:
runs-on: ubuntu-20.04
strategy:
matrix:
go: [ '1.18.x', '1.19.x' ]
steps:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Check out code
uses: actions/checkout@v3
- name: Cache go mod
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go }}-
- name: Run go test
run: go test -coverprofile=coverage.txt -covermode=atomic ./...
- name: Codecov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: bash <(curl -s https://codecov.io/bash)
lint:
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.50.0
args: --timeout=5m
only-new-issues: true

View file

@ -1,11 +0,0 @@
[general]
fail-without-commits=True
regex-style-search=True
contrib=CC1
[title-match-regex]
regex=^\[\#[0-9Xx]+\]\s
[ignore-by-title]
regex=^Release(.*)
ignore=title-match-regex

View file

@ -4,7 +4,7 @@
# options for analysis running # options for analysis running
run: run:
# timeout for analysis, e.g. 30s, 5m, default is 1m # timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 20m timeout: 5m
# include test files or not, default is true # include test files or not, default is true
tests: false tests: false
@ -24,28 +24,6 @@ linters-settings:
govet: govet:
# report about shadowed variables # report about shadowed variables
check-shadowing: false check-shadowing: false
staticcheck:
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
funlen:
lines: 80 # default 60
statements: 60 # default 40
gocognit:
min-complexity: 40 # default 30
importas:
no-unaliased: true
no-extra-aliases: false
alias:
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
alias: objectSDK
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: linters:
enable: enable:
@ -73,11 +51,6 @@ linters:
- predeclared - predeclared
- reassign - reassign
- whitespace - whitespace
- containedctx
- funlen
- gocognit
- contextcheck
- importas
- truecloudlab-linters
disable-all: true disable-all: true
fast: false fast: false

View file

@ -1,54 +0,0 @@
ci:
autofix_prs: false
repos:
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
stages: [commit-msg]
- id: gitlint-ci
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-merge-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
exclude: ".key$"
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.9.0.2
hooks:
- id: shellcheck
- repo: local
hooks:
- id: make-lint
name: Run Make Lint
entry: make lint
language: system
pass_filenames: false
- repo: local
hooks:
- id: go-unit-tests
name: go unit tests
entry: make test
pass_filenames: false
types: [go]
language: system
- repo: https://github.com/TekWizely/pre-commit-golang
rev: v1.0.0-rc.1
hooks:
- id: go-staticcheck-repo-mod
- id: go-mod-tidy

View file

@ -1,11 +0,0 @@
pipeline:
# Kludge for non-root containers under WoodPecker
fix-ownership:
image: alpine:latest
commands: chown -R 1234:1234 .
pre-commit:
image: git.frostfs.info/truecloudlab/frostfs-ci:v0.36
commands:
- export HOME="$(getent passwd $(id -u) | cut '-d:' -f6)"
- pre-commit run --hook-stage manual

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@
First, thank you for contributing! We love and encourage pull requests from First, thank you for contributing! We love and encourage pull requests from
everyone. Please follow the guidelines: everyone. Please follow the guidelines:
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues) and - Check the open [issues](https://github.com/TrueCloudLab/frostfs-node/issues) and
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls) for existing [pull requests](https://github.com/TrueCloudLab/frostfs-node/pulls) for existing
discussions. discussions.
- Open an issue first, to discuss a new feature or enhancement. - 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 send a pull request. We encourage pull requests to discuss code changes. Here
are the steps in details: are the steps in details:
### Set up your Forgejo repository ### Set up your GitHub Repository
Fork [FrostFS node upstream](https://git.frostfs.info/TrueCloudLab/frostfs-node) source 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 repository to your own personal repository. Copy the URL of your fork (you will
need it for the `git clone` command below). need it for the `git clone` command below).
```sh ```sh
$ git clone https://git.frostfs.info/TrueCloudLab/frostfs-node $ git clone https://github.com/TrueCloudLab/frostfs-node
``` ```
### Set up git remote as ``upstream`` ### Set up git remote as ``upstream``
```sh ```sh
$ cd frostfs-node $ 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 fetch upstream
$ git merge upstream/master $ git merge upstream/master
... ...
@ -58,7 +58,7 @@ $ git checkout -b feature/123-something_awesome
After your code changes, make sure After your code changes, make sure
- To add test cases for the new code. - 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 - 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. commits run `git rebase -i`. It's okay to force update your pull request.
- To run `make test` and `make all` completes. - To run `make test` and `make all` completes.
@ -89,8 +89,8 @@ $ git push origin feature/123-something_awesome
``` ```
### Create a Pull Request ### Create a Pull Request
Pull requests can be created via Forgejo. Refer to [this Pull requests can be created via GitHub. Refer to [this
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for 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 detailed steps on how to create a pull request. After a Pull Request gets peer
reviewed and approved, it will be merged. reviewed and approved, it will be merged.

View file

@ -10,7 +10,7 @@ In alphabetical order:
- Alexey Vanin - Alexey Vanin
- Anastasia Prasolova - Anastasia Prasolova
- Anatoly Bogatyrev - Anatoly Bogatyrev
- Evgeny Kulikov - Evgeny Kulikov
- Evgeny Stratonikov - Evgeny Stratonikov
- Leonard Liubich - Leonard Liubich
- Sergei Liubich - Sergei Liubich

53
Makefile Executable file → Normal file
View file

@ -7,9 +7,8 @@ VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8
HUB_IMAGE ?= truecloudlab/frostfs HUB_IMAGE ?= truecloudlab/frostfs
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')" HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
GO_VERSION ?= 1.21 GO_VERSION ?= 1.19
LINT_VERSION ?= 1.54.0 LINT_VERSION ?= 1.50.0
TRUECLOUDLAB_LINT_VERSION ?= 0.0.2
ARCH = amd64 ARCH = amd64
BIN = bin BIN = bin
@ -26,12 +25,8 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \ sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
sed "s/-/~/")-${OS_RELEASE} 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 .PHONY: help all images dep clean fmts fmt imports test lint docker/lint
prepare-release debpackage pre-commit unpre-commit prepare-release debpackage
# To build a specific binary, use it's name prefix with bin/ as a target # To build a specific binary, use it's name prefix with bin/ as a target
# For example `make bin/frostfs-node` will build only storage node binary # For example `make bin/frostfs-node` will build only storage node binary
@ -75,7 +70,7 @@ protoc:
@GOPRIVATE=github.com/TrueCloudLab go mod vendor @GOPRIVATE=github.com/TrueCloudLab go mod vendor
# Install specific version for protobuf lib # Install specific version for protobuf lib
@go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v @go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v
@GOBIN=$(abspath $(BIN)) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen @GOBIN=$(abspath $(BIN)) go install -mod=mod -v github.com/TrueCloudLab/frostfs-api-go/v2/util/protogen
# Protoc generate # Protoc generate
@for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \ @for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \
echo "⇒ Processing $$f "; \ echo "⇒ Processing $$f "; \
@ -100,7 +95,7 @@ image-%:
-t $(HUB_IMAGE)-$*:$(HUB_TAG) . -t $(HUB_IMAGE)-$*:$(HUB_TAG) .
# Build all Docker images # 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 # Build dirty local Docker images
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
@ -131,36 +126,11 @@ imports:
# Run Unit Test with go test # Run Unit Test with go test
test: test:
@echo "⇒ Running go 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 # Run linters
lint: lint:
@if [ ! -d "$(LINT_DIR)" ]; then \ @golangci-lint --timeout=5m run
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
# Run staticcheck
staticcheck-run:
@staticcheck ./...
# Run linters in Docker # Run linters in Docker
docker/lint: docker/lint:
@ -170,19 +140,10 @@ docker/lint:
--env HOME=/src \ --env HOME=/src \
golangci/golangci-lint:v$(LINT_VERSION) bash -c 'cd /src/ && make lint' golangci/golangci-lint:v$(LINT_VERSION) bash -c 'cd /src/ && make lint'
# Activate pre-commit hooks
pre-commit:
pre-commit install -t pre-commit -t commit-msg
# Deactivate pre-commit hooks
unpre-commit:
pre-commit uninstall -t pre-commit -t commit-msg
# Print version # Print version
version: version:
@echo $(VERSION) @echo $(VERSION)
# Delete built artifacts
clean: clean:
rm -rf vendor rm -rf vendor
rm -rf .cache rm -rf .cache

View file

@ -31,7 +31,7 @@ dApps directly from
code level. This way dApps are not limited to on-chain storage and can code level. This way dApps are not limited to on-chain storage and can
manipulate large amounts of data without paying a prohibitive price. manipulate large amounts of data without paying a prohibitive price.
FrostFS has a native [gRPC API](https://git.frostfs.info/TrueCloudLab/frostfs-api) and has FrostFS has a native [gRPC API](https://github.com/TrueCloudLab/frostfs-api) and has
protocol gateways for popular protocols such as [AWS protocol gateways for popular protocols such as [AWS
S3](https://github.com/TrueCloudLab/frostfs-s3-gw), S3](https://github.com/TrueCloudLab/frostfs-s3-gw),
[HTTP](https://github.com/TrueCloudLab/frostfs-http-gw), [HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
@ -49,7 +49,7 @@ The latest version of frostfs-node works with frostfs-contract
# Building # Building
To make all binaries you need Go 1.20+ and `make`: To make all binaries you need Go 1.18+ and `make`:
``` ```
make all make all
``` ```

View file

@ -1 +1 @@
v0.36.0 v0.35.0

View file

@ -3,22 +3,23 @@
## Overview ## Overview
Admin tool provides an easier way to deploy and maintain private installation Admin tool provides an easier way to deploy and maintain private installation
of FrostFS. Private installation provides a set of N3 consensus nodes, FrostFS of FrostFS. Private installation provides a set of N3 consensus nodes, FrostFS
Alphabet, and Storage nodes. Admin tool generates consensus keys, initializes Alphabet, and Storage nodes. Admin tool generates consensus keys, initializes
the sidechain, and provides functions to update the network and register new the sidechain, and provides functions to update the network and register new
Storage nodes. Storage nodes.
## Build ## Build
To build binary locally, use `make bin/frostfs-adm` command. To build binary locally, use `make bin/frostfs-adm` command.
For clean build inside a docker container, use `make docker/bin/frostfs-adm`. For clean build inside a docker container, use `make docker/bin/frostfs-adm`.
Build docker image with `make image-adm`. Build docker image with `make image-adm`.
At FrostFS private install deployment, frostfs-adm requires compiled FrostFS At FrostFS private install deployment, frostfs-adm requires compiled FrostFS
contracts. Find them in the latest release of contracts. Find them in the latest release of
[frostfs-contract repository](https://git.frostfs.info/TrueCloudLab/frostfs-contract/releases). [frostfs-contract repository](https://github.com/TrueCloudLab/frostfs-contract/releases).
## Commands ## Commands
@ -26,7 +27,7 @@ contracts. Find them in the latest release of
Config section provides `init` command that creates a configuration file for Config section provides `init` command that creates a configuration file for
private installation deployment and updates. Config file is optional, all private installation deployment and updates. Config file is optional, all
parameters can be passed by arguments or read from standard input (wallet parameters can be passed by arguments or read from standard input (wallet
passwords). passwords).
Config example: Config example:
@ -36,7 +37,9 @@ alphabet-wallets: /path # path to consensus node / alphabet wallets s
network: network:
max_object_size: 67108864 # max size of a single FrostFS object, bytes 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 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: fee:
audit: 0 # network audit fee, for private installation consider 0
candidate: 0 # inner ring candidate registration 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: 0 # container creation fee, for private installation consider 0
container_alias: 0 # container nice-name registration fee, for private installation consider 0 container_alias: 0 # container nice-name registration fee, for private installation consider 0
@ -55,14 +58,14 @@ credentials: # passwords for consensus node / alphabet wallets
#### Network deployment #### Network deployment
- `generate-alphabet` generates a set of wallets for consensus and - `generate-alphabet` generates a set of wallets for consensus and
Alphabet nodes. Alphabet nodes.
- `init` initializes the sidechain by deploying smart contracts and - `init` initializes the sidechain by deploying smart contracts and
setting provided FrostFS network configuration. setting provided FrostFS network configuration.
- `generate-storage-wallet` generates a wallet for the Storage node that - `generate-storage-wallet` generates a wallet for the Storage node that
is ready for deployment. It also transfers a bit of sidechain GAS, so this is ready for deployment. It also transfers a bit of sidechain GAS, so this
wallet can be used for FrostFS bootstrap. wallet can be used for FrostFS bootstrap.
#### Network maintenance #### Network maintenance
@ -72,7 +75,7 @@ credentials: # passwords for consensus node / alphabet wallets
- `force-new-epoch` increments FrostFS epoch number and executes new epoch - `force-new-epoch` increments FrostFS epoch number and executes new epoch
handlers in FrostFS nodes. handlers in FrostFS nodes.
- `refill-gas` transfers sidechain GAS to the specified wallet. - `refill-gas` transfers sidechain GAS to the specified wallet.
- `update-contracts` updates contracts to a new version. - `update-contracts` updates contracts to a new version.
@ -84,7 +87,7 @@ info. These commands **do not migrate actual objects**.
- `dump-containers` saves all containers and metadata registered in the container - `dump-containers` saves all containers and metadata registered in the container
contract to a file. contract to a file.
- `restore-containers` restores previously saved containers by their repeated registration in - `restore-containers` restores previously saved containers by their repeated registration in
the container contract. the container contract.
- `list-containers` output all containers ids. - `list-containers` output all containers ids.

View file

@ -2,7 +2,7 @@
This is a short guide on how to deploy a private FrostFS storage network on bare This is a short guide on how to deploy a private FrostFS storage network on bare
metal without docker images. This guide does not cover details on how to start metal without docker images. This guide does not cover details on how to start
consensus, Alphabet, or Storage nodes. This guide covers only `frostfs-adm` consensus, Alphabet, or Storage nodes. This guide covers only `frostfs-adm`
related configuration details. related configuration details.
## Prerequisites ## Prerequisites
@ -12,13 +12,12 @@ To follow this guide you need:
- latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases) utility (v0.25.1 at the moment), - latest released version of [frostfs-adm](https://github.com/TrueCloudLab/frostfs-node/releases) utility (v0.25.1 at the moment),
- latest released version of compiled [frostfs-contract](https://github.com/TrueCloudLab/frostfs-contract/releases) (v0.11.0 at the moment). - latest released version of compiled [frostfs-contract](https://github.com/TrueCloudLab/frostfs-contract/releases) (v0.11.0 at the moment).
## Step 1: Prepare network configuration ## Step 1: Prepare network configuration
To start a network, you need a set of consensus nodes, the same number of 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 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 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. 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 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 a network configuration file. In this example, there is going to be only one
@ -28,13 +27,15 @@ consensus / Alphabet node in the network.
$ frostfs-adm config init --path foo.network.yml $ frostfs-adm config init --path foo.network.yml
Initial config file saved to foo.network.yml Initial config file saved to foo.network.yml
$ cat foo.network.yml $ cat foo.network.yml
rpc-endpoint: https://neo.rpc.node:30333 rpc-endpoint: https://neo.rpc.node:30333
alphabet-wallets: /home/user/deploy/alphabet-wallets alphabet-wallets: /home/user/deploy/alphabet-wallets
network: network:
max_object_size: 67108864 max_object_size: 67108864
epoch_duration: 240 epoch_duration: 240
basic_income_rate: 0
fee: fee:
audit: 0
candidate: 0 candidate: 0
container: 0 container: 0
withdraw: 0 withdraw: 0
@ -42,17 +43,17 @@ credentials:
az: hunter2 az: hunter2
``` ```
For private installation, it is recommended to set all **fees** and **basic For private installation, it is recommended to set all **fees** and **basic
income rate** to 0. income rate** to 0.
As for **epoch duration**, consider consensus node block generation frequency. As for **epoch duration**, consider consensus node block generation frequency.
With default 15 seconds per block, 240 blocks are going to be a 1-hour epoch. With default 15 seconds per block, 240 blocks are going to be a 1-hour epoch.
For **max object size**, 67108864 (64 MiB) or 134217728 (128 MiB) should provide For **max object size**, 67108864 (64 MiB) or 134217728 (128 MiB) should provide
good chunk distribution in most cases. good chunk distribution in most cases.
With this config, generate wallets (private keys) of consensus nodes. The same With this config, generate wallets (private keys) of consensus nodes. The same
wallets will be used for Alphabet nodes. Make sure, that dir for alphabet wallets will be used for Alphabet nodes. Make sure, that dir for alphabet
wallets already exists. wallets already exists.
``` ```
@ -68,14 +69,14 @@ storage.
## Step 2: Launch consensus nodes ## Step 2: Launch consensus nodes
Configure blockchain nodes with the generated wallets from the previous step. Configure blockchain nodes with the generated wallets from the previous step.
Config examples can be found in Config examples can be found in
[neo-go repository](https://github.com/nspcc-dev/neo-go/tree/master/config). [neo-go repository](https://github.com/nspcc-dev/neo-go/tree/master/config).
Gather public keys from **all** generated wallets. We are interested in the first Gather public keys from **all** generated wallets. We are interested in the first
`simple signature contract` public key. `simple signature contract` public key.
``` ```
$ neo-go wallet dump-keys -w alphabet-wallets/az.json $ neo-go wallet dump-keys -w alphabet-wallets/az.json
NitdS4k4f1Hh5mbLJhAswBK3WC2gQgPN1o (simple signature contract): NitdS4k4f1Hh5mbLJhAswBK3WC2gQgPN1o (simple signature contract):
02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869 02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869
@ -86,10 +87,10 @@ NiMKabp3ddi3xShmLAXhTfbnuWb4cSJT6E (1 out of 1 multisig contract):
02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869 02c1cc85f9c856dbe2d02017349bcb7b4e5defa78b8056a09b3240ba2a8c078869
``` ```
Put the list of public keys into `ProtocolConfiguration.StandbyCommittee` Put the list of public keys into `ProtocolConfiguration.StandbyCommittee`
section. Specify the wallet path and the password in `ApplicationConfiguration.P2PNotary` section. Specify the wallet path and the password in `ApplicationConfiguration.P2PNotary`
and `ApplicationConfiguration.UnlockWallet` sections. If config includes and `ApplicationConfiguration.UnlockWallet` sections. If config includes
`ProtocolConfiguration.NativeActivations` section, add notary `ProtocolConfiguration.NativeActivations` section, add notary
contract `Notary: [0]`. contract `Notary: [0]`.
```yaml ```yaml
@ -120,7 +121,7 @@ and possible overload issues.
Use archive with compiled FrostFS contracts to initialize the sidechain. Use archive with compiled FrostFS contracts to initialize the sidechain.
``` ```
$ tar -xzvf frostfs-contract-v0.11.0.tar.gz $ tar -xzvf frostfs-contract-v0.11.0.tar.gz
$ ./frostfs-adm -c foo.network.yml morph init --contracts ./frostfs-contract-v0.11.0 $ ./frostfs-adm -c foo.network.yml morph init --contracts ./frostfs-contract-v0.11.0
Stage 1: transfer GAS to alphabet nodes. Stage 1: transfer GAS to alphabet nodes.
@ -140,18 +141,20 @@ Waiting for transactions to persist...
Stage 7: set addresses in NNS. Stage 7: set addresses in NNS.
Waiting for transactions to persist... Waiting for transactions to persist...
NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a NNS: Set alphabet0.frostfs -> f692dfb4d43a15b464eb51a7041160fb29c44b6a
NNS: Set audit.frostfs -> 7df847b993affb3852074345a7c2bd622171ee0d
NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879 NNS: Set balance.frostfs -> 103519b3067a66307080a66570c0491ee8f68879
NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846 NNS: Set container.frostfs -> cae60bdd689d185901e495352d0247752ce50846
NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2 NNS: Set frostfsid.frostfs -> c421fb60a3895865a8f24d197d6a80ef686041d2
NNS: Set netmap.frostfs -> 894eb854632f50fb124412ce7951ebc00763525e NNS: Set netmap.frostfs -> 894eb854632f50fb124412ce7951ebc00763525e
NNS: Set proxy.frostfs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d NNS: Set proxy.frostfs -> ac6e6fe4b373d0ca0ca4969d1e58fa0988724e7d
NNS: Set reputation.frostfs -> 6eda57c9d93d990573646762d1fea327ce41191f
Waiting for transactions to persist... Waiting for transactions to persist...
``` ```
## Step 4: Launch Alphabet nodes ## Step 4: Launch Alphabet nodes
Configure Alphabet nodes with the wallets generated in step 1. For Configure Alphabet nodes with the wallets generated in step 1. For
`morph.validators` use a list of public keys from `morph.validators` use a list of public keys from
`ProtocolConfiguration.StandbyCommittee`. `ProtocolConfiguration.StandbyCommittee`.
```yaml ```yaml
@ -175,10 +178,10 @@ Generate a new wallet for a Storage node.
``` ```
$ frostfs-adm -c foo.network.yml morph generate-storage-wallet --storage-wallet ./sn01.json --initial-gas 10.0 $ frostfs-adm -c foo.network.yml morph generate-storage-wallet --storage-wallet ./sn01.json --initial-gas 10.0
New password > New password >
Waiting for transactions to persist... Waiting for transactions to persist...
$ neo-go wallet dump-keys -w sn01.json $ neo-go wallet dump-keys -w sn01.json
Ngr7p8Z9S22XDH6VkUG9oXobv8zZRAWwwv (simple signature contract): Ngr7p8Z9S22XDH6VkUG9oXobv8zZRAWwwv (simple signature contract):
0355eccb72cd46f09a3e5237eaa0f4949cceb5ecfa5a225bd3bb9fd021c4d75b85 0355eccb72cd46f09a3e5237eaa0f4949cceb5ecfa5a225bd3bb9fd021c4d75b85
``` ```
@ -202,7 +205,7 @@ Current epoch: 8, increase to 9.
Waiting for transactions to persist... Waiting for transactions to persist...
``` ```
--- ---
After that, FrostFS Storage is ready to work. You can access it directly or After that, FrostFS Storage is ready to work. You can access it directly or
with protocol gates. with protocol gates.

View 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.

View 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>
```

View file

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"text/template" "text/template"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -18,6 +18,8 @@ type configTemplate struct {
AlphabetDir string AlphabetDir string
MaxObjectSize int MaxObjectSize int
EpochDuration int EpochDuration int
BasicIncomeRate int
AuditFee int
CandidateFee int CandidateFee int
ContainerFee int ContainerFee int
ContainerAliasFee int ContainerAliasFee int
@ -31,8 +33,10 @@ alphabet-wallets: {{ .AlphabetDir}}
network: network:
max_object_size: {{ .MaxObjectSize}} max_object_size: {{ .MaxObjectSize}}
epoch_duration: {{ .EpochDuration}} epoch_duration: {{ .EpochDuration}}
basic_income_rate: {{ .BasicIncomeRate}}
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}} homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
fee: fee:
audit: {{ .AuditFee}}
candidate: {{ .CandidateFee}} candidate: {{ .CandidateFee}}
container: {{ .ContainerFee}} container: {{ .ContainerFee}}
container_alias: {{ .ContainerAliasFee }} container_alias: {{ .ContainerAliasFee }}
@ -43,7 +47,7 @@ credentials:
{{.}}: password{{end}} {{.}}: password{{end}}
` `
func initConfig(cmd *cobra.Command, _ []string) error { func initConfig(cmd *cobra.Command, args []string) error {
configPath, err := readConfigPathFromArgs(cmd) configPath, err := readConfigPathFromArgs(cmd)
if err != nil { if err != nil {
return nil return nil
@ -107,7 +111,9 @@ func generateConfigExample(appDir string, credSize int) (string, error) {
Endpoint: "https://neo.rpc.node:30333", Endpoint: "https://neo.rpc.node:30333",
MaxObjectSize: 67108864, // 64 MiB MaxObjectSize: 67108864, // 64 MiB
EpochDuration: 240, // 1 hour with 15s per block 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 HomomorphicHashDisabled: false, // object homomorphic hash is enabled
AuditFee: 1_0000, // 0.00000001 GAS per audit (Fixed12)
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8) CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12) ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
ContainerAliasFee: 500, // ContainerFee / 2 ContainerAliasFee: 500, // ContainerFee / 2

View file

@ -5,7 +5,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -28,6 +28,8 @@ func TestGenerateConfigExample(t *testing.T) {
require.Equal(t, filepath.Join(appDir, "alphabet-wallets"), v.GetString("alphabet-wallets")) 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, 67108864, v.GetInt("network.max_object_size"))
require.Equal(t, 240, v.GetInt("network.epoch_duration")) 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, 10000000000, v.GetInt("network.fee.candidate"))
require.Equal(t, 1000, v.GetInt("network.fee.container")) require.Equal(t, 1000, v.GetInt("network.fee.container"))
require.Equal(t, 100000000, v.GetInt("network.fee.withdraw")) require.Equal(t, 100000000, v.GetInt("network.fee.withdraw"))

View file

@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/TrueCloudLab/frostfs-contract/nns"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "github.com/TrueCloudLab/frostfs-sdk-go/netmap"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -37,6 +37,11 @@ const (
dumpBalancesAlphabetFlag = "alphabet" dumpBalancesAlphabetFlag = "alphabet"
dumpBalancesProxyFlag = "proxy" dumpBalancesProxyFlag = "proxy"
dumpBalancesUseScriptHashFlag = "script-hash" 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 { func dumpBalances(cmd *cobra.Command, _ []string) error {
@ -55,7 +60,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
if dumpStorage || dumpAlphabet || dumpProxy { if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy {
nnsCs, err = c.GetContractStateByID(1) nnsCs, err = c.GetContractStateByID(1)
if err != nil { if err != nil {
return fmt.Errorf("can't get NNS contract info: %w", err) 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 { if err != nil {
return err return err
} }
@ -78,126 +83,120 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
printBalances(cmd, "Inner ring nodes balances:", irList) printBalances(cmd, "Inner ring nodes balances:", irList)
if dumpStorage { if dumpStorage {
if err := printStorageNodeBalances(cmd, inv, nmHash); err != nil { arr, err := unwrap.Array(inv.Call(nmHash, "netmap"))
if err != nil {
return errors.New("can't fetch the list of storage nodes")
}
snList := make([]accBalancePair, len(arr))
for i := range arr {
node, ok := arr[i].Value().([]stackitem.Item)
if !ok || len(node) == 0 {
return errors.New("can't parse the list of storage nodes")
}
bs, err := node[0].TryBytes()
if err != nil {
return errors.New("can't parse the list of storage nodes")
}
var ni netmap.NodeInfo
if err := ni.Unmarshal(bs); err != nil {
return fmt.Errorf("can't parse the list of storage nodes: %w", err)
}
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
if err != nil {
return fmt.Errorf("can't parse storage node public key: %w", err)
}
snList[i].scriptHash = pub.GetScriptHash()
}
if err := fetchBalances(inv, gas.Hash, snList); err != nil {
return err return err
} }
printBalances(cmd, "\nStorage node balances:", snList)
} }
if dumpProxy { if dumpProxy {
if err := printProxyContractBalance(cmd, inv, nnsCs.Hash); err != nil { h, err := nnsResolveHash(inv, nnsCs.Hash, proxyContract+".frostfs")
if err != nil {
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
}
proxyList := []accBalancePair{{scriptHash: h}}
if err := fetchBalances(inv, gas.Hash, proxyList); err != nil {
return err return err
} }
printBalances(cmd, "\nProxy contract balance:", proxyList)
} }
if dumpAlphabet { if dumpAlphabet {
if err := printAlphabetContractBalances(cmd, c, inv, len(irList), nnsCs.Hash); err != nil { alphaList := make([]accBalancePair, len(irList))
w := io.NewBufBinWriter()
for i := range alphaList {
emit.AppCall(w.BinWriter, nnsCs.Hash, "resolve", callflag.ReadOnly,
getAlphabetNNSDomain(i),
int64(nns.TXT))
}
if w.Err != nil {
panic(w.Err)
}
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
if err != nil {
return fmt.Errorf("can't fetch info from NNS: %w", err)
}
for i := range alphaList {
h, err := parseNNSResolveResult(alphaRes.Stack[i])
if err != nil {
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
}
alphaList[i].scriptHash = h
}
if err := fetchBalances(inv, gas.Hash, alphaList); err != nil {
return err return err
} }
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
} }
return nil return nil
} }
func printStorageNodeBalances(cmd *cobra.Command, inv *invoker.Invoker, nmHash util.Uint160) error { func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, error) {
arr, err := unwrap.Array(inv.Call(nmHash, "netmap")) var irList []accBalancePair
if err != nil {
return errors.New("can't fetch the list of storage nodes")
}
snList := make([]accBalancePair, len(arr))
for i := range arr {
node, ok := arr[i].Value().([]stackitem.Item)
if !ok || len(node) == 0 {
return errors.New("can't parse the list of storage nodes")
}
bs, err := node[0].TryBytes()
if err != nil {
return errors.New("can't parse the list of storage nodes")
}
var ni netmap.NodeInfo
if err := ni.Unmarshal(bs); err != nil {
return fmt.Errorf("can't parse the list of storage nodes: %w", err)
}
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
if err != nil {
return fmt.Errorf("can't parse storage node public key: %w", err)
}
snList[i].scriptHash = pub.GetScriptHash()
}
if err := fetchBalances(inv, gas.Hash, snList); err != nil {
return err
}
printBalances(cmd, "\nStorage node balances:", snList)
return nil
}
func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error {
h, err := nnsResolveHash(inv, nnsHash, proxyContract+".frostfs")
if err != nil {
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
}
proxyList := []accBalancePair{{scriptHash: h}}
if err := fetchBalances(inv, gas.Hash, proxyList); err != nil {
return err
}
printBalances(cmd, "\nProxy contract balance:", proxyList)
return nil
}
func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
alphaList := make([]accBalancePair, count)
w := io.NewBufBinWriter()
for i := range alphaList {
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
getAlphabetNNSDomain(i),
int64(nns.TXT))
}
if w.Err != nil {
panic(w.Err)
}
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
if err != nil {
return fmt.Errorf("can't fetch info from NNS: %w", err)
}
for i := range alphaList {
h, err := parseNNSResolveResult(alphaRes.Stack[i])
if err != nil {
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
}
alphaList[i].scriptHash = h
}
if err := fetchBalances(inv, gas.Hash, alphaList); err != nil {
return err
}
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
return nil
}
func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
height, err := c.GetBlockCount() if notaryEnabled {
if err != nil { height, err := c.GetBlockCount()
return nil, fmt.Errorf("can't get block height: %w", err) if err != nil {
} return nil, fmt.Errorf("can't get block height: %w", err)
}
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height) arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
if err != nil { if err != nil {
return nil, errors.New("can't fetch list of IR nodes from the netmap contract") return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
} }
irList := make([]accBalancePair, len(arr)) irList = make([]accBalancePair, len(arr))
for i := range arr { for i := range arr {
irList[i].scriptHash = arr[i].GetScriptHash() 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 return irList, nil
} }

View file

@ -10,12 +10,12 @@ import (
"strings" "strings"
"text/tabwriter" "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/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "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/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "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/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -48,24 +48,41 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0) tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
m, err := parseConfigFromNetmapContract(arr) for _, param := range arr {
if err != nil { tuple, ok := param.Value().([]stackitem.Item)
return err if !ok || len(tuple) != 2 {
} return errors.New("invalid ListConfig response from netmap contract")
for k, v := range m { }
switch k {
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig, k, err := tuple[0].TryBytes()
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig, if err != nil {
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig: 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) nbuf := make([]byte, 8)
copy(nbuf[:], v) copy(nbuf[:], v)
n := binary.LittleEndian.Uint64(nbuf) n := binary.LittleEndian.Uint64(nbuf)
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n))) _, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%d (int)\n", k, n)))
case netmap.HomomorphicHashingDisabledKey, netmap.MaintenanceModeAllowedConfig: case netmapEigenTrustAlphaKey:
if len(v) == 0 || len(v) > 1 { _, _ = 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) 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: default:
_, _ = tw.Write([]byte(fmt.Sprintf("%s:\t%s (hex)\n", k, hex.EncodeToString(v)))) _, _ = 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 valRaw := v
switch key { switch key {
case netmap.ContainerFeeConfig, netmap.ContainerAliasFeeConfig, case netmapAuditFeeKey, netmapBasicIncomeRateKey,
netmap.EpochDurationConfig, netmap.IrCandidateFeeConfig, netmapContainerFeeKey, netmapContainerAliasFeeKey,
netmap.MaxObjectSizeConfig, netmap.WithdrawFeeConfig: netmapEigenTrustIterationsKey,
netmapEpochKey, netmapInnerRingCandidateFeeKey,
netmapMaxObjectSizeKey, netmapWithdrawFeeKey:
val, err = strconv.ParseInt(valRaw, 10, 64) val, err = strconv.ParseInt(valRaw, 10, 64)
if err != nil { if err != nil {
err = fmt.Errorf("could not parse %s's value '%s' as int: %w", key, valRaw, err) 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) val, err = strconv.ParseBool(valRaw)
if err != nil { if err != nil {
err = fmt.Errorf("could not parse %s's value '%s' as bool: %w", key, valRaw, err) 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 return
} }
func invalidConfigValueErr(key string) error { func invalidConfigValueErr(key []byte) error {
return fmt.Errorf("invalid %s config value from netmap contract", key) return fmt.Errorf("invalid %s config value from netmap contract", key)
} }

View file

@ -7,7 +7,7 @@ import (
"os" "os"
"sort" "sort"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io" "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/invoker"
@ -41,28 +41,16 @@ func getContainerContractHash(cmd *cobra.Command, inv *invoker.Invoker, c Client
return ch, nil return ch, nil
} }
func iterateContainerList(inv *invoker.Invoker, ch util.Uint160, f func([]byte) error) error { func getContainersList(inv *invoker.Invoker, ch util.Uint160) ([][]byte, error) {
sid, r, err := unwrap.SessionIterator(inv.Call(ch, "containersOf", "")) res, err := inv.Call(ch, "list", "")
if err != nil { if err != nil {
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err) return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
} }
// Nothing bad, except live session on the server, do not report to the user. itm, err := unwrap.Item(res, err)
defer func() { _ = inv.TerminateSession(sid) }() if _, ok := itm.(stackitem.Null); !ok {
return unwrap.ArrayOfBytes(res, err)
items, err := inv.TraverseIterator(sid, &r, 0)
for err == nil && len(items) != 0 {
for j := range items {
b, err := items[j].TryBytes()
if err != nil {
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
if err := f(b); err != nil {
return err
}
}
items, err = inv.TraverseIterator(sid, &r, 0)
} }
return err return nil, nil
} }
func dumpContainers(cmd *cobra.Command, _ []string) error { func dumpContainers(cmd *cobra.Command, _ []string) error {
@ -83,81 +71,56 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("unable to get contaract hash: %w", err) return fmt.Errorf("unable to get contaract hash: %w", err)
} }
cids, err := getContainersList(inv, ch)
if err != nil {
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
isOK, err := getCIDFilterFunc(cmd) isOK, err := getCIDFilterFunc(cmd)
if err != nil { if err != nil {
return err return err
} }
f, err := os.OpenFile(filename, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0o660) var containers []*Container
if err != nil {
return err
}
defer f.Close()
_, err = f.Write([]byte{'['})
if err != nil {
return err
}
written := 0
enc := json.NewEncoder(f)
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
iterErr := iterateContainerList(inv, ch, func(id []byte) error { for _, id := range cids {
if !isOK(id) { if !isOK(id) {
return nil continue
} }
bw.Reset()
cnt, err := dumpSingleContainer(bw, ch, inv, id) emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
res, err := inv.Run(bw.Bytes())
if err != nil { if err != nil {
return err return fmt.Errorf("can't get container info: %w", err)
}
if len(res.Stack) != 2 {
return fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
} }
// Writing directly to the file is ok, because json.Encoder does no internal buffering. cnt := new(Container)
if written != 0 { err = cnt.FromStackItem(res.Stack[0])
_, err = f.Write([]byte{','}) if err != nil {
if err != nil { return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
return err
}
} }
written++ ea := new(EACL)
return enc.Encode(cnt) err = ea.FromStackItem(res.Stack[1])
}) if err != nil {
if iterErr != nil { return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
return iterErr }
if len(ea.Value) != 0 {
cnt.EACL = ea
}
containers = append(containers, cnt)
} }
_, err = f.Write([]byte{']'}) out, err := json.Marshal(containers)
return err
}
func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) {
bw.Reset()
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, id)
emit.AppCall(bw.BinWriter, ch, "eACL", callflag.All, id)
res, err := inv.Run(bw.Bytes())
if err != nil { if err != nil {
return nil, fmt.Errorf("can't get container info: %w", err) return err
} }
if len(res.Stack) != 2 { return os.WriteFile(filename, out, 0o660)
return nil, fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
}
cnt := new(Container)
err = cnt.FromStackItem(res.Stack[0])
if err != nil {
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
ea := new(EACL)
err = ea.FromStackItem(res.Stack[1])
if err != nil {
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
if len(ea.Value) != 0 {
cnt.EACL = ea
}
return cnt, nil
} }
func listContainers(cmd *cobra.Command, _ []string) error { func listContainers(cmd *cobra.Command, _ []string) error {
@ -173,15 +136,20 @@ func listContainers(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("unable to get contaract hash: %w", err) return fmt.Errorf("unable to get contaract hash: %w", err)
} }
return iterateContainerList(inv, ch, func(id []byte) error { cids, err := getContainersList(inv, ch)
if err != nil {
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
for _, id := range cids {
var idCnr cid.ID var idCnr cid.ID
err = idCnr.Decode(id) err = idCnr.Decode(id)
if err != nil { if err != nil {
return fmt.Errorf("unable to decode container id: %w", err) return fmt.Errorf("unable to decode container id: %w", err)
} }
cmd.Println(idCnr) cmd.Println(idCnr)
return nil }
}) return nil
} }
func restoreContainers(cmd *cobra.Command, _ []string) error { func restoreContainers(cmd *cobra.Command, _ []string) error {
@ -196,14 +164,25 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
} }
defer wCtx.close() defer wCtx.close()
containers, err := parseContainers(filename) nnsCs, err := wCtx.Client.GetContractStateByID(1)
if err != nil { if err != nil {
return err return fmt.Errorf("can't get NNS contract state: %w", err)
} }
ch, err := fetchContainerContractHash(wCtx) ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs")
if err != nil { if err != nil {
return err return fmt.Errorf("can't fetch container contract hash: %w", err)
}
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("can't read dump file: %w", err)
}
var containers []Container
err = json.Unmarshal(data, &containers)
if err != nil {
return fmt.Errorf("can't parse dump file: %w", err)
} }
isOK, err := getCIDFilterFunc(cmd) isOK, err := getCIDFilterFunc(cmd)
@ -211,15 +190,6 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
return err return err
} }
err = restoreOrPutContainers(containers, isOK, cmd, wCtx, ch)
if err != nil {
return err
}
return wCtx.awaitTx()
}
func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *initializeContext, ch util.Uint160) error {
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
for _, cnt := range containers { for _, cnt := range containers {
hv := hash.Sha256(cnt.Value) hv := hash.Sha256(cnt.Value)
@ -227,18 +197,33 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
continue continue
} }
bw.Reset() bw.Reset()
restored, err := isContainerRestored(cmd, wCtx, ch, bw, hv) emit.AppCall(bw.BinWriter, ch, "get", callflag.All, hv.BytesBE())
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
if err != nil { if err != nil {
return err return fmt.Errorf("can't check if container is already restored: %w", err)
} }
if restored { if len(res.Stack) == 0 {
return errors.New("empty stack")
}
old := new(Container)
if err := old.FromStackItem(res.Stack[0]); err != nil {
return fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
if len(old.Value) != 0 {
var id cid.ID
id.SetSHA256(hv)
cmd.Printf("Container %s is already deployed.\n", id)
continue continue
} }
bw.Reset() bw.Reset()
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
putContainer(bw, ch, cnt) cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token)
if ea := cnt.EACL; ea != nil {
emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All,
ea.Value, ea.Signature, ea.PublicKey, ea.Token)
}
if bw.Err != nil { if bw.Err != nil {
panic(bw.Err) panic(bw.Err)
} }
@ -247,67 +232,8 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
return err return err
} }
} }
return nil
}
func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) { return wCtx.awaitTx()
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token)
if ea := cnt.EACL; ea != nil {
emit.AppCall(bw.BinWriter, ch, "setEACL", callflag.All,
ea.Value, ea.Signature, ea.PublicKey, ea.Token)
}
}
func isContainerRestored(cmd *cobra.Command, wCtx *initializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE())
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
if err != nil {
return false, fmt.Errorf("can't check if container is already restored: %w", err)
}
if len(res.Stack) == 0 {
return false, errors.New("empty stack")
}
old := new(Container)
if err := old.FromStackItem(res.Stack[0]); err != nil {
return false, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
}
if len(old.Value) != 0 {
var id cid.ID
id.SetSHA256(hashValue)
cmd.Printf("Container %s is already deployed.\n", id)
return true, nil
}
return false, nil
}
func parseContainers(filename string) ([]Container, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("can't read dump file: %w", err)
}
var containers []Container
err = json.Unmarshal(data, &containers)
if err != nil {
return nil, fmt.Errorf("can't parse dump file: %w", err)
}
return containers, nil
}
func fetchContainerContractHash(wCtx *initializeContext) (util.Uint160, error) {
nnsCs, err := wCtx.Client.GetContractStateByID(1)
if err != nil {
return util.Uint160{}, fmt.Errorf("can't get NNS contract state: %w", err)
}
ch, err := nnsResolveHash(wCtx.ReadOnlyInvoker, nnsCs.Hash, containerContract+".frostfs")
if err != nil {
return util.Uint160{}, fmt.Errorf("can't fetch container contract hash: %w", err)
}
return ch, nil
} }
// Container represents container struct in contract storage. // Container represents container struct in contract storage.

View file

@ -6,7 +6,7 @@ import (
"os" "os"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/TrueCloudLab/frostfs-contract/nns"
"github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/cmdargs"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -100,88 +100,80 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
cs.Manifest.Name) cs.Manifest.Name)
} }
writer := io.NewBufBinWriter() w := io.NewBufBinWriter()
if err := emitDeploymentArguments(writer.BinWriter, args); err != nil { if err := emitDeploymentArguments(w.BinWriter, args); err != nil {
return err return err
} }
emit.Bytes(writer.BinWriter, cs.RawManifest) emit.Bytes(w.BinWriter, cs.RawManifest)
emit.Bytes(writer.BinWriter, cs.RawNEF) emit.Bytes(w.BinWriter, cs.RawNEF)
emit.Int(writer.BinWriter, 3) emit.Int(w.BinWriter, 3)
emit.Opcodes(writer.BinWriter, opcode.PACK) emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(writer.BinWriter, callHash, method, callflag.All) emit.AppCallNoArgs(w.BinWriter, callHash, method, callflag.All)
emit.Opcodes(writer.BinWriter, opcode.DROP) // contract state on stack emit.Opcodes(w.BinWriter, opcode.DROP) // contract state on stack
if !isUpdate { if !isUpdate {
err := registerNNS(nnsCs, c, zone, domain, cs, writer) bw := io.NewBufBinWriter()
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
emit.Opcodes(bw.BinWriter, opcode.STSFLD0)
emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1)
start := bw.Len()
needRecord := false
ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
if err != nil { if err != nil {
return err return err
} else if !ok {
needRecord = true
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
zone, c.CommitteeAcc.Contract.ScriptHash(),
"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(),
"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)
if err != nil {
return err
}
needRecord = !ok
if len(s) != 0 {
bw.WriteBytes(s)
}
}
if needRecord {
emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All,
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
}
if bw.Err != nil {
panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err))
} else if bw.Len() != start {
w.WriteBytes(bw.Bytes())
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
if needRecord {
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
}
} }
} }
if writer.Err != nil { if w.Err != nil {
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err)) panic(fmt.Errorf("BUG: can't create deployment script: %w", w.Err))
} }
if err := c.sendCommitteeTx(writer.Bytes(), false); err != nil { if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
return err return err
} }
return c.awaitTx() return c.awaitTx()
} }
func registerNNS(nnsCs *state.Contract, c *initializeContext, zone string, domain string, cs *contractState, writer *io.BufBinWriter) error {
bw := io.NewBufBinWriter()
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All)
emit.Opcodes(bw.BinWriter, opcode.STSFLD0)
emit.AppCall(bw.BinWriter, nnsCs.Hash, "setPrice", callflag.All, 1)
start := bw.Len()
needRecord := false
ok, err := c.nnsRootRegistered(nnsCs.Hash, zone)
if err != nil {
return err
} else if !ok {
needRecord = true
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
zone, c.CommitteeAcc.Contract.ScriptHash(),
"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(),
"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)
if err != nil {
return err
}
needRecord = !ok
if len(s) != 0 {
bw.WriteBytes(s)
}
}
if needRecord {
emit.AppCall(bw.BinWriter, nnsCs.Hash, "deleteRecords", callflag.All, domain, int64(nns.TXT))
emit.AppCall(bw.BinWriter, nnsCs.Hash, "addRecord", callflag.All,
domain, int64(nns.TXT), address.Uint160ToString(cs.Hash))
}
if bw.Err != nil {
panic(fmt.Errorf("BUG: can't create deployment script: %w", writer.Err))
} else if bw.Len() != start {
writer.WriteBytes(bw.Bytes())
emit.Opcodes(writer.BinWriter, opcode.LDSFLD0, opcode.PUSH1, opcode.PACK)
emit.AppCallNoArgs(writer.BinWriter, nnsCs.Hash, "setPrice", callflag.All)
if needRecord {
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
}
}
return nil
}
func emitDeploymentArguments(w *io.BinWriter, args []string) error { func emitDeploymentArguments(w *io.BinWriter, args []string) error {
_, ps, err := cmdargs.ParseParams(args, true) _, ps, err := cmdargs.ParseParams(args, true)
if err != nil { if err != nil {

View 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
}

View file

@ -7,8 +7,7 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/TrueCloudLab/frostfs-contract/nns"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "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/rpcclient/unwrap"
@ -124,7 +123,7 @@ func dumpCustomZoneHashes(cmd *cobra.Command, nnsHash util.Uint160, zone string,
return return
} }
if !bytes.HasSuffix(bs, []byte(zone)) || bytes.HasPrefix(bs, []byte(morphClient.NNSGroupKeyName)) { if !bytes.HasSuffix(bs, []byte(zone)) {
// Related https://github.com/nspcc-dev/neofs-contract/issues/316. // Related https://github.com/nspcc-dev/neofs-contract/issues/316.
return return
} }

View file

@ -3,7 +3,6 @@ package morph
import ( import (
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
@ -14,7 +13,7 @@ import (
"github.com/spf13/viper" "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()) wCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't to initialize context: %w", err) return fmt.Errorf("can't to initialize context: %w", err)
@ -39,14 +38,7 @@ func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
return err return err
} }
if err := wCtx.awaitTx(); err != nil { return wCtx.awaitTx()
if strings.Contains(err.Error(), "invalid epoch") {
cmd.Println("Epoch has already ticked.")
return nil
}
return err
}
return nil
} }
func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error { func emitNewEpochCall(bw *io.BufBinWriter, wCtx *initializeContext, nmHash util.Uint160) error {

View file

@ -6,8 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
@ -21,7 +21,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/sync/errgroup"
) )
const ( const (
@ -30,7 +29,7 @@ const (
consensusAccountName = "consensus" 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 // alphabet size is not part of the config
size, err := cmd.Flags().GetUint(alphabetSizeFlag) size, err := cmd.Flags().GetUint(alphabetSizeFlag)
if err != nil { if err != nil {
@ -39,9 +38,6 @@ func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
if size == 0 { if size == 0 {
return errors.New("size must be > 0") return errors.New("size must be > 0")
} }
if size > maxAlphabetNodes {
return ErrTooManyAlphabetNodes
}
v := viper.GetViper() v := viper.GetViper()
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) 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() pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
} }
var errG errgroup.Group
// Create committee account with N/2+1 multi-signature. // Create committee account with N/2+1 multi-signature.
majCount := smartcontract.GetMajorityHonestNodeCount(size) 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. // Create consensus account with 2*N/3+1 multi-signature.
bftCount := smartcontract.GetDefaultHonestNodeCount(size) bftCount := smartcontract.GetDefaultHonestNodeCount(size)
for i := range wallets { for i, w := range wallets {
i := i if err := addMultisigAccount(w, bftCount, consensusAccountName, passwords[i], pubs); err != nil {
ps := pubs.Copy() return nil, fmt.Errorf("can't create consensus account: %w", err)
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
})
} }
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 return passwords, nil
} }

View file

@ -7,10 +7,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"sync"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -72,32 +71,24 @@ func TestGenerateAlphabet(t *testing.T) {
buf.WriteString(testContractPassword + "\r") buf.WriteString(testContractPassword + "\r")
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil)) require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
var wg sync.WaitGroup
for i := uint64(0); i < size; i++ { for i := uint64(0); i < size; i++ {
i := i p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
wg.Add(1) w, err := wallet.NewWalletFromFile(p)
go func() { require.NoError(t, err, "wallet doesn't exist")
defer wg.Done() require.Equal(t, 3, len(w.Accounts), "not all accounts were created")
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json") for _, a := range w.Accounts {
w, err := wallet.NewWalletFromFile(p) err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
require.NoError(t, err, "wallet doesn't exist") require.NoError(t, err, "can't decrypt account")
require.Equal(t, 3, len(w.Accounts), "not all accounts were created") switch a.Label {
case consensusAccountName:
for _, a := range w.Accounts { require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams()) case committeeAccountName:
require.NoError(t, err, "can't decrypt account") require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
switch a.Label { default:
case consensusAccountName: require.Equal(t, singleAccountName, a.Label)
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) { t.Run("check contract group wallet", func(t *testing.T) {
p := filepath.Join(walletDir, contractWalletFilename) p := filepath.Join(walletDir, contractWalletFilename)

View file

@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"

View file

@ -7,9 +7,9 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" morphClient "github.com/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -23,13 +23,6 @@ import (
"github.com/spf13/viper" "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 { type cache struct {
nnsCs *state.Contract nnsCs *state.Contract
groupKey *keys.PublicKey groupKey *keys.PublicKey
@ -52,9 +45,7 @@ type initializeContext struct {
ContractPath string ContractPath string
} }
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes) func initializeSideChainCmd(cmd *cobra.Command, args []string) error {
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
initCtx, err := newInitializeContext(cmd, viper.GetViper()) initCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("initialization error: %w", err) 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.") 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() { func (c *initializeContext) close() {
@ -120,21 +115,27 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
return nil, err return nil, err
} }
if len(wallets) > maxAlphabetNodes {
return nil, ErrTooManyAlphabetNodes
}
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init" needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
var w *wallet.Wallet var w *wallet.Wallet
w, err = getWallet(cmd, v, needContracts, walletDir) if needContracts {
if err != nil { w, err = openContractWallet(v, cmd, walletDir)
return nil, err if err != nil {
return nil, err
}
} }
c, err := createClient(cmd, v, wallets) var c Client
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)
} else {
c, err = getN3Client(v)
}
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("can't create N3 client: %w", err)
} }
committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName) committeeAcc, err := getWalletAccount(wallets[0], committeeAccountName)
@ -147,22 +148,35 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
return nil, fmt.Errorf("can't find consensus account: %w", err) return nil, fmt.Errorf("can't find consensus account: %w", err)
} }
if err := validateInit(cmd); err != nil { var ctrPath string
return nil, err if cmd.Name() == "init" {
if viper.GetInt64(epochDurationInitFlag) <= 0 {
return nil, fmt.Errorf("epoch duration must be positive")
}
if viper.GetInt64(maxObjectSizeInitFlag) <= 0 {
return nil, fmt.Errorf("max object size must be positive")
}
} }
ctrPath, err := getContractsPath(cmd, needContracts) if needContracts {
if err != nil { ctrPath, err = cmd.Flags().GetString(contractsInitFlag)
return nil, err if err != nil {
return nil, fmt.Errorf("invalid contracts path: %w", err)
}
} }
if err := checkNotaryEnabled(c); err != nil { if err := checkNotaryEnabled(c); err != nil {
return nil, err return nil, err
} }
accounts, err := createWalletAccounts(wallets) accounts := make([]*wallet.Account, len(wallets))
if err != nil { for i, w := range wallets {
return nil, err acc, err := getWalletAccount(w, singleAccountName)
if err != nil {
return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err)
}
accounts[i] = acc
} }
cliCtx, err := defaultClientContext(c, committeeAcc) cliCtx, err := defaultClientContext(c, committeeAcc)
@ -192,69 +206,6 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
return initCtx, nil return initCtx, nil
} }
func validateInit(cmd *cobra.Command) error {
if cmd.Name() != "init" {
return nil
}
if viper.GetInt64(epochDurationInitFlag) <= 0 {
return fmt.Errorf("epoch duration must be positive")
}
if viper.GetInt64(maxObjectSizeInitFlag) <= 0 {
return fmt.Errorf("max object size must be positive")
}
return nil
}
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) {
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
}
c, err = newLocalClient(cmd, v, wallets, ldf.Value.String())
} else {
c, err = getN3Client(v)
}
if err != nil {
return nil, fmt.Errorf("can't create N3 client: %w", err)
}
return c, nil
}
func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) {
if !needContracts {
return nil, nil
}
return openContractWallet(v, cmd, walletDir)
}
func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) {
if !needContracts {
return "", nil
}
ctrPath, err := cmd.Flags().GetString(contractsInitFlag)
if err != nil {
return "", fmt.Errorf("invalid contracts path: %w", err)
}
return ctrPath, nil
}
func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) {
accounts := make([]*wallet.Account, len(wallets))
for i, w := range wallets {
acc, err := getWalletAccount(w, singleAccountName)
if err != nil {
return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err)
}
accounts[i] = acc
}
return accounts, nil
}
func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) { func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, error) {
walletFiles, err := os.ReadDir(walletDir) walletFiles, err := os.ReadDir(walletDir)
if err != nil { if err != nil {

View file

@ -12,11 +12,10 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "github.com/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/TrueCloudLab/frostfs-contract/nns"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" morphClient "github.com/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/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "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"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management" "github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "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/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "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/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate" "github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/spf13/viper"
) )
const ( const (
@ -41,20 +39,44 @@ const (
frostfsContract = "frostfs" // not deployed in side-chain. frostfsContract = "frostfs" // not deployed in side-chain.
processingContract = "processing" // not deployed in side-chain. processingContract = "processing" // not deployed in side-chain.
alphabetContract = "alphabet" alphabetContract = "alphabet"
auditContract = "audit"
balanceContract = "balance" balanceContract = "balance"
containerContract = "container" containerContract = "container"
frostfsIDContract = "frostfsid" frostfsIDContract = "frostfsid"
netmapContract = "netmap" netmapContract = "netmap"
proxyContract = "proxy" 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 ( var (
contractList = []string{ contractList = []string{
auditContract,
balanceContract, balanceContract,
containerContract, containerContract,
frostfsIDContract, frostfsIDContract,
netmapContract, netmapContract,
proxyContract, proxyContract,
reputationContract,
subnetContract,
} }
fullContractList = append([]string{ fullContractList = append([]string{
@ -63,17 +85,6 @@ var (
nnsContract, nnsContract,
alphabetContract, alphabetContract,
}, contractList...) }, contractList...)
netmapConfigKeys = []string{
netmap.EpochDurationConfig,
netmap.MaxObjectSizeConfig,
netmap.ContainerFeeConfig,
netmap.ContainerAliasFeeConfig,
netmap.IrCandidateFeeConfig,
netmap.WithdrawFeeConfig,
netmap.HomomorphicHashingDisabledKey,
netmap.MaintenanceModeAllowedConfig,
}
) )
type contractState struct { type contractState struct {
@ -156,6 +167,8 @@ func (c *initializeContext) updateContracts() error {
w := io2.NewBufBinWriter() w := io2.NewBufBinWriter()
var keysParam []any
// Update script size for a single-node committee is close to the maximum allowed size of 65535. // Update script size for a single-node committee is close to the maximum allowed size of 65535.
// Because of this we want to reuse alphabet contract NEF and manifest for different updates. // Because of this we want to reuse alphabet contract NEF and manifest for different updates.
// The generated script is as following. // The generated script is as following.
@ -169,36 +182,42 @@ func (c *initializeContext) updateContracts() error {
emit.Bytes(w.BinWriter, alphaCs.RawNEF) emit.Bytes(w.BinWriter, alphaCs.RawNEF)
emit.Opcodes(w.BinWriter, opcode.STSFLD0) emit.Opcodes(w.BinWriter, opcode.STSFLD0)
keysParam, err := c.deployAlphabetAccounts(nnsHash, w, alphaCs) baseGroups := alphaCs.Manifest.Groups
if err != nil {
return err // alphabet contracts should be deployed by individual nodes to get different hashes.
for i, acc := range c.Accounts {
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i))
if err != nil {
return fmt.Errorf("can't resolve hash for contract update: %w", err)
}
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
params := c.getAlphabetDeployItems(i, len(c.Wallets))
emit.Array(w.BinWriter, params...)
alphaCs.Manifest.Groups = baseGroups
err = c.addManifestGroup(ctrHash, alphaCs)
if err != nil {
return fmt.Errorf("can't sign manifest group: %v", err)
}
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
emit.Int(w.BinWriter, 3)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All)
}
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
return err
}
c.Command.Println("Alphabet contracts are already updated.")
} }
w.Reset() w.Reset()
if err = c.deployOrUpdateContracts(w, nnsHash, keysParam); err != nil {
return err
}
groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
_, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey)
if err != nil {
return err
}
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
emit.Int(w.BinWriter, 1)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
return err
}
return c.awaitTx()
}
func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash util.Uint160, keysParam []any) error {
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1}) emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All) emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
emit.Opcodes(w.BinWriter, opcode.STSFLD0) emit.Opcodes(w.BinWriter, opcode.STSFLD0)
@ -228,7 +247,7 @@ func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash
invokeHash = ctrHash 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...) res, err := c.CommitteeAct.MakeCall(invokeHash, method, params...)
if err != nil { if err != nil {
if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) { if method != updateMethodName || !strings.Contains(err.Error(), common.ErrAlreadyUpdated) {
@ -258,46 +277,23 @@ func (c *initializeContext) deployOrUpdateContracts(w *io2.BufBinWriter, nnsHash
c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE()) c.Command.Printf("NNS: Set %s -> %s\n", domain, cs.Hash.StringLE())
} }
} }
return nil
}
func (c *initializeContext) deployAlphabetAccounts(nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *contractState) ([]any, error) { groupKey := c.ContractWallet.Accounts[0].PrivateKey().PublicKey()
var keysParam []any _, _, err = c.emitUpdateNNSGroupScript(w, nnsHash, groupKey)
if err != nil {
baseGroups := alphaCs.Manifest.Groups return err
// alphabet contracts should be deployed by individual nodes to get different hashes.
for i, acc := range c.Accounts {
ctrHash, err := nnsResolveHash(c.ReadOnlyInvoker, nnsHash, getAlphabetNNSDomain(i))
if err != nil {
return nil, fmt.Errorf("can't resolve hash for contract update: %w", err)
}
keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes())
params := c.getAlphabetDeployItems(i, len(c.Wallets))
emit.Array(w.BinWriter, params...)
alphaCs.Manifest.Groups = baseGroups
err = c.addManifestGroup(ctrHash, alphaCs)
if err != nil {
return nil, fmt.Errorf("can't sign manifest group: %v", err)
}
emit.Bytes(w.BinWriter, alphaCs.RawManifest)
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
emit.Int(w.BinWriter, 3)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, ctrHash, updateMethodName, callflag.All)
} }
c.Command.Printf("NNS: Set %s -> %s\n", morphClient.NNSGroupKeyName, hex.EncodeToString(groupKey.Bytes()))
emit.Opcodes(w.BinWriter, opcode.LDSFLD0)
emit.Int(w.BinWriter, 1)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, nnsHash, "setPrice", callflag.All)
if err := c.sendCommitteeTx(w.Bytes(), false); err != nil { if err := c.sendCommitteeTx(w.Bytes(), false); err != nil {
if !strings.Contains(err.Error(), common.ErrAlreadyUpdated) { return err
return nil, err
}
c.Command.Println("Alphabet contracts are already updated.")
} }
return c.awaitTx()
return keysParam, nil
} }
func (c *initializeContext) deployContracts() error { func (c *initializeContext) deployContracts() error {
@ -351,7 +347,7 @@ func (c *initializeContext) deployContracts() error {
return fmt.Errorf("can't sign manifest group: %v", err) 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...) res, err := c.CommitteeAct.MakeCall(management.Hash, deployMethodName, params...)
if err != nil { if err != nil {
return fmt.Errorf("can't deploy %s contract: %w", ctrName, err) return fmt.Errorf("can't deploy %s contract: %w", ctrName, err)
@ -397,9 +393,11 @@ func (c *initializeContext) readContracts(names []string) error {
} else { } else {
var r io.ReadCloser var r io.ReadCloser
if c.ContractPath == "" { 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 { if err != nil {
return fmt.Errorf("can't open contracts archive: %w", err) return fmt.Errorf("can't open contracts archive: %w", err)
} }
@ -516,7 +514,7 @@ func getContractDeployParameters(cs *contractState, deployData []any) []any {
return []any{cs.RawNEF, cs.RawManifest, deployData} 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 := make([]any, 1, 6)
items[0] = false // notaryDisabled is false items[0] = false // notaryDisabled is false
@ -529,6 +527,8 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
case processingContract: case processingContract:
items = append(items, c.Contracts[frostfsContract].Hash) items = append(items, c.Contracts[frostfsContract].Hash)
return items[1:] // no notary info return items[1:] // no notary info
case auditContract:
items = append(items, c.Contracts[netmapContract].Hash)
case balanceContract: case balanceContract:
items = append(items, items = append(items,
c.Contracts[netmapContract].Hash, c.Contracts[netmapContract].Hash,
@ -551,31 +551,20 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
c.Contracts[netmapContract].Hash, c.Contracts[netmapContract].Hash,
c.Contracts[containerContract].Hash) c.Contracts[containerContract].Hash)
case netmapContract: case netmapContract:
md := getDefaultNetmapContractConfigMap() configParam := []any{
if method == updateMethodName { netmapEpochKey, viper.GetInt64(epochDurationInitFlag),
arr, err := c.getNetConfigFromNetmapContract() netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag),
if err != nil { netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag),
panic(err) netmapContainerFeeKey, viper.GetInt64(containerFeeInitFlag),
} netmapContainerAliasFeeKey, viper.GetInt64(containerAliasFeeInitFlag),
m, err := parseConfigFromNetmapContract(arr) netmapEigenTrustIterationsKey, int64(defaultEigenTrustIterations),
if err != nil { netmapEigenTrustAlphaKey, defaultEigenTrustAlpha,
panic(err) netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag),
} netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag),
for k, v := range m { netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag),
for _, key := range netmapConfigKeys { netmapHomomorphicHashDisabledKey, viper.GetBool(homomorphicHashDisabledInitFlag),
if k == key { netmapMaintenanceAllowedKey, viper.GetBool(maintenanceModeAllowedInitFlag),
md[k] = v
break
}
}
}
} }
var configParam []any
for k, v := range md {
configParam = append(configParam, k, v)
}
items = append(items, items = append(items,
c.Contracts[balanceContract].Hash, c.Contracts[balanceContract].Hash,
c.Contracts[containerContract].Hash, c.Contracts[containerContract].Hash,
@ -583,28 +572,14 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []an
configParam) configParam)
case proxyContract: case proxyContract:
items = nil items = nil
case reputationContract:
case subnetContract:
default: default:
panic(fmt.Sprintf("invalid contract name: %s", ctrName)) panic(fmt.Sprintf("invalid contract name: %s", ctrName))
} }
return items 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 { func (c *initializeContext) getAlphabetDeployItems(i, n int) []any {
items := make([]any, 6) items := make([]any, 6)
items[0] = false items[0] = false

View file

@ -7,15 +7,14 @@ import (
"strconv" "strconv"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "github.com/TrueCloudLab/frostfs-contract/nns"
morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" morphClient "github.com/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "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/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -83,13 +82,13 @@ func (c *initializeContext) setNNS() error {
func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error { func (c *initializeContext) updateNNSGroup(nnsHash util.Uint160, pub *keys.PublicKey) error {
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
keyAlreadyAdded, domainRegCodeEmitted, err := c.emitUpdateNNSGroupScript(bw, nnsHash, pub) needUpdate, needRegister, err := c.emitUpdateNNSGroupScript(bw, nnsHash, pub)
if keyAlreadyAdded || err != nil { if !needUpdate || err != nil {
return err return err
} }
script := bw.Bytes() script := bw.Bytes()
if domainRegCodeEmitted { if needRegister {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1}) emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
wrapRegisterScriptWithPrice(w, nnsHash, script) wrapRegisterScriptWithPrice(w, nnsHash, script)
@ -229,27 +228,20 @@ func nnsResolve(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (stac
} }
func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) { func nnsResolveKey(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (*keys.PublicKey, error) {
res, err := nnsResolve(inv, nnsHash, domain) item, err := nnsResolve(inv, nnsHash, domain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, ok := res.Value().(stackitem.Null); ok { v, ok := item.Value().(stackitem.Null)
if ok {
return nil, errors.New("NNS record is missing") return nil, errors.New("NNS record is missing")
} }
arr, ok := res.Value().([]stackitem.Item) bs, err := v.TryBytes()
if !ok { if err != nil {
return nil, errors.New("API of the NNS contract method `resolve` has changed") return nil, errors.New("malformed response")
} }
for i := range arr {
var bs []byte
bs, err = arr[i].TryBytes()
if err != nil {
continue
}
return keys.NewPublicKeyFromString(string(bs)) return keys.NewPublicKeyFromString(string(bs))
}
return nil, errors.New("no valid keys are found")
} }
// parseNNSResolveResult parses the result of resolving NNS record. // parseNNSResolveResult parses the result of resolving NNS record.
@ -285,11 +277,9 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) {
} }
func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) { func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) {
switch c.(type) { switch ct := c.(type) {
case *rpcclient.Client: case *rpcclient.Client:
inv := invoker.New(c, nil) return ct.NNSIsAvailable(nnsHash, name)
reader := nnsClient.NewReader(inv, nnsHash)
return reader.IsAvailable(name)
default: default:
b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []any{name}, nil)) b, err := unwrap.Bool(invokeFunction(c, nnsHash, "isAvailable", []any{name}, nil))
if err != nil { if err != nil {

View file

@ -9,7 +9,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
@ -19,24 +18,33 @@ import (
) )
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes. // initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
const ( const initialAlphabetNEOAmount = native.NEOTotalSupply
initialAlphabetNEOAmount = native.NEOTotalSupply
registerBatchSize = transaction.MaxAttributes - 1 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() regPrice, err := c.getCandidateRegisterPrice()
if err != nil { if err != nil {
return fmt.Errorf("can't fetch registration price: %w", err) return fmt.Errorf("can't fetch registration price: %w", err)
} }
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, 1) emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, 1)
for _, acc := range c.Accounts[start:end] { for _, acc := range c.Accounts {
emit.AppCall(w.BinWriter, neo.Hash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes()) emit.AppCall(w.BinWriter, neoHash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
emit.Opcodes(w.BinWriter, opcode.ASSERT) 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 { if w.Err != nil {
panic(fmt.Sprintf("BUG: %v", w.Err)) 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), Signer: c.getSigner(false, c.CommitteeAcc),
Account: c.CommitteeAcc, Account: c.CommitteeAcc,
}} }}
for _, acc := range c.Accounts[start:end] { for i := range c.Accounts {
signers = append(signers, rpcclient.SignerAccount{ signers = append(signers, rpcclient.SignerAccount{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(), Account: c.Accounts[i].Contract.ScriptHash(),
Scopes: transaction.CustomContracts, 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() network := c.CommitteeAct.GetNetwork()
for _, acc := range c.Accounts[start:end] { for i := range c.Accounts {
if err := acc.SignTx(network, tx); err != nil { if err := c.Accounts[i].SignTx(network, tx); err != nil {
return fmt.Errorf("can't sign a transaction: %w", err) 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) 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 { func (c *initializeContext) transferNEOToAlphabetContracts() error {
neoHash := neo.Hash neoHash := neo.Hash
@ -141,11 +116,9 @@ func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, err
var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response") var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
func (c *initializeContext) getCandidateRegisterPrice() (int64, error) { func (c *initializeContext) getCandidateRegisterPrice() (int64, error) {
switch c.Client.(type) { switch ct := c.Client.(type) {
case *rpcclient.Client: case *rpcclient.Client:
inv := invoker.New(c.Client, nil) return ct.GetCandidateRegisterPrice()
reader := neo.NewReader(inv)
return reader.GetRegisterPrice()
default: default:
neoHash := neo.Hash neoHash := neo.Hash
res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil) res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil)

View file

@ -2,14 +2,12 @@ package morph
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"testing" "testing"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" "github.com/TrueCloudLab/frostfs-node/pkg/innerring"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
@ -38,22 +36,13 @@ func TestInitialize(t *testing.T) {
t.Run("7 nodes", func(t *testing.T) { t.Run("7 nodes", func(t *testing.T) {
testInitialize(t, 7) 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) { func testInitialize(t *testing.T, committeeSize int) {
testdataDir := t.TempDir() testdataDir := t.TempDir()
v := viper.GetViper() v := viper.GetViper()
require.NoError(t, generateTestData(t, testdataDir, committeeSize)) generateTestData(t, testdataDir, committeeSize)
v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName)) v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
// Set to the path or remove the next statement to download from the network. // 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 := viper.GetViper()
v.Set(alphabetWalletsFlag, dir) v.Set(alphabetWalletsFlag, dir)
sizeStr := strconv.FormatUint(uint64(size), 10) sizeStr := strconv.FormatUint(uint64(size), 10)
if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil { require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr))
return err
}
setTestCredentials(v, size) setTestCredentials(v, size)
if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil { require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil))
return err
}
var pubs []string var pubs []string
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json") p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
w, err := wallet.NewWalletFromFile(p) w, err := wallet.NewWalletFromFile(p)
if err != nil { require.NoError(t, err, "wallet doesn't exist")
return fmt.Errorf("wallet doesn't exist: %w", err)
}
for _, acc := range w.Accounts { for _, acc := range w.Accounts {
if acc.Label == singleAccountName { if acc.Label == singleAccountName {
pub, ok := vm.ParseSignatureContract(acc.Contract.Script) pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
if !ok { require.True(t, ok)
return fmt.Errorf("could not parse signature script for %s", acc.Address)
}
pubs = append(pubs, hex.EncodeToString(pub)) pubs = append(pubs, hex.EncodeToString(pub))
continue continue
} }
@ -119,18 +100,17 @@ func generateTestData(t *testing.T, dir string, size int) error {
cfg := config.Config{} cfg := config.Config{}
cfg.ProtocolConfiguration.Magic = 12345 cfg.ProtocolConfiguration.Magic = 12345
cfg.ProtocolConfiguration.ValidatorsCount = uint32(size) cfg.ProtocolConfiguration.ValidatorsCount = size
cfg.ProtocolConfiguration.TimePerBlock = time.Second cfg.ProtocolConfiguration.SecondsPerBlock = 1
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
cfg.ProtocolConfiguration.P2PSigExtensions = true cfg.ProtocolConfiguration.P2PSigExtensions = true
cfg.ProtocolConfiguration.VerifyTransactions = true cfg.ProtocolConfiguration.VerifyTransactions = true
cfg.ProtocolConfiguration.VerifyBlocks = true
data, err := yaml.Marshal(cfg) data, err := yaml.Marshal(cfg)
if err != nil { require.NoError(t, err)
return err
}
protoPath := filepath.Join(dir, protoFileName) 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) { func setTestCredentials(v *viper.Viper, size int) {

View 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
}

Binary file not shown.

View file

@ -0,0 +1,15 @@
syntax = "proto3";
package neo.fs.v2.refs;
option go_package = "github.com/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"];
}

View file

@ -51,7 +51,7 @@ type localClient struct {
maxGasInvoke int64 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)) cfg, err := config.LoadFile(v.GetString(protoConfigPath))
if err != nil { if err != nil {
return nil, err return nil, err
@ -62,7 +62,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
return nil, err return nil, err
} }
m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount)) m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount)
accounts := make([]*wallet.Account, len(wallets)) accounts := make([]*wallet.Account, len(wallets))
for i := range accounts { for i := range accounts {
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName) 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() go bc.Run()
dumpPath := v.GetString(localDumpFlag)
if cmd.Name() != "init" { if cmd.Name() != "init" {
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600) f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600)
if err != nil { if err != nil {
@ -247,7 +248,7 @@ func (l *localClient) GetVersion() (*result.Version, error) {
}, nil }, 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 // not used by `morph init` command
panic("unexpected call") panic("unexpected call")
} }

View file

@ -1,9 +1,9 @@
package morph package morph
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "github.com/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"

View file

@ -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
}

View file

@ -9,12 +9,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -25,9 +23,16 @@ import (
const defaultNotaryDepositLifetime = 5760 const defaultNotaryDepositLifetime = 5760
func depositNotary(cmd *cobra.Command, _ []string) error { func depositNotary(cmd *cobra.Command, _ []string) error {
w, err := openWallet(cmd) p, err := cmd.Flags().GetString(storageWalletFlag)
if err != nil { if err != nil {
return err return err
} else if p == "" {
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
}
w, err := wallet.NewWalletFromFile(p)
if err != nil {
return fmt.Errorf("can't open wallet: %v", err)
} }
accHash := w.GetChangeAddress() accHash := w.GetChangeAddress()
@ -75,10 +80,6 @@ func depositNotary(cmd *cobra.Command, _ []string) error {
} }
} }
return transferGas(cmd, acc, accHash, gasAmount, till)
}
func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
c, err := getN3Client(viper.GetViper()) c, err := getN3Client(viper.GetViper())
if err != nil { if err != nil {
return err return err
@ -118,18 +119,3 @@ func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160,
return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}}) return awaitTx(cmd, c, []hashVUBPair{{hash: txHash, vub: vub}})
} }
func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
p, err := cmd.Flags().GetString(storageWalletFlag)
if err != nil {
return nil, err
} else if p == "" {
return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
}
w, err := wallet.NewWalletFromFile(p)
if err != nil {
return nil, fmt.Errorf("can't open wallet: %v", err)
}
return w, nil
}

View file

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
netmapcontract "git.frostfs.info/TrueCloudLab/frostfs-contract/netmap" netmapcontract "github.com/TrueCloudLab/frostfs-contract/netmap"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"

View file

@ -18,6 +18,10 @@ const (
maxObjectSizeCLIFlag = "max-object-size" maxObjectSizeCLIFlag = "max-object-size"
epochDurationInitFlag = "network.epoch_duration" epochDurationInitFlag = "network.epoch_duration"
epochDurationCLIFlag = "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" containerFeeInitFlag = "network.fee.container"
containerAliasFeeInitFlag = "network.fee.container_alias" containerAliasFeeInitFlag = "network.fee.container_alias"
containerFeeCLIFlag = "container-fee" containerFeeCLIFlag = "container-fee"
@ -65,12 +69,15 @@ var (
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag)) _ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag)) _ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
_ = viper.BindPFlag(incomeRateInitFlag, cmd.Flags().Lookup(incomeRateCLIFlag))
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag)) _ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
_ = viper.BindPFlag(auditFeeInitFlag, cmd.Flags().Lookup(auditFeeCLIFlag))
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag)) _ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag)) _ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag)) _ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag)) _ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath)) _ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
_ = viper.BindPFlag(localDumpFlag, cmd.Flags().Lookup(localDumpFlag))
}, },
RunE: initializeSideChainCmd, RunE: initializeSideChainCmd,
} }
@ -232,140 +239,14 @@ var (
) )
func init() { func init() {
initGenerateAlphabetCmd() RootCmd.AddCommand(generateAlphabetCmd)
initInitCmd() generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
initDeployCmd() generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
initGenerateStorageCmd()
initForceNewEpochCmd()
initRemoveNodesCmd()
initSetPolicyCmd()
initDumpContractHashesCmd()
initDumpNetworkConfigCmd()
initSetConfigCmd()
initDumpBalancesCmd()
initUpdateContractsCmd()
initDumpContainersCmd()
initRestoreContainersCmd()
initListContainersCmd()
initRefillGasCmd()
initDepositoryNotaryCmd()
initNetmapCandidatesCmd()
}
func initNetmapCandidatesCmd() {
RootCmd.AddCommand(netmapCandidatesCmd)
netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
}
func initDepositoryNotaryCmd() {
RootCmd.AddCommand(depositNotaryCmd)
depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
depositNotaryCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit")
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
}
func initRefillGasCmd() {
RootCmd.AddCommand(refillGasCmd)
refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
refillGasCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer")
refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag)
}
func initListContainersCmd() {
RootCmd.AddCommand(listContainersCmd)
listContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
}
func initRestoreContainersCmd() {
RootCmd.AddCommand(restoreContainersCmd)
restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
}
func initDumpContainersCmd() {
RootCmd.AddCommand(dumpContainersCmd)
dumpContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
dumpContainersCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers")
dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump")
}
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)
}
func initDumpBalancesCmd() {
RootCmd.AddCommand(dumpBalancesCmd)
dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap")
dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts")
dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract")
dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses")
}
func initSetConfigCmd() {
RootCmd.AddCommand(setConfig)
setConfig.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
setConfig.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
}
func initDumpNetworkConfigCmd() {
RootCmd.AddCommand(dumpNetworkConfigCmd)
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
}
func initDumpContractHashesCmd() {
RootCmd.AddCommand(dumpContractHashesCmd)
dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.")
}
func initSetPolicyCmd() {
RootCmd.AddCommand(setPolicy)
setPolicy.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
setPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
}
func initRemoveNodesCmd() {
RootCmd.AddCommand(removeNodes)
removeNodes.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
removeNodes.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
}
func initForceNewEpochCmd() {
RootCmd.AddCommand(forceNewEpoch)
forceNewEpoch.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
forceNewEpoch.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
}
func initGenerateStorageCmd() {
RootCmd.AddCommand(generateStorageCmd)
generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
generateStorageCmd.Flags().String(storageWalletFlag, "", "Path to new storage node wallet")
generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer")
generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label")
}
func initInitCmd() {
RootCmd.AddCommand(initCmd) RootCmd.AddCommand(initCmd)
initCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir") initCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
initCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") initCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts") initCmd.Flags().String(contractsInitFlag, "", "Path to archive with compiled FrostFS contracts (default fetched from latest github release)")
_ = initCmd.MarkFlagRequired(contractsInitFlag)
initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch") 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().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing") initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
@ -374,14 +255,85 @@ func initInitCmd() {
initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee") initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
initCmd.Flags().String(protoConfigPath, "", "Path to the consensus node configuration") initCmd.Flags().String(protoConfigPath, "", "Path to the consensus node configuration")
initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file") initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file")
}
func initGenerateAlphabetCmd() {
RootCmd.AddCommand(generateAlphabetCmd)
generateAlphabetCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
}
func initDeployCmd() {
RootCmd.AddCommand(deployCmd) RootCmd.AddCommand(deployCmd)
RootCmd.AddCommand(generateStorageCmd)
generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
generateStorageCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
generateStorageCmd.Flags().String(storageWalletFlag, "", "Path to new storage node wallet")
generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer")
generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label")
RootCmd.AddCommand(forceNewEpoch)
forceNewEpoch.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
forceNewEpoch.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
RootCmd.AddCommand(removeNodes)
removeNodes.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
removeNodes.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
RootCmd.AddCommand(setPolicy)
setPolicy.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
setPolicy.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
RootCmd.AddCommand(dumpContractHashesCmd)
dumpContractHashesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.")
RootCmd.AddCommand(dumpNetworkConfigCmd)
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
RootCmd.AddCommand(setConfig)
setConfig.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
setConfig.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key")
RootCmd.AddCommand(dumpBalancesCmd)
dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "Dump balances of storage nodes from the current netmap")
dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "Dump balances of alphabet contracts")
dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "Dump balances of the proxy contract")
dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "Use script-hash format for addresses")
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 (default fetched from latest github release)")
RootCmd.AddCommand(dumpContainersCmd)
dumpContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
dumpContainersCmd.Flags().String(containerDumpFlag, "", "File where to save dumped containers")
dumpContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
dumpContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to dump")
RootCmd.AddCommand(restoreContainersCmd)
restoreContainersCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
restoreContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
restoreContainersCmd.Flags().String(containerDumpFlag, "", "File to restore containers from")
restoreContainersCmd.Flags().StringSlice(containerIDsFlag, nil, "Containers to restore")
RootCmd.AddCommand(listContainersCmd)
listContainersCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
listContainersCmd.Flags().String(containerContractFlag, "", "Container contract hash (for networks without NNS)")
RootCmd.AddCommand(refillGasCmd)
refillGasCmd.Flags().String(alphabetWalletsFlag, "", "Path to alphabet wallets dir")
refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
refillGasCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
refillGasCmd.Flags().String(refillGasAmountFlag, "", "Additional amount of GAS to transfer")
refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, storageWalletFlag)
RootCmd.AddCommand(cmdSubnet)
RootCmd.AddCommand(depositNotaryCmd)
depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
depositNotaryCmd.Flags().String(storageWalletFlag, "", "Path to storage node wallet")
depositNotaryCmd.Flags().String(walletAccountFlag, "", "Wallet account address")
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "Amount of GAS to deposit")
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "Notary deposit duration in blocks")
RootCmd.AddCommand(netmapCandidatesCmd)
netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
} }

File diff suppressed because it is too large Load diff

View file

@ -3,14 +3,14 @@ package modules
import ( import (
"os" "os"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
"git.frostfs.info/TrueCloudLab/frostfs-node/misc" "github.com/TrueCloudLab/frostfs-node/misc"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete" "github.com/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config" utilConfig "github.com/TrueCloudLab/frostfs-node/pkg/util/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/gendoc" "github.com/TrueCloudLab/frostfs-node/pkg/util/gendoc"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -45,14 +45,14 @@ func init() {
rootCmd.AddCommand(storagecfg.RootCmd) rootCmd.AddCommand(storagecfg.RootCmd)
rootCmd.AddCommand(autocomplete.Command("frostfs-adm")) rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{})) rootCmd.AddCommand(gendoc.Command(rootCmd))
} }
func Execute() error { func Execute() error {
return rootCmd.Execute() return rootCmd.Execute()
} }
func entryPoint(cmd *cobra.Command, _ []string) error { func entryPoint(cmd *cobra.Command, args []string) error {
printVersion, _ := cmd.Flags().GetBool("version") printVersion, _ := cmd.Flags().GetBool("version")
if printVersion { if printVersion {
cmd.Print(misc.BuildInfo("FrostFS Adm")) cmd.Print(misc.BuildInfo("FrostFS Adm"))

View file

@ -12,6 +12,9 @@ node:
- {{ .AnnouncedAddress }} - {{ .AnnouncedAddress }}
attribute_0: UN-LOCODE:{{ .Attribute.Locode }} attribute_0: UN-LOCODE:{{ .Attribute.Locode }}
relay: {{ .Relay }} # start Storage node in relay mode without bootstrapping into the Network map 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: grpc:
num: 1 # total number of listener endpoints num: 1 # total number of listener endpoints

View file

@ -16,7 +16,7 @@ import (
"text/template" "text/template"
"time" "time"
netutil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network" netutil "github.com/TrueCloudLab/frostfs-node/pkg/network"
"github.com/chzyer/readline" "github.com/chzyer/readline"
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
@ -80,7 +80,15 @@ type config struct {
} }
func storageConfig(cmd *cobra.Command, args []string) { func storageConfig(cmd *cobra.Command, args []string) {
outPath := getOutputPath(args) var outPath string
if len(args) != 0 {
outPath = args[0]
} else {
outPath = getPath("File to write config at [./config.yml]: ")
if outPath == "" {
outPath = "./config.yml"
}
}
historyPath := filepath.Join(os.TempDir(), "frostfs-adm.history") historyPath := filepath.Join(os.TempDir(), "frostfs-adm.history")
readline.SetHistoryPath(historyPath) readline.SetHistoryPath(historyPath)
@ -95,7 +103,14 @@ func storageConfig(cmd *cobra.Command, args []string) {
w, err := wallet.NewWalletFromFile(c.Wallet.Path) w, err := wallet.NewWalletFromFile(c.Wallet.Path)
fatalOnErr(err) fatalOnErr(err)
fillWalletAccount(cmd, &c, w) c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag)
if c.Wallet.Account == "" {
addr := address.Uint160ToString(w.GetChangeAddress())
c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr))
if c.Wallet.Account == "" {
c.Wallet.Account = addr
}
}
accH, err := flags.ParseAddress(c.Wallet.Account) accH, err := flags.ParseAddress(c.Wallet.Account)
fatalOnErr(err) fatalOnErr(err)
@ -113,51 +128,32 @@ func storageConfig(cmd *cobra.Command, args []string) {
c.AuthorizedKeys = append(c.AuthorizedKeys, hex.EncodeToString(acc.PrivateKey().PublicKey().Bytes())) c.AuthorizedKeys = append(c.AuthorizedKeys, hex.EncodeToString(acc.PrivateKey().PublicKey().Bytes()))
network := readNetwork(cmd) var network string
for {
network = getString("Choose network [mainnet]/testnet: ")
switch network {
case "":
network = "mainnet"
case "testnet", "mainnet":
default:
cmd.Println(`Network must be either "mainnet" or "testnet"`)
continue
}
break
}
c.MorphRPC = n3config[network].MorphRPC c.MorphRPC = n3config[network].MorphRPC
depositGas(cmd, acc, network) depositGas(cmd, acc, network)
c.Attribute.Locode = getString("UN-LOCODE attribute in [XX YYY] format: ") c.Attribute.Locode = getString("UN-LOCODE attribute in [XX YYY] format: ")
endpoint := getDefaultEndpoint(cmd, &c)
c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", endpoint))
if c.Endpoint == "" {
c.Endpoint = endpoint
}
c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint))
if c.ControlEndpoint == "" {
c.ControlEndpoint = defaultControlEndpoint
}
c.TLSCert = getPath("TLS Certificate (optional): ")
if c.TLSCert != "" {
c.TLSKey = getPath("TLS Key: ")
}
c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ")
if !c.Relay {
p := getPath("Path to the storage directory (all available storage will be used): ")
c.BlobstorPath = filepath.Join(p, "blob")
c.MetabasePath = filepath.Join(p, "meta")
}
out := applyTemplate(c)
fatalOnErr(os.WriteFile(outPath, out, 0644))
cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`")
}
func getDefaultEndpoint(cmd *cobra.Command, c *config) string {
var addr, port string var addr, port string
for { for {
c.AnnouncedAddress = getString("Publicly announced address: ") c.AnnouncedAddress = getString("Publicly announced address: ")
validator := netutil.Address{} validator := netutil.Address{}
err := validator.FromString(c.AnnouncedAddress) err := validator.FromString(c.AnnouncedAddress)
if err != nil { if err != nil {
cmd.Println("Incorrect address format. See https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/pkg/network/address.go for details.") cmd.Println("Incorrect address format. See https://github.com/TrueCloudLab/frostfs-node/blob/master/pkg/network/address.go for details.")
continue continue
} }
uriAddr, err := url.Parse(validator.URIAddr()) uriAddr, err := url.Parse(validator.URIAddr())
@ -186,46 +182,34 @@ func getDefaultEndpoint(cmd *cobra.Command, c *config) string {
break break
} }
return net.JoinHostPort(defaultDataEndpoint, port)
}
func fillWalletAccount(cmd *cobra.Command, c *config, w *wallet.Wallet) { defaultAddr := net.JoinHostPort(defaultDataEndpoint, port)
c.Wallet.Account, _ = cmd.Flags().GetString(accountFlag) c.Endpoint = getString(fmt.Sprintf("Listening address [%s]: ", defaultAddr))
if c.Wallet.Account == "" { if c.Endpoint == "" {
addr := address.Uint160ToString(w.GetChangeAddress()) c.Endpoint = defaultAddr
c.Wallet.Account = getWalletAccount(w, fmt.Sprintf("Wallet account [%s]: ", addr))
if c.Wallet.Account == "" {
c.Wallet.Account = addr
}
} }
}
func readNetwork(cmd *cobra.Command) string { c.ControlEndpoint = getString(fmt.Sprintf("Listening address (control endpoint) [%s]: ", defaultControlEndpoint))
var network string if c.ControlEndpoint == "" {
for { c.ControlEndpoint = defaultControlEndpoint
network = getString("Choose network [mainnet]/testnet: ")
switch network {
case "":
network = "mainnet"
case "testnet", "mainnet":
default:
cmd.Println(`Network must be either "mainnet" or "testnet"`)
continue
}
break
} }
return network
}
func getOutputPath(args []string) string { c.TLSCert = getPath("TLS Certificate (optional): ")
if len(args) != 0 { if c.TLSCert != "" {
return args[0] c.TLSKey = getPath("TLS Key: ")
} }
outPath := getPath("File to write config at [./config.yml]: ")
if outPath == "" { c.Relay = getConfirmation(false, "Use node as a relay? yes/[no]: ")
outPath = "./config.yml" if !c.Relay {
p := getPath("Path to the storage directory (all available storage will be used): ")
c.BlobstorPath = filepath.Join(p, "blob")
c.MetabasePath = filepath.Join(p, "meta")
} }
return outPath
out := applyTemplate(c)
fatalOnErr(os.WriteFile(outPath, out, 0644))
cmd.Println("Node is ready for work! Run `frostfs-node -config " + outPath + "`")
} }
func getWalletAccount(w *wallet.Wallet, prompt string) string { func getWalletAccount(w *wallet.Wallet, prompt string) string {

View file

@ -3,7 +3,7 @@ package main
import ( import (
"os" "os"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules"
) )
func main() { func main() {

View file

@ -9,16 +9,16 @@ duplicated header names or headers with empty values are considered invalid.
## Existing headers ## Existing headers
There are some "well-known" headers starting with `__SYSTEM__` prefix that There are some "well-known" headers starting with `__FROSTFS__` prefix that
affect system behaviour. For backward compatibility, the same set of affect system behaviour. For backward compatibility, the same set of
"well-known" headers may also use `__NEOFS__` prefix: "well-known" headers may also use `__NEOFS__` prefix:
* `__SYSTEM__NETMAP_EPOCH` - netmap epoch to use for object placement calculation. The `value` is string * `__FROSTFS__NETMAP_EPOCH` - netmap epoch to use for object placement calculation. The `value` is string
encoded `uint64` in decimal presentation. If set to '0' or omitted, the encoded `uint64` in decimal presentation. If set to '0' or omitted, the
current epoch only will be used. current epoch only will be used.
* `__SYSTEM__NETMAP_LOOKUP_DEPTH` - if object can't be found using current epoch's netmap, this header limits * `__FROSTFS__NETMAP_LOOKUP_DEPTH` - if object can't be found using current epoch's netmap, this header limits
how many past epochs the node can look up through. Depth is applied to a current epoch or the value how many past epochs the node can look up through. Depth is applied to a current epoch or the value
of `__SYSTEM__NETMAP_EPOCH` attribute. The `value` is string encoded `uint64` in decimal presentation. of `__FROSTFS__NETMAP_EPOCH` attribute. The `value` is string encoded `uint64` in decimal presentation.
If set to '0' or not set, only the current epoch is used. If set to '0' or not set, only the current epoch is used.
## `frostfs-cli` commands with `--xhdr` ## `frostfs-cli` commands with `--xhdr`
@ -26,8 +26,9 @@ If set to '0' or not set, only the current epoch is used.
List of commands with support of extended headers: List of commands with support of extended headers:
* `container list-objects` * `container list-objects`
* `object delete/get/hash/head/lock/put/range/search` * `object delete/get/hash/head/lock/put/range/search`
* `storagegroup delete/get/list/put`
Example: Example:
```shell ```shell
$ frostfs-cli object put -r s01.frostfs.devenv:8080 -w wallet.json --cid CID --file FILE --xhdr "__SYSTEM__NETMAP_EPOCH=777" $ frostfs-cli object put -r s01.frostfs.devenv:8080 -w wallet.json --cid CID --file FILE --xhdr "__FROSTFS__NETMAP_EPOCH=777"
``` ```

View file

@ -7,15 +7,15 @@ import (
"fmt" "fmt"
"io" "io"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting" "github.com/TrueCloudLab/frostfs-sdk-go/accounting"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "github.com/TrueCloudLab/frostfs-sdk-go/client"
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" containerSDK "github.com/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "github.com/TrueCloudLab/frostfs-sdk-go/netmap"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "github.com/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" "github.com/TrueCloudLab/frostfs-sdk-go/version"
) )
// BalanceOfPrm groups parameters of BalanceOf operation. // BalanceOfPrm groups parameters of BalanceOf operation.
@ -37,8 +37,8 @@ func (x BalanceOfRes) Balance() accounting.Decimal {
// BalanceOf requests the current balance of a FrostFS user. // BalanceOf requests the current balance of a FrostFS user.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func BalanceOf(ctx context.Context, prm BalanceOfPrm) (res BalanceOfRes, err error) { func BalanceOf(prm BalanceOfPrm) (res BalanceOfRes, err error) {
res.cliRes, err = prm.cli.BalanceGet(ctx, prm.PrmBalanceGet) res.cliRes, err = prm.cli.BalanceGet(context.Background(), prm.PrmBalanceGet)
return return
} }
@ -62,16 +62,16 @@ func (x ListContainersRes) IDList() []cid.ID {
// ListContainers requests a list of FrostFS user's containers. // ListContainers requests a list of FrostFS user's containers.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContainersRes, err error) { func ListContainers(prm ListContainersPrm) (res ListContainersRes, err error) {
res.cliRes, err = prm.cli.ContainerList(ctx, prm.PrmContainerList) res.cliRes, err = prm.cli.ContainerList(context.Background(), prm.PrmContainerList)
return return
} }
// PutContainerPrm groups parameters of PutContainer operation. // PutContainerPrm groups parameters of PutContainer operation.
type PutContainerPrm struct { type PutContainerPrm struct {
Client *client.Client commonPrm
ClientParams client.PrmContainerPut client.PrmContainerPut
} }
// PutContainerRes groups the resulting values of PutContainer operation. // PutContainerRes groups the resulting values of PutContainer operation.
@ -92,8 +92,8 @@ func (x PutContainerRes) ID() cid.ID {
// Success can be verified by reading by identifier. // Success can be verified by reading by identifier.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes, err error) { func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) {
cliRes, err := prm.Client.ContainerPut(ctx, prm.ClientParams) cliRes, err := prm.cli.ContainerPut(context.Background(), prm.PrmContainerPut)
if err == nil { if err == nil {
res.cnr = cliRes.ID() res.cnr = cliRes.ID()
} }
@ -103,15 +103,13 @@ func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes
// GetContainerPrm groups parameters of GetContainer operation. // GetContainerPrm groups parameters of GetContainer operation.
type GetContainerPrm struct { type GetContainerPrm struct {
Client *client.Client commonPrm
ClientParams client.PrmContainerGet cliPrm client.PrmContainerGet
} }
// SetContainer sets identifier of the container to be read. // SetContainer sets identifier of the container to be read.
//
// Deprecated: Use GetContainerPrm.ClientParams.ContainerID instead.
func (x *GetContainerPrm) SetContainer(id cid.ID) { func (x *GetContainerPrm) SetContainer(id cid.ID) {
x.ClientParams.ContainerID = &id x.cliPrm.SetContainer(id)
} }
// GetContainerRes groups the resulting values of GetContainer operation. // GetContainerRes groups the resulting values of GetContainer operation.
@ -127,23 +125,20 @@ func (x GetContainerRes) Container() containerSDK.Container {
// GetContainer reads a container from FrostFS by ID. // GetContainer reads a container from FrostFS by ID.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func GetContainer(ctx context.Context, prm GetContainerPrm) (res GetContainerRes, err error) { func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) {
res.cliRes, err = prm.Client.ContainerGet(ctx, prm.ClientParams) res.cliRes, err = prm.cli.ContainerGet(context.Background(), prm.cliPrm)
return return
} }
// IsACLExtendable checks if ACL of the container referenced by the given identifier // IsACLExtendable checks if ACL of the container referenced by the given identifier
// can be extended. Client connection MUST BE correctly established in advance. // can be extended. Client connection MUST BE correctly established in advance.
func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, error) { func IsACLExtendable(c *client.Client, cnr cid.ID) (bool, error) {
prm := GetContainerPrm{ var prm GetContainerPrm
Client: c, prm.SetClient(c)
ClientParams: client.PrmContainerGet{ prm.SetContainer(cnr)
ContainerID: &cnr,
},
}
res, err := GetContainer(ctx, prm) res, err := GetContainer(prm)
if err != nil { if err != nil {
return false, fmt.Errorf("get container from the FrostFS: %w", err) return false, fmt.Errorf("get container from the FrostFS: %w", err)
} }
@ -153,8 +148,8 @@ func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, e
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation. // DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
type DeleteContainerPrm struct { type DeleteContainerPrm struct {
Client *client.Client commonPrm
ClientParams client.PrmContainerDelete client.PrmContainerDelete
} }
// DeleteContainerRes groups the resulting values of DeleteContainer operation. // DeleteContainerRes groups the resulting values of DeleteContainer operation.
@ -168,16 +163,16 @@ type DeleteContainerRes struct{}
// Success can be verified by reading by identifier. // Success can be verified by reading by identifier.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteContainerRes, err error) { func DeleteContainer(prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
_, err = prm.Client.ContainerDelete(ctx, prm.ClientParams) _, err = prm.cli.ContainerDelete(context.Background(), prm.PrmContainerDelete)
return return
} }
// EACLPrm groups parameters of EACL operation. // EACLPrm groups parameters of EACL operation.
type EACLPrm struct { type EACLPrm struct {
Client *client.Client commonPrm
ClientParams client.PrmContainerEACL client.PrmContainerEACL
} }
// EACLRes groups the resulting values of EACL operation. // EACLRes groups the resulting values of EACL operation.
@ -193,16 +188,16 @@ func (x EACLRes) EACL() eacl.Table {
// EACL reads eACL table from FrostFS by container ID. // EACL reads eACL table from FrostFS by container ID.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) { func EACL(prm EACLPrm) (res EACLRes, err error) {
res.cliRes, err = prm.Client.ContainerEACL(ctx, prm.ClientParams) res.cliRes, err = prm.cli.ContainerEACL(context.Background(), prm.PrmContainerEACL)
return return
} }
// SetEACLPrm groups parameters of SetEACL operation. // SetEACLPrm groups parameters of SetEACL operation.
type SetEACLPrm struct { type SetEACLPrm struct {
Client *client.Client commonPrm
ClientParams client.PrmContainerSetEACL client.PrmContainerSetEACL
} }
// SetEACLRes groups the resulting values of SetEACL operation. // SetEACLRes groups the resulting values of SetEACL operation.
@ -216,8 +211,8 @@ type SetEACLRes struct{}
// Success can be verified by reading by container identifier. // Success can be verified by reading by container identifier.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func SetEACL(ctx context.Context, prm SetEACLPrm) (res SetEACLRes, err error) { func SetEACL(prm SetEACLPrm) (res SetEACLRes, err error) {
_, err = prm.Client.ContainerSetEACL(ctx, prm.ClientParams) _, err = prm.cli.ContainerSetEACL(context.Background(), prm.PrmContainerSetEACL)
return return
} }
@ -241,8 +236,8 @@ func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
// NetworkInfo reads information about the FrostFS network. // NetworkInfo reads information about the FrostFS network.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func NetworkInfo(ctx context.Context, prm NetworkInfoPrm) (res NetworkInfoRes, err error) { func NetworkInfo(prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
res.cliRes, err = prm.cli.NetworkInfo(ctx, prm.PrmNetworkInfo) res.cliRes, err = prm.cli.NetworkInfo(context.Background(), prm.PrmNetworkInfo)
return return
} }
@ -271,8 +266,8 @@ func (x NodeInfoRes) LatestVersion() version.Version {
// NodeInfo requests information about the remote server from FrostFS netmap. // NodeInfo requests information about the remote server from FrostFS netmap.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func NodeInfo(ctx context.Context, prm NodeInfoPrm) (res NodeInfoRes, err error) { func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) {
res.cliRes, err = prm.cli.EndpointInfo(ctx, prm.PrmEndpointInfo) res.cliRes, err = prm.cli.EndpointInfo(context.Background(), prm.PrmEndpointInfo)
return return
} }
@ -295,8 +290,8 @@ func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
// NetMapSnapshot requests current network view of the remote server. // NetMapSnapshot requests current network view of the remote server.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func NetMapSnapshot(ctx context.Context, prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) { func NetMapSnapshot(prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
res.cliRes, err = prm.cli.NetMapSnapshot(ctx, client.PrmNetMapSnapshot{}) res.cliRes, err = prm.cli.NetMapSnapshot(context.Background(), client.PrmNetMapSnapshot{})
return return
} }
@ -324,8 +319,8 @@ func (x CreateSessionRes) SessionKey() []byte {
// CreateSession opens a new unlimited session with the remote node. // CreateSession opens a new unlimited session with the remote node.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func CreateSession(ctx context.Context, prm CreateSessionPrm) (res CreateSessionRes, err error) { func CreateSession(prm CreateSessionPrm) (res CreateSessionRes, err error) {
res.cliRes, err = prm.cli.SessionCreate(ctx, prm.PrmSessionCreate) res.cliRes, err = prm.cli.SessionCreate(context.Background(), prm.PrmSessionCreate)
return return
} }
@ -334,19 +329,15 @@ func CreateSession(ctx context.Context, prm CreateSessionPrm) (res CreateSession
type PutObjectPrm struct { type PutObjectPrm struct {
commonObjectPrm commonObjectPrm
copyNum []uint32 hdr *object.Object
hdr *objectSDK.Object
rdr io.Reader rdr io.Reader
headerCallback func(*objectSDK.Object) headerCallback func(*object.Object)
prepareLocally bool
} }
// SetHeader sets object header. // SetHeader sets object header.
func (x *PutObjectPrm) SetHeader(hdr *objectSDK.Object) { func (x *PutObjectPrm) SetHeader(hdr *object.Object) {
x.hdr = hdr x.hdr = hdr
} }
@ -357,51 +348,10 @@ func (x *PutObjectPrm) SetPayloadReader(rdr io.Reader) {
// SetHeaderCallback sets callback which is called on the object after the header is received // SetHeaderCallback sets callback which is called on the object after the header is received
// but before the payload is written. // 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 x.headerCallback = f
} }
// SetCopiesNumberByVectors sets ordered list of minimal required object copies numbers
// per placement vector.
func (x *PutObjectPrm) SetCopiesNumberByVectors(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. // PutObjectRes groups the resulting values of PutObject operation.
type PutObjectRes struct { type PutObjectRes struct {
id oid.ID id oid.ID
@ -412,26 +362,32 @@ func (x PutObjectRes) ID() oid.ID {
return x.id return x.id
} }
type epochSource uint64
func (s epochSource) CurrentEpoch() uint64 {
return uint64(s)
}
// PutObject saves the object in FrostFS network. // PutObject saves the object in FrostFS network.
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) { func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
sdkPrm, err := prm.convertToSDKPrm(ctx) var putPrm client.PrmObjectPutInit
if err != nil {
return nil, fmt.Errorf("unable to create parameters of object put operation: %w", err) 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...)
wrt, err := prm.cli.ObjectPutInit(context.Background(), putPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("init object writing: %w", err) return nil, fmt.Errorf("init object writing: %w", err)
} }
if wrt.WriteHeader(ctx, *prm.hdr) { if wrt.WriteHeader(*prm.hdr) {
if prm.headerCallback != nil { if prm.headerCallback != nil {
prm.headerCallback(prm.hdr) prm.headerCallback(prm.hdr)
} }
@ -461,7 +417,7 @@ func PutObject(ctx context.Context, prm PutObjectPrm) (*PutObjectRes, error) {
for { for {
n, err = prm.rdr.Read(buf) n, err = prm.rdr.Read(buf)
if n > 0 { if n > 0 {
if !wrt.WritePayloadChunk(ctx, buf[:n]) { if !wrt.WritePayloadChunk(buf[:n]) {
break break
} }
@ -477,7 +433,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 if err != nil { // here err already carries both status and client errors
return nil, fmt.Errorf("client failure: %w", err) return nil, fmt.Errorf("client failure: %w", err)
} }
@ -506,7 +462,7 @@ func (x DeleteObjectRes) Tombstone() oid.ID {
// DeleteObject marks an object to be removed from FrostFS through tombstone placement. // 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. // Returns any error which prevented the operation from completing correctly in error return.
func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, error) { func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
var delPrm client.PrmObjectDelete var delPrm client.PrmObjectDelete
delPrm.FromContainer(prm.objAddr.Container()) delPrm.FromContainer(prm.objAddr.Container())
delPrm.ByID(prm.objAddr.Object()) delPrm.ByID(prm.objAddr.Object())
@ -521,7 +477,7 @@ func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, e
delPrm.WithXHeaders(prm.xHeaders...) delPrm.WithXHeaders(prm.xHeaders...)
cliRes, err := prm.cli.ObjectDelete(ctx, delPrm) cliRes, err := prm.cli.ObjectDelete(context.Background(), delPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("remove object via client: %w", err) return nil, fmt.Errorf("remove object via client: %w", err)
} }
@ -537,22 +493,22 @@ type GetObjectPrm struct {
objectAddressPrm objectAddressPrm
rawPrm rawPrm
payloadWriterPrm payloadWriterPrm
headerCallback func(*objectSDK.Object) headerCallback func(*object.Object)
} }
// SetHeaderCallback sets callback which is called on the object after the header is received // SetHeaderCallback sets callback which is called on the object after the header is received
// but before the payload is written. // 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 p.headerCallback = f
} }
// GetObjectRes groups the resulting values of GetObject operation. // GetObjectRes groups the resulting values of GetObject operation.
type GetObjectRes struct { type GetObjectRes struct {
hdr *objectSDK.Object hdr *object.Object
} }
// Header returns the header of the request object. // Header returns the header of the request object.
func (x GetObjectRes) Header() *objectSDK.Object { func (x GetObjectRes) Header() *object.Object {
return x.hdr return x.hdr
} }
@ -562,7 +518,7 @@ func (x GetObjectRes) Header() *objectSDK.Object {
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
// For raw reading, returns *object.SplitInfoError error if object is virtual. // For raw reading, returns *object.SplitInfoError error if object is virtual.
func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) { func GetObject(prm GetObjectPrm) (*GetObjectRes, error) {
var getPrm client.PrmObjectGet var getPrm client.PrmObjectGet
getPrm.FromContainer(prm.objAddr.Container()) getPrm.FromContainer(prm.objAddr.Container())
getPrm.ByID(prm.objAddr.Object()) getPrm.ByID(prm.objAddr.Object())
@ -585,12 +541,12 @@ func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) {
getPrm.WithXHeaders(prm.xHeaders...) getPrm.WithXHeaders(prm.xHeaders...)
rdr, err := prm.cli.ObjectGetInit(ctx, getPrm) rdr, err := prm.cli.ObjectGetInit(context.Background(), getPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("init object reading on client: %w", err) return nil, fmt.Errorf("init object reading on client: %w", err)
} }
var hdr objectSDK.Object var hdr object.Object
if !rdr.ReadHeader(&hdr) { if !rdr.ReadHeader(&hdr) {
_, err = rdr.Close() _, err = rdr.Close()
@ -626,11 +582,11 @@ func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
// HeadObjectRes groups the resulting values of HeadObject operation. // HeadObjectRes groups the resulting values of HeadObject operation.
type HeadObjectRes struct { type HeadObjectRes struct {
hdr *objectSDK.Object hdr *object.Object
} }
// Header returns the requested object header. // Header returns the requested object header.
func (x HeadObjectRes) Header() *objectSDK.Object { func (x HeadObjectRes) Header() *object.Object {
return x.hdr return x.hdr
} }
@ -638,7 +594,7 @@ func (x HeadObjectRes) Header() *objectSDK.Object {
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
// For raw reading, returns *object.SplitInfoError error if object is virtual. // For raw reading, returns *object.SplitInfoError error if object is virtual.
func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error) { func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) {
var cliPrm client.PrmObjectHead var cliPrm client.PrmObjectHead
cliPrm.FromContainer(prm.objAddr.Container()) cliPrm.FromContainer(prm.objAddr.Container())
cliPrm.ByID(prm.objAddr.Object()) cliPrm.ByID(prm.objAddr.Object())
@ -661,12 +617,12 @@ func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error)
cliPrm.WithXHeaders(prm.xHeaders...) cliPrm.WithXHeaders(prm.xHeaders...)
res, err := prm.cli.ObjectHead(ctx, cliPrm) res, err := prm.cli.ObjectHead(context.Background(), cliPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("read object header via client: %w", err) return nil, fmt.Errorf("read object header via client: %w", err)
} }
var hdr objectSDK.Object var hdr object.Object
if !res.ReadHeader(&hdr) { if !res.ReadHeader(&hdr) {
return nil, fmt.Errorf("missing header in response") return nil, fmt.Errorf("missing header in response")
@ -682,11 +638,11 @@ type SearchObjectsPrm struct {
commonObjectPrm commonObjectPrm
containerIDPrm containerIDPrm
filters objectSDK.SearchFilters filters object.SearchFilters
} }
// SetFilters sets search filters. // SetFilters sets search filters.
func (x *SearchObjectsPrm) SetFilters(filters objectSDK.SearchFilters) { func (x *SearchObjectsPrm) SetFilters(filters object.SearchFilters) {
x.filters = filters x.filters = filters
} }
@ -703,7 +659,7 @@ func (x SearchObjectsRes) IDList() []oid.ID {
// SearchObjects selects objects from the container which match the filters. // SearchObjects selects objects from the container which match the filters.
// //
// Returns any error which prevented the operation from completing correctly in error return. // 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 var cliPrm client.PrmObjectSearch
cliPrm.InContainer(prm.cnrID) cliPrm.InContainer(prm.cnrID)
cliPrm.SetFilters(prm.filters) cliPrm.SetFilters(prm.filters)
@ -722,7 +678,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes
cliPrm.WithXHeaders(prm.xHeaders...) cliPrm.WithXHeaders(prm.xHeaders...)
rdr, err := prm.cli.ObjectSearchInit(ctx, cliPrm) rdr, err := prm.cli.ObjectSearchInit(context.Background(), cliPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("init object search: %w", err) return nil, fmt.Errorf("init object search: %w", err)
} }
@ -759,7 +715,7 @@ type HashPayloadRangesPrm struct {
tz bool tz bool
rngs []*objectSDK.Range rngs []*object.Range
salt []byte salt []byte
} }
@ -770,7 +726,7 @@ func (x *HashPayloadRangesPrm) TZ() {
} }
// SetRanges sets a list of payload ranges to hash. // 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 x.rngs = rngs
} }
@ -793,7 +749,7 @@ func (x HashPayloadRangesRes) HashList() [][]byte {
// //
// Returns any error which prevented the operation from completing correctly in error return. // 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. // Returns an error if number of received hashes differs with the number of requested ranges.
func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) { func HashPayloadRanges(prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
var cliPrm client.PrmObjectHash var cliPrm client.PrmObjectHash
cliPrm.FromContainer(prm.objAddr.Container()) cliPrm.FromContainer(prm.objAddr.Container())
cliPrm.ByID(prm.objAddr.Object()) cliPrm.ByID(prm.objAddr.Object())
@ -827,7 +783,7 @@ func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayl
cliPrm.WithXHeaders(prm.xHeaders...) cliPrm.WithXHeaders(prm.xHeaders...)
res, err := prm.cli.ObjectHash(ctx, cliPrm) res, err := prm.cli.ObjectHash(context.Background(), cliPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("read payload hashes via client: %w", err) return nil, fmt.Errorf("read payload hashes via client: %w", err)
} }
@ -844,11 +800,11 @@ type PayloadRangePrm struct {
rawPrm rawPrm
payloadWriterPrm payloadWriterPrm
rng *objectSDK.Range rng *object.Range
} }
// SetRange sets payload range to read. // SetRange sets payload range to read.
func (x *PayloadRangePrm) SetRange(rng *objectSDK.Range) { func (x *PayloadRangePrm) SetRange(rng *object.Range) {
x.rng = rng x.rng = rng
} }
@ -861,7 +817,7 @@ type PayloadRangeRes struct{}
// //
// Returns any error which prevented the operation from completing correctly in error return. // Returns any error which prevented the operation from completing correctly in error return.
// For raw reading, returns *object.SplitInfoError error if object is virtual. // For raw reading, returns *object.SplitInfoError error if object is virtual.
func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, error) { func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) {
var cliPrm client.PrmObjectRange var cliPrm client.PrmObjectRange
cliPrm.FromContainer(prm.objAddr.Container()) cliPrm.FromContainer(prm.objAddr.Container())
cliPrm.ByID(prm.objAddr.Object()) cliPrm.ByID(prm.objAddr.Object())
@ -887,7 +843,7 @@ func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, e
cliPrm.WithXHeaders(prm.xHeaders...) cliPrm.WithXHeaders(prm.xHeaders...)
rdr, err := prm.cli.ObjectRangeInit(ctx, cliPrm) rdr, err := prm.cli.ObjectRangeInit(context.Background(), cliPrm)
if err != nil { if err != nil {
return nil, fmt.Errorf("init payload reading: %w", err) return nil, fmt.Errorf("init payload reading: %w", err)
} }
@ -921,12 +877,12 @@ type SyncContainerRes struct{}
// Interrupts on any writer error. // Interrupts on any writer error.
// //
// Panics if a container passed as a parameter is nil. // 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 { if prm.c == nil {
panic("sync container settings with the network: nil container") 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -3,11 +3,11 @@ package internal
import ( import (
"io" "io"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "github.com/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "github.com/TrueCloudLab/frostfs-sdk-go/client"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "github.com/TrueCloudLab/frostfs-sdk-go/session"
) )
// here are small structures with public setters to share between parameter structures // here are small structures with public setters to share between parameter structures

View file

@ -8,15 +8,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network" "github.com/TrueCloudLab/frostfs-node/pkg/network"
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc" "github.com/TrueCloudLab/frostfs-sdk-go/client"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"google.golang.org/grpc"
) )
var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect") 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 { if err != nil {
return nil, fmt.Errorf("%v: %w", errInvalidEndpoint, err) 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. // 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 ( var (
c client.Client c client.Client
prmInit client.PrmInit prmInit client.PrmInit
@ -50,7 +48,7 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
) )
prmInit.SetDefaultPrivateKey(*key) prmInit.SetDefaultPrivateKey(*key)
prmInit.ResolveFrostFSFailures() prmInit.ResolveNeoFSFailures()
prmDial.SetServerURI(addr.URIAddr()) prmDial.SetServerURI(addr.URIAddr())
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 { if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
// In CLI we can only set a timeout for the whole operation. // In CLI we can only set a timeout for the whole operation.
@ -61,13 +59,10 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout) common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
} }
prmDial.SetGRPCDialOptions(
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()))
c.Init(prmInit) 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) 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) 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 { if err != nil {
return 0, err return 0, err
} }

View file

@ -4,10 +4,10 @@ import (
"errors" "errors"
"os" "os"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/version" "github.com/TrueCloudLab/frostfs-node/pkg/core/version"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
versionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" versionSDK "github.com/TrueCloudLab/frostfs-sdk-go/version"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"os" "os"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "github.com/TrueCloudLab/frostfs-sdk-go/bearer"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -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)
}

View file

@ -5,8 +5,8 @@ import (
"strconv" "strconv"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" "github.com/TrueCloudLab/frostfs-sdk-go/checksum"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )

View file

@ -47,9 +47,6 @@ const (
OIDFlag = "oid" OIDFlag = "oid"
OIDFlagUsage = "Object ID." OIDFlagUsage = "Object ID."
TracingFlag = "trace"
TracingFlagUsage = "Generate trace ID and print it."
) )
// Init adds common flags to the command: // Init adds common flags to the command:
@ -57,14 +54,12 @@ const (
// - WalletPath, // - WalletPath,
// - Account, // - Account,
// - RPC, // - RPC,
// - Tracing,
// - Timeout. // - Timeout.
func Init(cmd *cobra.Command) { func Init(cmd *cobra.Command) {
InitWithoutRPC(cmd) InitWithoutRPC(cmd)
ff := cmd.Flags() ff := cmd.Flags()
ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage) ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage)
ff.Bool(TracingFlag, false, TracingFlagUsage)
ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage) ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage)
} }

View file

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"

View file

@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"os" "os"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View file

@ -4,7 +4,7 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"errors" "errors"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"

View file

@ -1,6 +1,6 @@
package main package main
import cmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules" import cmd "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules"
func main() { func main() {
cmd.Execute() cmd.Execute()

View file

@ -3,13 +3,13 @@ package accounting
import ( import (
"math/big" "math/big"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/precision" "github.com/TrueCloudLab/frostfs-node/pkg/util/precision"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting" "github.com/TrueCloudLab/frostfs-sdk-go/accounting"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -41,7 +41,7 @@ var accountingBalanceCmd = &cobra.Command{
prm.SetClient(cli) prm.SetClient(cli)
prm.SetAccount(idUser) prm.SetAccount(idUser)
res, err := internalclient.BalanceOf(cmd.Context(), prm) res, err := internalclient.BalanceOf(prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
// print to stdout // print to stdout

View file

@ -1,8 +1,7 @@
package accounting package accounting
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -18,9 +17,7 @@ var Cmd = &cobra.Command{
_ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath)) _ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath))
_ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account)) _ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account))
_ = viper.BindPFlag(commonflags.RPC, flags.Lookup(commonflags.RPC)) _ = viper.BindPFlag(commonflags.RPC, flags.Lookup(commonflags.RPC))
common.StartClientCommandSpan(cmd)
}, },
PersistentPostRun: common.StopClientCommandSpan,
} }
func init() { func init() {

View file

@ -1,9 +1,9 @@
package basic package basic
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" "github.com/TrueCloudLab/frostfs-sdk-go/container/acl"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -6,11 +6,11 @@ import (
"os" "os"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -26,18 +26,18 @@ Action is 'allow' or 'deny'.
Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'. Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'.
Filter consists of <typ>:<key><match><value> Filter consists of <typ>:<key><match><value>
Typ is 'obj' for object applied filter or 'req' for request applied filter. Typ is 'obj' for object applied filter or 'req' for request applied filter.
Key is a valid unicode string corresponding to object or request header key. Key is a valid unicode string corresponding to object or request header key.
Well-known system object headers start with '$Object:' prefix. Well-known system object headers start with '$Object:' prefix.
User defined headers start without prefix. User defined headers start without prefix.
Read more about filter keys at git.frostfs.info.com/TrueCloudLab/frostfs-api/src/branch/master/proto-docs/acl.md#message-eaclrecordfilter Read more about filter keys at github.com/TrueCloudLab/frostfs-api/blob/master/proto-docs/acl.md#message-eaclrecordfilter
Match is '=' for matching and '!=' for non-matching filter. Match is '=' for matching and '!=' for non-matching filter.
Value is a valid unicode string corresponding to object or request header value. Value is a valid unicode string corresponding to object or request header value.
Target is Target is
'user' for container owner, 'user' for container owner,
'system' for Storage nodes in container and Inner Ring nodes, 'system' for Storage nodes in container and Inner Ring nodes,
'others' for all other request senders, 'others' for all other request senders,
'pubkey:<key1>,<key2>,...' for exact request sender, where <key> is a hex-encoded 33-byte public key. 'pubkey:<key1>,<key2>,...' for exact request sender, where <key> is a hex-encoded 33-byte public key.
When both '--rule' and '--file' arguments are used, '--rule' records will be placed higher in resulting extended ACL table. When both '--rule' and '--file' arguments are used, '--rule' records will be placed higher in resulting extended ACL table.

View file

@ -3,8 +3,8 @@ package extended
import ( import (
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View file

@ -4,9 +4,9 @@ import (
"os" "os"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -1,8 +1,8 @@
package acl package acl
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/basic" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/basic"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/extended" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/extended"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -7,13 +7,13 @@ import (
"os" "os"
"time" "time"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "github.com/TrueCloudLab/frostfs-sdk-go/bearer"
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" eaclSDK "github.com/TrueCloudLab/frostfs-sdk-go/eacl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/TrueCloudLab/frostfs-sdk-go/user"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -24,7 +24,6 @@ const (
ownerFlag = "owner" ownerFlag = "owner"
outFlag = "out" outFlag = "out"
jsonFlag = commonflags.JSON jsonFlag = commonflags.JSON
impersonateFlag = "impersonate"
) )
var createCmd = &cobra.Command{ var createCmd = &cobra.Command{
@ -40,20 +39,19 @@ is set to current epoch + n.
} }
func init() { func init() {
createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table (mutually exclusive with --impersonate flag)") createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table")
createCmd.Flags().StringP(issuedAtFlag, "i", "+0", "Epoch to issue token at") createCmd.Flags().StringP(issuedAtFlag, "i", "", "Epoch to issue token at")
createCmd.Flags().StringP(notValidBeforeFlag, "n", "+0", "Not valid before epoch") 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(commonflags.ExpireAt, "x", "", "The last active epoch for the token")
createCmd.Flags().StringP(ownerFlag, "o", "", "Token owner") createCmd.Flags().StringP(ownerFlag, "o", "", "Token owner")
createCmd.Flags().String(outFlag, "", "File to write token to") createCmd.Flags().String(outFlag, "", "File to write token to")
createCmd.Flags().Bool(jsonFlag, false, "Output token in JSON") 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.Flags().StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
createCmd.MarkFlagsMutuallyExclusive(eaclFlag, impersonateFlag)
_ = cobra.MarkFlagFilename(createCmd.Flags(), eaclFlag) _ = 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(), commonflags.ExpireAt)
_ = cobra.MarkFlagRequired(createCmd.Flags(), ownerFlag) _ = cobra.MarkFlagRequired(createCmd.Flags(), ownerFlag)
_ = cobra.MarkFlagRequired(createCmd.Flags(), outFlag) _ = 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) commonCmd.ExitOnErr(cmd, "can't parse --"+notValidBeforeFlag+" flag: %w", err)
if iatRelative || expRelative || nvbRelative { 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) ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel() defer cancel()
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint) currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint)
commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", err) commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", err)
@ -107,9 +101,6 @@ func createToken(cmd *cobra.Command, _ []string) {
b.SetIat(iat) b.SetIat(iat)
b.ForUser(ownerID) b.ForUser(ownerID)
impersonate, _ := cmd.Flags().GetBool(impersonateFlag)
b.SetImpersonate(impersonate)
eaclPath, _ := cmd.Flags().GetString(eaclFlag) eaclPath, _ := cmd.Flags().GetString(eaclFlag)
if eaclPath != "" { if eaclPath != "" {
table := eaclSDK.NewTable() table := eaclSDK.NewTable()

View file

@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete" "github.com/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
) )
func init() { func init() {

View file

@ -7,17 +7,17 @@ import (
"strings" "strings"
"time" "time"
containerApi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" containerApi "github.com/TrueCloudLab/frostfs-api-go/v2/container"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "github.com/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "github.com/TrueCloudLab/frostfs-sdk-go/container/acl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" "github.com/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" subnetid "github.com/TrueCloudLab/frostfs-sdk-go/subnet/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/TrueCloudLab/frostfs-sdk-go/user"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -30,13 +30,14 @@ var (
containerNnsName string containerNnsName string
containerNnsZone string containerNnsZone string
containerNoTimestamp bool containerNoTimestamp bool
containerSubnet string
force bool force bool
) )
var createContainerCmd = &cobra.Command{ var createContainerCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create new container", Short: "Create new container",
Long: `Create new container and register it in the FrostFS. Long: `Create new container and register it in the FrostFS.
It will be stored in sidechain when inner ring will accepts it.`, It will be stored in sidechain when inner ring will accepts it.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
placementPolicy, err := parseContainerPolicy(cmd, containerPolicy) placementPolicy, err := parseContainerPolicy(cmd, containerPolicy)
@ -49,7 +50,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
var prm internalclient.NetMapSnapshotPrm var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli) 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, "+ commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot to validate container placement, "+
"use --force option to skip this check: %w", err) "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 var cnr container.Container
cnr.Init() cnr.Init()
@ -97,18 +107,18 @@ It will be stored in sidechain when inner ring will accepts it.`,
syncContainerPrm.SetClient(cli) syncContainerPrm.SetClient(cli)
syncContainerPrm.SetContainer(&cnr) syncContainerPrm.SetContainer(&cnr)
_, err = internalclient.SyncContainerSettings(cmd.Context(), syncContainerPrm) _, err = internalclient.SyncContainerSettings(syncContainerPrm)
commonCmd.ExitOnErr(cmd, "syncing container's settings rpc error: %w", err) commonCmd.ExitOnErr(cmd, "syncing container's settings rpc error: %w", err)
putPrm := internalclient.PutContainerPrm{ var putPrm internalclient.PutContainerPrm
Client: cli, putPrm.SetClient(cli)
ClientParams: client.PrmContainerPut{ putPrm.SetContainer(cnr)
Container: &cnr,
Session: tok, 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) commonCmd.ExitOnErr(cmd, "put container rpc error: %w", err)
id := res.ID() id := res.ID()
@ -118,17 +128,14 @@ It will be stored in sidechain when inner ring will accepts it.`,
if containerAwait { if containerAwait {
cmd.Println("awaiting...") cmd.Println("awaiting...")
getPrm := internalclient.GetContainerPrm{ var getPrm internalclient.GetContainerPrm
Client: cli, getPrm.SetClient(cli)
ClientParams: client.PrmContainerGet{ getPrm.SetContainer(id)
ContainerID: &id,
},
}
for i := 0; i < awaitTimeout; i++ { for i := 0; i < awaitTimeout; i++ {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
_, err := internalclient.GetContainer(cmd.Context(), getPrm) _, err := internalclient.GetContainer(getPrm)
if err == nil { if err == nil {
cmd.Println("container has been persisted on sidechain") cmd.Println("container has been persisted on sidechain")
return return
@ -145,7 +152,6 @@ func initContainerCreateCmd() {
// Init common flags // Init common flags
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage) 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.DurationP(commonflags.Timeout, commonflags.TimeoutShorthand, commonflags.TimeoutDefault, commonflags.TimeoutUsage)
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage) flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage) 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(&containerNnsName, "nns-name", "", "Container nns name attribute")
flags.StringVar(&containerNnsZone, "nns-zone", "", "Container nns zone attribute") flags.StringVar(&containerNnsZone, "nns-zone", "", "Container nns zone attribute")
flags.BoolVar(&containerNoTimestamp, "disable-timestamp", false, "Disable timestamp container 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, flags.BoolVarP(&force, commonflags.ForceFlag, commonflags.ForceFlagShorthand, false,
"Skip placement validity check") "Skip placement validity check")
} }

View file

@ -4,21 +4,20 @@ import (
"fmt" "fmt"
"time" "time"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" objectSDK "github.com/TrueCloudLab/frostfs-sdk-go/object"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "github.com/TrueCloudLab/frostfs-sdk-go/user"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var deleteContainerCmd = &cobra.Command{ var deleteContainerCmd = &cobra.Command{
Use: "delete", Use: "delete",
Short: "Delete existing container", Short: "Delete existing container",
Long: `Delete existing container. Long: `Delete existing container.
Only owner of the container has a permission to remove container.`, Only owner of the container has a permission to remove container.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
id := parseContainerID(cmd) id := parseContainerID(cmd)
@ -31,14 +30,11 @@ Only owner of the container has a permission to remove container.`,
if force, _ := cmd.Flags().GetBool(commonflags.ForceFlag); !force { if force, _ := cmd.Flags().GetBool(commonflags.ForceFlag); !force {
common.PrintVerbose(cmd, "Reading the container to check ownership...") common.PrintVerbose(cmd, "Reading the container to check ownership...")
getPrm := internalclient.GetContainerPrm{ var getPrm internalclient.GetContainerPrm
Client: cli, getPrm.SetClient(cli)
ClientParams: client.PrmContainerGet{ getPrm.SetContainer(id)
ContainerID: &id,
},
}
resGet, err := internalclient.GetContainer(cmd.Context(), getPrm) resGet, err := internalclient.GetContainer(getPrm)
commonCmd.ExitOnErr(cmd, "can't get the container: %w", err) commonCmd.ExitOnErr(cmd, "can't get the container: %w", err)
owner := resGet.Container().Owner() owner := resGet.Container().Owner()
@ -76,26 +72,26 @@ Only owner of the container has a permission to remove container.`,
common.PrintVerbose(cmd, "Searching for LOCK objects...") 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) commonCmd.ExitOnErr(cmd, "can't search for LOCK objects: %w", err)
if len(res.IDList()) != 0 { if len(res.IDList()) != 0 {
commonCmd.ExitOnErr(cmd, "", commonCmd.ExitOnErr(cmd, "",
fmt.Errorf("container wasn't removed because LOCK objects were found, "+ fmt.Errorf("Container wasn't removed because LOCK objects were found.\n"+
"use --%s flag to remove anyway", commonflags.ForceFlag)) "Use --%s flag to remove anyway.", commonflags.ForceFlag))
} }
} }
} }
delPrm := internalclient.DeleteContainerPrm{ var delPrm internalclient.DeleteContainerPrm
Client: cli, delPrm.SetClient(cli)
ClientParams: client.PrmContainerDelete{ delPrm.SetContainer(id)
ContainerID: &id,
Session: tok, if tok != nil {
}, delPrm.WithinSession(*tok)
} }
_, err := internalclient.DeleteContainer(cmd.Context(), delPrm) _, err := internalclient.DeleteContainer(delPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
cmd.Println("container delete method invoked") cmd.Println("container delete method invoked")
@ -103,17 +99,14 @@ Only owner of the container has a permission to remove container.`,
if containerAwait { if containerAwait {
cmd.Println("awaiting...") cmd.Println("awaiting...")
getPrm := internalclient.GetContainerPrm{ var getPrm internalclient.GetContainerPrm
Client: cli, getPrm.SetClient(cli)
ClientParams: client.PrmContainerGet{ getPrm.SetContainer(id)
ContainerID: &id,
},
}
for i := 0; i < awaitTimeout; i++ { for i := 0; i < awaitTimeout; i++ {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
_, err := internalclient.GetContainer(cmd.Context(), getPrm) _, err := internalclient.GetContainer(getPrm)
if err != nil { if err != nil {
cmd.Println("container has been removed:", containerID) cmd.Println("container has been removed:", containerID)
return return
@ -131,7 +124,6 @@ func initContainerDeleteCmd() {
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage) flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage) flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage) 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.StringVar(&containerID, commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
flags.BoolVar(&containerAwait, "await", false, "Block execution until container is removed") flags.BoolVar(&containerAwait, "await", false, "Block execution until container is removed")

View file

@ -4,16 +4,15 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"os" "os"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "github.com/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "github.com/TrueCloudLab/frostfs-sdk-go/container/acl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -148,14 +147,11 @@ func getContainer(cmd *cobra.Command) (container.Container, *ecdsa.PrivateKey) {
pk = key.GetOrGenerate(cmd) pk = key.GetOrGenerate(cmd)
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC) cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
prm := internalclient.GetContainerPrm{ var prm internalclient.GetContainerPrm
Client: cli, prm.SetClient(cli)
ClientParams: client.PrmContainerGet{ prm.SetContainer(id)
ContainerID: &id,
},
}
res, err := internalclient.GetContainer(cmd.Context(), prm) res, err := internalclient.GetContainer(prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
cnr = res.Container() cnr = res.Container()

View file

@ -3,12 +3,11 @@ package container
import ( import (
"os" "os"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -21,14 +20,11 @@ var getExtendedACLCmd = &cobra.Command{
pk := key.GetOrGenerate(cmd) pk := key.GetOrGenerate(cmd)
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC) cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
eaclPrm := internalclient.EACLPrm{ var eaclPrm internalclient.EACLPrm
Client: cli, eaclPrm.SetClient(cli)
ClientParams: client.PrmContainerEACL{ eaclPrm.SetContainer(id)
ContainerID: &id,
},
}
res, err := internalclient.EACL(cmd.Context(), eaclPrm) res, err := internalclient.EACL(eaclPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
eaclTable := res.EACL() eaclTable := res.EACL()

View file

@ -3,13 +3,12 @@ package container
import ( import (
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" "github.com/TrueCloudLab/frostfs-api-go/v2/container"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "github.com/TrueCloudLab/frostfs-sdk-go/user"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -17,14 +16,12 @@ import (
const ( const (
flagListPrintAttr = "with-attr" flagListPrintAttr = "with-attr"
flagListContainerOwner = "owner" flagListContainerOwner = "owner"
flagListName = "name"
) )
// flag vars of list command. // flag vars of list command.
var ( var (
flagVarListPrintAttr bool flagVarListPrintAttr bool
flagVarListContainerOwner string flagVarListContainerOwner string
flagVarListName string
) )
var listContainersCmd = &cobra.Command{ var listContainersCmd = &cobra.Command{
@ -49,42 +46,30 @@ var listContainersCmd = &cobra.Command{
prm.SetClient(cli) prm.SetClient(cli)
prm.SetAccount(idUser) prm.SetAccount(idUser)
res, err := internalclient.ListContainers(cmd.Context(), prm) res, err := internalclient.ListContainers(prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
prmGet := internalclient.GetContainerPrm{ var prmGet internalclient.GetContainerPrm
Client: cli, prmGet.SetClient(cli)
}
containerIDs := res.IDList() list := res.IDList()
for _, cnrID := range containerIDs { for i := range list {
if flagVarListName == "" && !flagVarListPrintAttr { cmd.Println(list[i].String())
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())
if flagVarListPrintAttr { if flagVarListPrintAttr {
cnr.IterateAttributes(func(key, val string) { prmGet.SetContainer(list[i])
if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97 res, err := internalclient.GetContainer(prmGet)
// Use dedicated method to skip system attributes. if err == nil {
cmd.Printf(" %s: %s\n", key, val) res.Container().IterateAttributes(func(key, val string) {
} if !strings.HasPrefix(key, container.SysAttributePrefix) {
}) // 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 := listContainersCmd.Flags()
flags.StringVar(&flagVarListName, flagListName, "",
"List containers by the attribute name",
)
flags.StringVar(&flagVarListContainerOwner, flagListContainerOwner, "", flags.StringVar(&flagVarListContainerOwner, flagListContainerOwner, "",
"Owner of containers (omit to use owner from private key)", "Owner of containers (omit to use owner from private key)",
) )

View file

@ -3,14 +3,14 @@ package container
import ( import (
"strings" "strings"
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" v2object "github.com/TrueCloudLab/frostfs-api-go/v2/object"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object" objectCli "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "github.com/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -31,7 +31,7 @@ var listContainerObjectsCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
id := parseContainerID(cmd) id := parseContainerID(cmd)
filters := new(objectSDK.SearchFilters) filters := new(object.SearchFilters)
filters.AddRootFilter() // search only user created objects filters.AddRootFilter() // search only user created objects
cli := internalclient.GetSDKClientByFlag(cmd, key.GetOrGenerate(cmd), commonflags.RPC) cli := internalclient.GetSDKClientByFlag(cmd, key.GetOrGenerate(cmd), commonflags.RPC)
@ -51,7 +51,7 @@ var listContainerObjectsCmd = &cobra.Command{
prmSearch.SetContainerID(id) prmSearch.SetContainerID(id)
prmSearch.SetFilters(*filters) prmSearch.SetFilters(*filters)
res, err := internalclient.SearchObjects(cmd.Context(), prmSearch) res, err := internalclient.SearchObjects(prmSearch)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
objectIDs := res.IDList() objectIDs := res.IDList()
@ -65,14 +65,13 @@ var listContainerObjectsCmd = &cobra.Command{
addr.SetObject(objectIDs[i]) addr.SetObject(objectIDs[i])
prmHead.SetAddress(addr) prmHead.SetAddress(addr)
resHead, err := internalclient.HeadObject(cmd.Context(), prmHead) resHead, err := internalclient.HeadObject(prmHead)
if err == nil { if err == nil {
attrs := resHead.Header().Attributes() attrs := resHead.Header().Attributes()
for i := range attrs { for i := range attrs {
attrKey := attrs[i].Key() attrKey := attrs[i].Key()
if !strings.HasPrefix(attrKey, v2object.SysAttributePrefix) && !strings.HasPrefix(attrKey, v2object.SysAttributePrefixNeoFS) { if !strings.HasPrefix(attrKey, v2object.SysAttributePrefix) {
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97 // FIXME(@cthulhu-rider): neofs-sdk-go#226 use dedicated method to skip system attributes
// Use dedicated method to skip system attributes.
cmd.Printf(" %s: %s\n", attrKey, attrs[i].Value()) cmd.Printf(" %s: %s\n", attrKey, attrs[i].Value())
} }
} }

View file

@ -3,13 +3,13 @@ package container
import ( import (
"crypto/sha256" "crypto/sha256"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
containerAPI "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" containerAPI "github.com/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "github.com/TrueCloudLab/frostfs-sdk-go/netmap"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -31,7 +31,7 @@ var containerNodesCmd = &cobra.Command{
var prm internalclient.NetMapSnapshotPrm var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli) prm.SetClient(cli)
resmap, err := internalclient.NetMapSnapshot(cmd.Context(), prm) resmap, err := internalclient.NetMapSnapshot(prm)
commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot", err) commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot", err)
var id cid.ID var id cid.ID

View file

@ -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)
}

View file

@ -1,8 +1,7 @@
package container package container
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,9 +15,7 @@ var Cmd = &cobra.Command{
// the viper before execution // the viper before execution
commonflags.Bind(cmd) commonflags.Bind(cmd)
commonflags.BindAPI(cmd) commonflags.BindAPI(cmd)
common.StartClientCommandSpan(cmd)
}, },
PersistentPostRun: common.StopClientCommandSpan,
} }
func init() { func init() {
@ -31,7 +28,6 @@ func init() {
getExtendedACLCmd, getExtendedACLCmd,
setExtendedACLCmd, setExtendedACLCmd,
containerNodesCmd, containerNodesCmd,
policyPlaygroundCmd,
} }
Cmd.AddCommand(containerChildCommand...) Cmd.AddCommand(containerChildCommand...)
@ -44,7 +40,6 @@ func init() {
initContainerGetEACLCmd() initContainerGetEACLCmd()
initContainerSetEACLCmd() initContainerSetEACLCmd()
initContainerNodesCmd() initContainerNodesCmd()
initContainerPolicyPlaygroundCmd()
for _, containerCommand := range containerChildCommand { for _, containerCommand := range containerChildCommand {
commonflags.InitAPI(containerCommand) commonflags.InitAPI(containerCommand)

View file

@ -5,12 +5,11 @@ import (
"errors" "errors"
"time" "time"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -39,7 +38,7 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
if !flagVarsSetEACL.noPreCheck { if !flagVarsSetEACL.noPreCheck {
cmd.Println("Checking the ability to modify access rights in the container...") 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) commonCmd.ExitOnErr(cmd, "Extensibility check failure: %w", err)
if !extendable { 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.") cmd.Println("ACL extension is enabled in the container, continue processing.")
} }
setEACLPrm := internalclient.SetEACLPrm{ var setEACLPrm internalclient.SetEACLPrm
Client: cli, setEACLPrm.SetClient(cli)
ClientParams: client.PrmContainerSetEACL{ setEACLPrm.SetTable(*eaclTable)
Table: eaclTable,
Session: tok, if tok != nil {
}, setEACLPrm.WithinSession(*tok)
} }
_, err := internalclient.SetEACL(cmd.Context(), setEACLPrm) _, err := internalclient.SetEACL(setEACLPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
if containerAwait { if containerAwait {
@ -66,17 +65,14 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
cmd.Println("awaiting...") cmd.Println("awaiting...")
getEACLPrm := internalclient.EACLPrm{ var getEACLPrm internalclient.EACLPrm
Client: cli, getEACLPrm.SetClient(cli)
ClientParams: client.PrmContainerEACL{ getEACLPrm.SetContainer(id)
ContainerID: &id,
},
}
for i := 0; i < awaitTimeout; i++ { for i := 0; i < awaitTimeout; i++ {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
res, err := internalclient.EACL(cmd.Context(), getEACLPrm) res, err := internalclient.EACL(getEACLPrm)
if err == nil { if err == nil {
// compare binary values because EACL could have been set already // compare binary values because EACL could have been set already
table := res.EACL() table := res.EACL()

View file

@ -3,11 +3,11 @@ package container
import ( import (
"errors" "errors"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "github.com/TrueCloudLab/frostfs-sdk-go/session"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -1,53 +0,0 @@
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 (
concurrencyFlag = "concurrency"
removeDuplicatesFlag = "remove-duplicates"
)
var doctorCmd = &cobra.Command{
Use: "doctor",
Short: "Restructure node's storage",
Long: "Restructure node's storage",
Run: doctor,
}
func doctor(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
req := &control.DoctorRequest{Body: new(control.DoctorRequest_Body)}
req.Body.Concurrency, _ = cmd.Flags().GetUint32(concurrencyFlag)
req.Body.RemoveDuplicates, _ = cmd.Flags().GetBool(removeDuplicatesFlag)
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.DoctorResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.Doctor(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
cmd.Println("Operation has finished.")
}
func initControlDoctorCmd() {
initControlFlags(doctorCmd)
ff := doctorCmd.Flags()
ff.Uint32(concurrencyFlag, 0, "Number of parallel threads to use")
ff.Bool(removeDuplicatesFlag, false, "Remove duplicate objects")
}

View file

@ -1,10 +1,10 @@
package control package control
import ( import (
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" rawclient "github.com/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control" "github.com/TrueCloudLab/frostfs-node/pkg/services/control"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -1,21 +1,18 @@
package control package control
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" "github.com/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "github.com/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control" "github.com/TrueCloudLab/frostfs-node/pkg/services/control"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const ignoreErrorsFlag = "no-errors"
var evacuateShardCmd = &cobra.Command{ var evacuateShardCmd = &cobra.Command{
Use: "evacuate", Use: "evacuate",
Short: "Evacuate objects from shard", Short: "Evacuate objects from shard",
Long: "Evacuate objects from shard to other shards", Long: "Evacuate objects from shard to other shards",
Run: evacuateShard, Run: evacuateShard,
Deprecated: "use frostfs-cli control shards evacuation start",
} }
func evacuateShard(cmd *cobra.Command, _ []string) { 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 := &control.EvacuateShardRequest{Body: new(control.EvacuateShardRequest_Body)}
req.Body.Shard_ID = getShardIDList(cmd) req.Body.Shard_ID = getShardIDList(cmd)
req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(ignoreErrorsFlag) req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(dumpIgnoreErrorsFlag)
signRequest(cmd, pk, req) signRequest(cmd, pk, req)
@ -50,7 +47,7 @@ func initControlEvacuateShardCmd() {
flags := evacuateShardCmd.Flags() flags := evacuateShardCmd.Flags()
flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding") flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding")
flags.Bool(shardAllFlag, false, "Process all shards") 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) evacuateShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
} }

View file

@ -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)
}

Some files were not shown because too many files have changed in this diff Show more