forked from TrueCloudLab/frostfs-node
Compare commits
218 commits
fix/ir-rel
...
master
Author | SHA1 | Date | |
---|---|---|---|
6c45a17af6 | |||
d19ab43500 | |||
5bcf81d1cc | |||
c2effcc61c | |||
2285cfc36f | |||
e74d05c03f | |||
48862e0e63 | |||
89892d9754 | |||
7ac0852364 | |||
d28a5d2d7a | |||
87ac3c5279 | |||
d5ee6d3039 | |||
433aab12bb | |||
81f4cdbb91 | |||
3cd7d23f10 | |||
012af5cc38 | |||
eb5336d5ff | |||
bc8d79ddf9 | |||
29708b78d7 | |||
b9284604d9 | |||
65a4320c75 | |||
9a260c2e64 | |||
6f798b9c4b | |||
e515dd4582 | |||
8b6ec57c61 | |||
ed13387c0e | |||
5afea62ec0 | |||
c0a2f20eee | |||
2d064d0bd8 | |||
ef38420623 | |||
f7caef355b | |||
fbdfd503e4 | |||
67798bb50e | |||
5b653aa65f | |||
e314f328c4 | |||
6c96cc2af6 | |||
74db735265 | |||
3304afa9d1 | |||
b42bcdc6fa | |||
b0c5def2d9 | |||
90f3669399 | |||
07ce40e119 | |||
41038b2ec0 | |||
d83879d4b8 | |||
f6582081a4 | |||
00b1cecfb7 | |||
63466d71b2 | |||
d53732f663 | |||
3012286452 | |||
714ff784fa | |||
d2a59b2de8 | |||
acd6eb1815 | |||
42bf03e5cc | |||
5992ee901a | |||
dfb00083d0 | |||
1134760271 | |||
02bb7159a5 | |||
94302235d0 | |||
cc5360a578 | |||
4190fba86d | |||
936ebbb8e5 | |||
c065d55ca3 | |||
fe9f664b57 | |||
87f4b934d1 | |||
8093e145b3 | |||
3da168f8cf | |||
4572fa4874 | |||
1efa64ee72 | |||
be744ae3e6 | |||
1b520f7973 | |||
899cd55c27 | |||
0c49bca19c | |||
5fbb2657ca | |||
a5de74a249 | |||
fc032838c0 | |||
2f710d8f94 | |||
4dc9a1b300 | |||
963faa615a | |||
9a87acb87a | |||
9206ce5cd2 | |||
6c46044c9c | |||
01e3944b31 | |||
434048e8d9 | |||
f83f7feb8c | |||
62028cd7ee | |||
f45e75e3eb | |||
57c31e9802 | |||
9c5ddc4dfe | |||
54eb005822 | |||
a13219808a | |||
7f8a1dcf8e | |||
d0ed29b3c7 | |||
5f22ba6f38 | |||
a5e1aa22c9 | |||
772b471aab | |||
4fbfffd44c | |||
29e4cf7ba1 | |||
a2ab6d4942 | |||
bdd57c8b6b | |||
d1d6e3471c | |||
401c398704 | |||
004ff9e9bf | |||
63a567a1de | |||
580cd55180 | |||
e319bf403e | |||
aedb55f913 | |||
b69e07da7a | |||
2bd560e528 | |||
95597d3437 | |||
fd18aa363b | |||
76268e3ea2 | |||
8434f3dbfc | |||
34e6a309c6 | |||
bdf386366c | |||
839dead226 | |||
3bb65ba820 | |||
d4493a6d08 | |||
0b87be804a | |||
f71418b73c | |||
c34b8acedd | |||
c290d079fd | |||
53a90634fc | |||
5a53f9c4fd | |||
1361db91ee | |||
945b7c740b | |||
61d5e140e0 | |||
3441fff05d | |||
ac1eee091d | |||
a603d14d08 | |||
d4be2f20d4 | |||
e5c8f7ff9f | |||
1e7f9909da | |||
b807d8c400 | |||
d4bec24c9f | |||
ea48e928c8 | |||
96308a26c6 | |||
74a6a1da7f | |||
2be1aa781d | |||
89d0435b1d | |||
54fe8383a4 | |||
944160427b | |||
bb44867491 | |||
546d09660f | |||
e3764c51df | |||
b33559754d | |||
f345fe9a58 | |||
3b236160a6 | |||
25d2ae8aaf | |||
e39378b1c3 | |||
8a6e3025a0 | |||
2dd3a6f7a8 | |||
b142b6f48e | |||
5f6c7cbdb1 | |||
66e17f4b8e | |||
99be4c83a7 | |||
ec8da40567 | |||
dea6f031f9 | |||
5fac4058e8 | |||
2220f6a809 | |||
a812932984 | |||
92fe5d90f5 | |||
4668efc0bf | |||
654d970fad | |||
d3b209c8e1 | |||
edb1747af7 | |||
a61201a987 | |||
6b6eabe41c | |||
d508da8397 | |||
007827255e | |||
f652518c24 | |||
273980cfb9 | |||
108e4e07be | |||
b3deb893ba | |||
7768a482b5 | |||
371d97f61a | |||
e655336390 | |||
ed396448ac | |||
9cbd32bce8 | |||
1ae86f35a8 | |||
b9043433a0 | |||
a4fb7f085b | |||
a685fcdc96 | |||
2b3fc50681 | |||
98fe24cdb7 | |||
882c068410 | |||
6c2146bbc1 | |||
03976c6ed5 | |||
7e97df4878 | |||
01b6f1733c | |||
7abbdca064 | |||
6488ddee88 | |||
d6b42972a8 | |||
5e9a97fd3e | |||
fa7f9fbce2 | |||
806ea37101 | |||
80099d9a2f | |||
a059a7dcf0 | |||
bd24beecf8 | |||
dfe825b81b | |||
76f67ea34e | |||
7d0d781db1 | |||
0f08a2efba | |||
7bf20c9f1f | |||
2542d4f5df | |||
15dae8685e | |||
7bca428db0 | |||
a345c972bf | |||
8c1082b31a | |||
cfda9003a7 | |||
6ff0b0996b | |||
8319b59238 | |||
b7acb34fa4 | |||
41104f2383 | |||
eeab417dcf | |||
5ed317e24c | |||
e890f1b4b1 | |||
a4a1c3f18b | |||
ec1509de4e |
514 changed files with 14251 additions and 5799 deletions
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22 AS builder
|
FROM golang:1.23 AS builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22
|
FROM golang:1.23
|
||||||
|
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22 AS builder
|
FROM golang:1.23 AS builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22 AS builder
|
FROM golang:1.23 AS builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22 AS builder
|
FROM golang:1.23 AS builder
|
||||||
ARG BUILD=now
|
ARG BUILD=now
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
ARG REPO=repository
|
ARG REPO=repository
|
||||||
|
|
|
@ -8,7 +8,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go_versions: [ '1.21', '1.22' ]
|
go_versions: [ '1.22', '1.23' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.22
|
go-version: 1.23
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
run: |
|
run: |
|
||||||
apt update
|
apt update
|
||||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install linters
|
- name: Install linters
|
||||||
|
@ -25,7 +25,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go_versions: [ '1.21', '1.22' ]
|
go_versions: [ '1.22', '1.23' ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@ -48,7 +48,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.22'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
@ -63,7 +63,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install staticcheck
|
- name: Install staticcheck
|
||||||
|
@ -81,7 +81,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.22'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install gopls
|
- name: Install gopls
|
||||||
|
@ -99,11 +99,13 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Install gofumpt
|
- name: Install gofumpt
|
||||||
run: make fumpt-install
|
run: make fumpt-install
|
||||||
|
|
||||||
- name: Run gofumpt
|
- name: Run gofumpt
|
||||||
run: make fumpt
|
run: |
|
||||||
|
make fumpt
|
||||||
|
git diff --exit-code --quiet
|
||||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
|
|
||||||
- name: Install govulncheck
|
- name: Install govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|
|
@ -12,7 +12,8 @@ run:
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||||
format: tab
|
formats:
|
||||||
|
- format: tab
|
||||||
|
|
||||||
# all available settings of specific linters
|
# all available settings of specific linters
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
@ -37,6 +38,10 @@ linters-settings:
|
||||||
alias:
|
alias:
|
||||||
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||||
alias: objectSDK
|
alias: objectSDK
|
||||||
|
unused:
|
||||||
|
field-writes-are-uses: false
|
||||||
|
exported-fields-are-used: false
|
||||||
|
local-variables-are-used: false
|
||||||
custom:
|
custom:
|
||||||
truecloudlab-linters:
|
truecloudlab-linters:
|
||||||
path: bin/linters/external_linters.so
|
path: bin/linters/external_linters.so
|
||||||
|
@ -66,7 +71,7 @@ linters:
|
||||||
- bidichk
|
- bidichk
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- copyloopvar
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
- misspell
|
- misspell
|
||||||
|
@ -82,5 +87,7 @@ linters:
|
||||||
- perfsprint
|
- perfsprint
|
||||||
- testifylint
|
- testifylint
|
||||||
- protogetter
|
- protogetter
|
||||||
|
- intrange
|
||||||
|
- tenv
|
||||||
disable-all: true
|
disable-all: true
|
||||||
fast: false
|
fast: false
|
||||||
|
|
|
@ -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
|
|
41
Makefile
41
Makefile
|
@ -4,20 +4,19 @@ SHELL = bash
|
||||||
REPO ?= $(shell go list -m)
|
REPO ?= $(shell go list -m)
|
||||||
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||||
|
|
||||||
HUB_IMAGE ?= truecloudlab/frostfs
|
HUB_IMAGE ?= git.frostfs.info/truecloudlab/frostfs
|
||||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||||
|
|
||||||
GO_VERSION ?= 1.22
|
GO_VERSION ?= 1.22
|
||||||
LINT_VERSION ?= 1.56.1
|
LINT_VERSION ?= 1.61.0
|
||||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.5
|
TRUECLOUDLAB_LINT_VERSION ?= 0.0.7
|
||||||
PROTOC_VERSION ?= 25.0
|
PROTOC_VERSION ?= 25.0
|
||||||
PROTOC_GEN_GO_VERSION ?= $(shell go list -f '{{.Version}}' -m google.golang.org/protobuf)
|
|
||||||
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-api-go/v2)
|
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-api-go/v2)
|
||||||
PROTOC_OS_VERSION=osx-x86_64
|
PROTOC_OS_VERSION=osx-x86_64
|
||||||
ifeq ($(shell uname), Linux)
|
ifeq ($(shell uname), Linux)
|
||||||
PROTOC_OS_VERSION=linux-x86_64
|
PROTOC_OS_VERSION=linux-x86_64
|
||||||
endif
|
endif
|
||||||
STATICCHECK_VERSION ?= 2023.1.6
|
STATICCHECK_VERSION ?= 2024.1.1
|
||||||
ARCH = amd64
|
ARCH = amd64
|
||||||
|
|
||||||
BIN = bin
|
BIN = bin
|
||||||
|
@ -28,25 +27,18 @@ DIRS = $(BIN) $(RELEASE)
|
||||||
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
|
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
|
||||||
BINS = $(addprefix $(BIN)/, $(CMDS))
|
BINS = $(addprefix $(BIN)/, $(CMDS))
|
||||||
|
|
||||||
# .deb package versioning
|
|
||||||
OS_RELEASE = $(shell lsb_release -cs)
|
|
||||||
PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
|
||||||
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
|
|
||||||
sed "s/-/~/")-${OS_RELEASE}
|
|
||||||
|
|
||||||
OUTPUT_LINT_DIR ?= $(abspath $(BIN))/linters
|
OUTPUT_LINT_DIR ?= $(abspath $(BIN))/linters
|
||||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||||
TMP_DIR := .cache
|
TMP_DIR := .cache
|
||||||
PROTOBUF_DIR ?= $(abspath $(BIN))/protobuf
|
PROTOBUF_DIR ?= $(abspath $(BIN))/protobuf
|
||||||
PROTOC_DIR ?= $(PROTOBUF_DIR)/protoc-v$(PROTOC_VERSION)
|
PROTOC_DIR ?= $(PROTOBUF_DIR)/protoc-v$(PROTOC_VERSION)
|
||||||
PROTOC_GEN_GO_DIR ?= $(PROTOBUF_DIR)/protoc-gen-go-$(PROTOC_GEN_GO_VERSION)
|
|
||||||
PROTOGEN_FROSTFS_DIR ?= $(PROTOBUF_DIR)/protogen-$(PROTOGEN_FROSTFS_VERSION)
|
PROTOGEN_FROSTFS_DIR ?= $(PROTOBUF_DIR)/protogen-$(PROTOGEN_FROSTFS_VERSION)
|
||||||
STATICCHECK_DIR ?= $(abspath $(BIN))/staticcheck
|
STATICCHECK_DIR ?= $(abspath $(BIN))/staticcheck
|
||||||
STATICCHECK_VERSION_DIR ?= $(STATICCHECK_DIR)/$(STATICCHECK_VERSION)
|
STATICCHECK_VERSION_DIR ?= $(STATICCHECK_DIR)/$(STATICCHECK_VERSION)
|
||||||
|
|
||||||
SOURCES = $(shell find . -type f -name "*.go" -print)
|
SOURCES = $(shell find . -type f -name "*.go" -print)
|
||||||
|
|
||||||
GOFUMPT_VERSION ?= v0.6.0
|
GOFUMPT_VERSION ?= v0.7.0
|
||||||
GOFUMPT_DIR ?= $(abspath $(BIN))/gofumpt
|
GOFUMPT_DIR ?= $(abspath $(BIN))/gofumpt
|
||||||
GOFUMPT_VERSION_DIR ?= $(GOFUMPT_DIR)/$(GOFUMPT_VERSION)
|
GOFUMPT_VERSION_DIR ?= $(GOFUMPT_DIR)/$(GOFUMPT_VERSION)
|
||||||
|
|
||||||
|
@ -60,7 +52,7 @@ LOCODE_DB_PATH=$(abspath ./.cache/locode_db)
|
||||||
LOCODE_DB_VERSION=v0.4.0
|
LOCODE_DB_VERSION=v0.4.0
|
||||||
|
|
||||||
.PHONY: help all images dep clean fmts fumpt imports test lint docker/lint
|
.PHONY: help all images dep clean fmts fumpt imports test lint docker/lint
|
||||||
prepare-release debpackage pre-commit unpre-commit
|
prepare-release pre-commit unpre-commit
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -107,17 +99,15 @@ export-metrics: dep
|
||||||
|
|
||||||
# Regenerate proto files:
|
# Regenerate proto files:
|
||||||
protoc:
|
protoc:
|
||||||
@if [ ! -d "$(PROTOC_DIR)" ] || [ ! -d "$(PROTOC_GEN_GO_DIR)" ] || [ ! -d "$(PROTOGEN_FROSTFS_DIR)" ]; then \
|
@if [ ! -d "$(PROTOC_DIR)" ] || [ ! -d "$(PROTOGEN_FROSTFS_DIR)" ]; then \
|
||||||
make protoc-install; \
|
make protoc-install; \
|
||||||
fi
|
fi
|
||||||
@for f in `find . -type f -name '*.proto' -not -path './bin/*'`; do \
|
@for f in `find . -type f -name '*.proto' -not -path './bin/*'`; do \
|
||||||
echo "⇒ Processing $$f "; \
|
echo "⇒ Processing $$f "; \
|
||||||
$(PROTOC_DIR)/bin/protoc \
|
$(PROTOC_DIR)/bin/protoc \
|
||||||
--proto_path=.:$(PROTOC_DIR)/include:/usr/local/include \
|
--proto_path=.:$(PROTOC_DIR)/include:/usr/local/include \
|
||||||
--plugin=protoc-gen-go=$(PROTOC_GEN_GO_DIR)/protoc-gen-go \
|
|
||||||
--plugin=protoc-gen-go-frostfs=$(PROTOGEN_FROSTFS_DIR)/protogen \
|
--plugin=protoc-gen-go-frostfs=$(PROTOGEN_FROSTFS_DIR)/protogen \
|
||||||
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
|
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
|
||||||
--go_out=. --go_opt=paths=source_relative \
|
|
||||||
--go-grpc_opt=require_unimplemented_servers=false \
|
--go-grpc_opt=require_unimplemented_servers=false \
|
||||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
||||||
done
|
done
|
||||||
|
@ -130,8 +120,6 @@ protoc-install:
|
||||||
@wget -q -O $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip 'https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS_VERSION).zip'
|
@wget -q -O $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip 'https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS_VERSION).zip'
|
||||||
@unzip -q -o $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip -d $(PROTOC_DIR)
|
@unzip -q -o $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip -d $(PROTOC_DIR)
|
||||||
@rm $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip
|
@rm $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip
|
||||||
@echo "⇒ Installing protoc-gen-go..."
|
|
||||||
@GOBIN=$(PROTOC_GEN_GO_DIR) go install -v google.golang.org/protobuf/...@$(PROTOC_GEN_GO_VERSION)
|
|
||||||
@echo "⇒ Instaling protogen FrostFS plugin..."
|
@echo "⇒ Instaling protogen FrostFS plugin..."
|
||||||
@GOBIN=$(PROTOGEN_FROSTFS_DIR) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen@$(PROTOGEN_FROSTFS_VERSION)
|
@GOBIN=$(PROTOGEN_FROSTFS_DIR) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen@$(PROTOGEN_FROSTFS_VERSION)
|
||||||
|
|
||||||
|
@ -203,7 +191,7 @@ lint-install:
|
||||||
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||||
@rm -rf $(TMP_DIR)/linters
|
@rm -rf $(TMP_DIR)/linters
|
||||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
@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)
|
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install -trimpath github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||||
|
|
||||||
# Run linters
|
# Run linters
|
||||||
lint:
|
lint:
|
||||||
|
@ -269,19 +257,6 @@ clean:
|
||||||
rm -rf $(BIN)
|
rm -rf $(BIN)
|
||||||
rm -rf $(RELEASE)
|
rm -rf $(RELEASE)
|
||||||
|
|
||||||
# Package for Debian
|
|
||||||
debpackage:
|
|
||||||
dch -b --package frostfs-node \
|
|
||||||
--controlmaint \
|
|
||||||
--newversion $(PKG_VERSION) \
|
|
||||||
--distribution $(OS_RELEASE) \
|
|
||||||
"Please see CHANGELOG.md for code changes for $(VERSION)"
|
|
||||||
dpkg-buildpackage --no-sign -b
|
|
||||||
|
|
||||||
# Cleanup deb package build directories
|
|
||||||
debclean:
|
|
||||||
dh clean
|
|
||||||
|
|
||||||
# Download locode database
|
# Download locode database
|
||||||
locode-download:
|
locode-download:
|
||||||
mkdir -p $(TMP_DIR)
|
mkdir -p $(TMP_DIR)
|
||||||
|
|
17
README.md
17
README.md
|
@ -7,9 +7,8 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
[![Report](https://goreportcard.com/badge/github.com/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/github.com/TrueCloudLab/frostfs-node)
|
[![Report](https://goreportcard.com/badge/git.frostfs.info/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/git.frostfs.info/TrueCloudLab/frostfs-node)
|
||||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/TrueCloudLab/frostfs-node?sort=semver)
|
![Release (latest)](https://git.frostfs.info/TrueCloudLab/frostfs-node/badges/release.svg)
|
||||||
![License](https://img.shields.io/github/license/TrueCloudLab/frostfs-node.svg?style=popout)
|
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
|
@ -33,8 +32,8 @@ 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://git.frostfs.info/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://git.frostfs.info/TrueCloudLab/frostfs-s3-gw),
|
||||||
[HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
|
[HTTP](https://git.frostfs.info/TrueCloudLab/frostfs-http-gw),
|
||||||
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
||||||
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
||||||
developers to integrate applications without rewriting their code.
|
developers to integrate applications without rewriting their code.
|
||||||
|
@ -45,11 +44,11 @@ Now, we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
|
||||||
platforms will be officially supported after release `1.0`.
|
platforms will be officially supported after release `1.0`.
|
||||||
|
|
||||||
The latest version of frostfs-node works with frostfs-contract
|
The latest version of frostfs-node works with frostfs-contract
|
||||||
[v0.16.0](https://github.com/TrueCloudLab/frostfs-contract/releases/tag/v0.16.0).
|
[v0.19.2](https://git.frostfs.info/TrueCloudLab/frostfs-contract/releases/tag/v0.19.2).
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
To make all binaries you need Go 1.21+ and `make`:
|
To make all binaries you need Go 1.22+ and `make`:
|
||||||
```
|
```
|
||||||
make all
|
make all
|
||||||
```
|
```
|
||||||
|
@ -71,7 +70,7 @@ make docker/bin/frostfs-<name> # build a specific binary
|
||||||
|
|
||||||
## Docker images
|
## Docker images
|
||||||
|
|
||||||
To make docker images suitable for use in [frostfs-dev-env](https://github.com/TrueCloudLab/frostfs-dev-env/) use:
|
To make docker images suitable for use in [frostfs-dev-env](https://git.frostfs.info/TrueCloudLab/frostfs-dev-env/) use:
|
||||||
```
|
```
|
||||||
make images
|
make images
|
||||||
```
|
```
|
||||||
|
@ -125,7 +124,7 @@ the feature/topic you are going to implement.
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
FrostFS is maintained by [True Cloud Lab](https://github.com/TrueCloudLab/) with the help and
|
FrostFS is maintained by [True Cloud Lab](https://git.frostfs.info/TrueCloudLab/) with the help and
|
||||||
contributions from community members.
|
contributions from community members.
|
||||||
|
|
||||||
Please see [CREDITS](CREDITS.md) for details.
|
Please see [CREDITS](CREDITS.md) for details.
|
||||||
|
|
|
@ -9,8 +9,8 @@ related configuration details.
|
||||||
|
|
||||||
To follow this guide you need:
|
To follow this guide you need:
|
||||||
- latest released version of [neo-go](https://github.com/nspcc-dev/neo-go/releases) (v0.97.2 at the moment),
|
- latest released version of [neo-go](https://github.com/nspcc-dev/neo-go/releases) (v0.97.2 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 [frostfs-adm](https://git.frostfs.info/TrueCloudLab/frostfs-node/releases) utility (v0.42.9 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://git.frostfs.info/TrueCloudLab/frostfs-contract/releases) (v0.19.2 at the moment).
|
||||||
|
|
||||||
## Step 1: Prepare network configuration
|
## Step 1: Prepare network configuration
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ func generateConfigExample(appDir string, credSize int) (string, error) {
|
||||||
tmpl.AlphabetDir = filepath.Join(appDir, "alphabet-wallets")
|
tmpl.AlphabetDir = filepath.Join(appDir, "alphabet-wallets")
|
||||||
|
|
||||||
var i innerring.GlagoliticLetter
|
var i innerring.GlagoliticLetter
|
||||||
for i = 0; i < innerring.GlagoliticLetter(credSize); i++ {
|
for i = range innerring.GlagoliticLetter(credSize) {
|
||||||
tmpl.Glagolitics = append(tmpl.Glagolitics, i.String())
|
tmpl.Glagolitics = append(tmpl.Glagolitics, i.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
cmd/frostfs-adm/internal/modules/metabase/root.go
Normal file
15
cmd/frostfs-adm/internal/modules/metabase/root.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package metabase
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
// RootCmd is a root command of config section.
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "metabase",
|
||||||
|
Short: "Section for metabase commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(UpgradeCmd)
|
||||||
|
|
||||||
|
initUpgradeCommand()
|
||||||
|
}
|
150
cmd/frostfs-adm/internal/modules/metabase/upgrade.go
Normal file
150
cmd/frostfs-adm/internal/modules/metabase/upgrade.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package metabase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||||
|
engineconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine"
|
||||||
|
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
|
||||||
|
morphconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/morph"
|
||||||
|
nodeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/node"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
|
meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||||
|
morphcontainer "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
noCompactFlag = "no-compact"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoPathsFound = errors.New("no metabase paths found")
|
||||||
|
errNoMorphEndpointsFound = errors.New("no morph endpoints found")
|
||||||
|
)
|
||||||
|
|
||||||
|
var UpgradeCmd = &cobra.Command{
|
||||||
|
Use: "upgrade",
|
||||||
|
Short: "Upgrade metabase to latest version",
|
||||||
|
RunE: upgrade,
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgrade(cmd *cobra.Command, _ []string) error {
|
||||||
|
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
appCfg := config.New(configFile, configDir, config.EnvPrefix)
|
||||||
|
paths, err := getMetabasePaths(appCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return errNoPathsFound
|
||||||
|
}
|
||||||
|
cmd.Println("found", len(paths), "metabases:")
|
||||||
|
for i, path := range paths {
|
||||||
|
cmd.Println(i+1, ":", path)
|
||||||
|
}
|
||||||
|
mc, err := createMorphClient(cmd.Context(), appCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer mc.Close()
|
||||||
|
civ, err := createContainerInfoProvider(mc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
noCompact, _ := cmd.Flags().GetBool(noCompactFlag)
|
||||||
|
result := make(map[string]bool)
|
||||||
|
var resultGuard sync.Mutex
|
||||||
|
eg, ctx := errgroup.WithContext(cmd.Context())
|
||||||
|
for _, path := range paths {
|
||||||
|
eg.Go(func() error {
|
||||||
|
var success bool
|
||||||
|
cmd.Println("upgrading metabase", path, "...")
|
||||||
|
if err := meta.Upgrade(ctx, path, !noCompact, civ, func(a ...any) {
|
||||||
|
cmd.Println(append([]any{time.Now().Format(time.RFC3339), ":", path, ":"}, a...)...)
|
||||||
|
}); err != nil {
|
||||||
|
cmd.Println("error: failed to upgrade metabase", path, ":", err)
|
||||||
|
} else {
|
||||||
|
success = true
|
||||||
|
cmd.Println("metabase", path, "upgraded successfully")
|
||||||
|
}
|
||||||
|
resultGuard.Lock()
|
||||||
|
result[path] = success
|
||||||
|
resultGuard.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for mb, ok := range result {
|
||||||
|
if ok {
|
||||||
|
cmd.Println(mb, ": success")
|
||||||
|
} else {
|
||||||
|
cmd.Println(mb, ": failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetabasePaths(appCfg *config.Config) ([]string, error) {
|
||||||
|
var paths []string
|
||||||
|
if err := engineconfig.IterateShards(appCfg, false, func(sc *shardconfig.Config) error {
|
||||||
|
paths = append(paths, sc.Metabase().Path())
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("get metabase paths: %w", err)
|
||||||
|
}
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMorphClient(ctx context.Context, appCfg *config.Config) (*client.Client, error) {
|
||||||
|
addresses := morphconfig.RPCEndpoint(appCfg)
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
return nil, errNoMorphEndpointsFound
|
||||||
|
}
|
||||||
|
key := nodeconfig.Key(appCfg)
|
||||||
|
cli, err := client.New(ctx,
|
||||||
|
key,
|
||||||
|
client.WithDialTimeout(morphconfig.DialTimeout(appCfg)),
|
||||||
|
client.WithEndpoints(addresses...),
|
||||||
|
client.WithSwitchInterval(morphconfig.SwitchInterval(appCfg)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create morph client:%w", err)
|
||||||
|
}
|
||||||
|
return cli, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createContainerInfoProvider(cli *client.Client) (container.InfoProvider, error) {
|
||||||
|
sh, err := cli.NNSContractAddress(client.NNSContainerContractName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve container contract hash: %w", err)
|
||||||
|
}
|
||||||
|
cc, err := morphcontainer.NewFromMorph(cli, sh, 0, morphcontainer.TryNotary())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create morph container client: %w", err)
|
||||||
|
}
|
||||||
|
return container.NewInfoProvider(func() (container.Source, error) {
|
||||||
|
return morphcontainer.AsContainerSource(cc), nil
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initUpgradeCommand() {
|
||||||
|
flags := UpgradeCmd.Flags()
|
||||||
|
flags.Bool(noCompactFlag, false, "Do not compact upgraded metabase file")
|
||||||
|
}
|
|
@ -139,13 +139,12 @@ func dumpContainers(cmd *cobra.Command, _ []string) error {
|
||||||
func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) {
|
func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invoker, id []byte) (*Container, error) {
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
emit.AppCall(bw.BinWriter, ch, "get", callflag.All, 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())
|
res, err := inv.Run(bw.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't get container info: %w", err)
|
return nil, fmt.Errorf("can't get container info: %w", err)
|
||||||
}
|
}
|
||||||
if len(res.Stack) != 2 {
|
if len(res.Stack) != 1 {
|
||||||
return nil, fmt.Errorf("%w: expected 2 items on stack", errInvalidContainerResponse)
|
return nil, fmt.Errorf("%w: expected 1 items on stack", errInvalidContainerResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt := new(Container)
|
cnt := new(Container)
|
||||||
|
@ -154,14 +153,6 @@ func dumpSingleContainer(bw *io.BufBinWriter, ch util.Uint160, inv *invoker.Invo
|
||||||
return nil, fmt.Errorf("%w: %v", errInvalidContainerResponse, err)
|
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
|
return cnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,10 +249,6 @@ func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd
|
||||||
func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) {
|
func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) {
|
||||||
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
|
emit.AppCall(bw.BinWriter, ch, "put", callflag.All,
|
||||||
cnt.Value, cnt.Signature, cnt.PublicKey, cnt.Token)
|
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 *helper.InitializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
|
func isContainerRestored(cmd *cobra.Command, wCtx *helper.InitializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) {
|
||||||
|
@ -322,15 +309,6 @@ type Container struct {
|
||||||
Signature []byte `json:"signature"`
|
Signature []byte `json:"signature"`
|
||||||
PublicKey []byte `json:"public_key"`
|
PublicKey []byte `json:"public_key"`
|
||||||
Token []byte `json:"token"`
|
Token []byte `json:"token"`
|
||||||
EACL *EACL `json:"eacl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EACL represents extended ACL struct in contract storage.
|
|
||||||
type EACL struct {
|
|
||||||
Value []byte `json:"value"`
|
|
||||||
Signature []byte `json:"signature"`
|
|
||||||
PublicKey []byte `json:"public_key"`
|
|
||||||
Token []byte `json:"token"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToStackItem implements stackitem.Convertible.
|
// ToStackItem implements stackitem.Convertible.
|
||||||
|
@ -377,50 +355,6 @@ func (c *Container) FromStackItem(item stackitem.Item) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToStackItem implements stackitem.Convertible.
|
|
||||||
func (c *EACL) ToStackItem() (stackitem.Item, error) {
|
|
||||||
return stackitem.NewStruct([]stackitem.Item{
|
|
||||||
stackitem.NewByteArray(c.Value),
|
|
||||||
stackitem.NewByteArray(c.Signature),
|
|
||||||
stackitem.NewByteArray(c.PublicKey),
|
|
||||||
stackitem.NewByteArray(c.Token),
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromStackItem implements stackitem.Convertible.
|
|
||||||
func (c *EACL) FromStackItem(item stackitem.Item) error {
|
|
||||||
arr, ok := item.Value().([]stackitem.Item)
|
|
||||||
if !ok || len(arr) != 4 {
|
|
||||||
return errors.New("invalid stack item type")
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := arr[0].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("invalid eACL value")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := arr[1].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("invalid eACL signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub, err := arr[2].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("invalid eACL public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
tok, err := arr[3].TryBytes()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("invalid eACL token")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Value = value
|
|
||||||
c.Signature = sig
|
|
||||||
c.PublicKey = pub
|
|
||||||
c.Token = tok
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCIDFilterFunc returns filtering function for container IDs.
|
// getCIDFilterFunc returns filtering function for container IDs.
|
||||||
// Raw byte slices are used because it works with structures returned
|
// Raw byte slices are used because it works with structures returned
|
||||||
// from contract.
|
// from contract.
|
||||||
|
|
|
@ -68,7 +68,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
if irSize != 0 {
|
if irSize != 0 {
|
||||||
bw.Reset()
|
bw.Reset()
|
||||||
for i := 0; i < irSize; i++ {
|
for i := range irSize {
|
||||||
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
|
||||||
helper.GetAlphabetNNSDomain(i),
|
helper.GetAlphabetNNSDomain(i),
|
||||||
int64(nns.TXT))
|
int64(nns.TXT))
|
||||||
|
@ -79,7 +79,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < irSize; i++ {
|
for i := range irSize {
|
||||||
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)}
|
info := contractDumpInfo{name: fmt.Sprintf("alphabet %d", i)}
|
||||||
if h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i]); err == nil {
|
if h, err := helper.ParseNNSResolveResult(alphaRes.Stack[i]); err == nil {
|
||||||
info.hash = h
|
info.hash = h
|
||||||
|
|
|
@ -73,7 +73,6 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
||||||
return nil, fmt.Errorf("can't fetch password: %w", err)
|
return nil, fmt.Errorf("can't fetch password: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
i := i
|
|
||||||
errG.Go(func() error {
|
errG.Go(func() error {
|
||||||
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
|
||||||
f, err := os.OpenFile(p, os.O_CREATE, 0o644)
|
f, err := os.OpenFile(p, os.O_CREATE, 0o644)
|
||||||
|
@ -107,7 +106,6 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
|
||||||
// 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 := range wallets {
|
||||||
i := i
|
|
||||||
ps := pubs.Copy()
|
ps := pubs.Copy()
|
||||||
errG.Go(func() error {
|
errG.Go(func() error {
|
||||||
if err := addMultisigAccount(wallets[i], majCount, constants.CommitteeAccountName, passwords[i], ps); err != nil {
|
if err := addMultisigAccount(wallets[i], majCount, constants.CommitteeAccountName, passwords[i], ps); err != nil {
|
||||||
|
|
|
@ -63,7 +63,7 @@ func TestGenerateAlphabet(t *testing.T) {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
v.Set(commonflags.AlphabetWalletsFlag, walletDir)
|
||||||
require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
|
||||||
for i := uint64(0); i < size; i++ {
|
for i := range uint64(size) {
|
||||||
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
buf.WriteString(strconv.FormatUint(i, 10) + "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,5 +166,6 @@ func DeployNNS(c *InitializeContext, method string) error {
|
||||||
return fmt.Errorf("can't send deploy transaction: %w", err)
|
return fmt.Errorf("can't send deploy transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.Command.Println("NNS hash:", invokeHash.StringLE())
|
||||||
return c.AwaitTx()
|
return c.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,7 +224,7 @@ func (l *LocalClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, e
|
||||||
paramz = []manifest.Parameter{{Type: smartcontract.SignatureType}}
|
paramz = []manifest.Parameter{{Type: smartcontract.SignatureType}}
|
||||||
} else if nSigs, _, ok := vm.ParseMultiSigContract(w.VerificationScript); ok {
|
} else if nSigs, _, ok := vm.ParseMultiSigContract(w.VerificationScript); ok {
|
||||||
paramz = make([]manifest.Parameter, nSigs)
|
paramz = make([]manifest.Parameter, nSigs)
|
||||||
for j := 0; j < nSigs; j++ {
|
for j := range nSigs {
|
||||||
paramz[j] = manifest.Parameter{Type: smartcontract.SignatureType}
|
paramz[j] = manifest.Parameter{Type: smartcontract.SignatureType}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,13 +72,17 @@ func InvalidConfigValueErr(key string) error {
|
||||||
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
return fmt.Errorf("invalid %s config value from netmap contract", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EmitNewEpochCall(bw *io.BufBinWriter, wCtx *InitializeContext, nmHash util.Uint160) error {
|
func EmitNewEpochCall(bw *io.BufBinWriter, wCtx *InitializeContext, nmHash util.Uint160, countEpoch int64) error {
|
||||||
|
if countEpoch <= 0 {
|
||||||
|
return errors.New("number of epochs cannot be less than 1")
|
||||||
|
}
|
||||||
|
|
||||||
curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
|
curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("can't fetch current epoch from the netmap contract")
|
return errors.New("can't fetch current epoch from the netmap contract")
|
||||||
}
|
}
|
||||||
|
|
||||||
newEpoch := curr + 1
|
newEpoch := curr + countEpoch
|
||||||
wCtx.Command.Printf("Current epoch: %d, increase to %d.\n", curr, newEpoch)
|
wCtx.Command.Printf("Current epoch: %d, increase to %d.\n", curr, newEpoch)
|
||||||
|
|
||||||
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
// In NeoFS this is done via Notary contract. Here, however, we can form the
|
||||||
|
|
|
@ -44,7 +44,7 @@ func openAlphabetWallets(v *viper.Viper, walletDir string) ([]*wallet.Wallet, er
|
||||||
|
|
||||||
var wallets []*wallet.Wallet
|
var wallets []*wallet.Wallet
|
||||||
var letter string
|
var letter string
|
||||||
for i := 0; i < constants.MaxAlphabetNodes; i++ {
|
for i := range constants.MaxAlphabetNodes {
|
||||||
letter = innerring.GlagoliticLetter(i).String()
|
letter = innerring.GlagoliticLetter(i).String()
|
||||||
p := filepath.Join(walletDir, letter+".json")
|
p := filepath.Join(walletDir, letter+".json")
|
||||||
var w *wallet.Wallet
|
var w *wallet.Wallet
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||||
"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/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
@ -29,10 +31,14 @@ func setNotaryAndAlphabetNodes(c *helper.InitializeContext) error {
|
||||||
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
|
callflag.States|callflag.AllowNotify, int64(noderoles.NeoFSAlphabet), pubs)
|
||||||
|
|
||||||
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
if err := c.SendCommitteeTx(w.Bytes(), false); err != nil {
|
||||||
return err
|
return fmt.Errorf("send committee transaction: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.AwaitTx()
|
err := c.AwaitTx()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("await committee transaction: %w", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRolesFinished(c *helper.InitializeContext) (bool, error) {
|
func setRolesFinished(c *helper.InitializeContext) (bool, error) {
|
||||||
|
|
|
@ -113,7 +113,7 @@ func generateTestData(dir string, size int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubs []string
|
var pubs []string
|
||||||
for i := 0; i < size; i++ {
|
for i := range size {
|
||||||
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 {
|
if err != nil {
|
||||||
|
@ -148,7 +148,7 @@ func generateTestData(dir string, size int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTestCredentials(v *viper.Viper, size int) {
|
func setTestCredentials(v *viper.Viper, size int) {
|
||||||
for i := 0; i < size; i++ {
|
for i := range size {
|
||||||
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
||||||
}
|
}
|
||||||
v.Set("credentials.contract", constants.TestContractPassword)
|
v.Set("credentials.contract", constants.TestContractPassword)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package initialize
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
|
||||||
|
@ -26,12 +27,12 @@ const (
|
||||||
initialAlphabetGASAmount = 10_000 * native.GASFactor
|
initialAlphabetGASAmount = 10_000 * native.GASFactor
|
||||||
// initialProxyGASAmount represents the amount of GAS given to a proxy contract.
|
// initialProxyGASAmount represents the amount of GAS given to a proxy contract.
|
||||||
initialProxyGASAmount = 50_000 * native.GASFactor
|
initialProxyGASAmount = 50_000 * native.GASFactor
|
||||||
// alphabetGasRatio is a coefficient that defines the threshold below which
|
|
||||||
// the balance of the alphabet node is considered not replenished. The value
|
|
||||||
// of this coefficient is determined empirically.
|
|
||||||
alphabetGasRatio = 5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func initialCommitteeGASAmount(c *helper.InitializeContext) int64 {
|
||||||
|
return (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2
|
||||||
|
}
|
||||||
|
|
||||||
func transferFunds(c *helper.InitializeContext) error {
|
func transferFunds(c *helper.InitializeContext) error {
|
||||||
ok, err := transferFundsFinished(c)
|
ok, err := transferFundsFinished(c)
|
||||||
if ok || err != nil {
|
if ok || err != nil {
|
||||||
|
@ -58,7 +59,7 @@ func transferFunds(c *helper.InitializeContext) error {
|
||||||
transferTarget{
|
transferTarget{
|
||||||
Token: gas.Hash,
|
Token: gas.Hash,
|
||||||
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
Address: c.CommitteeAcc.Contract.ScriptHash(),
|
||||||
Amount: (gasInitialTotalSupply - initialAlphabetGASAmount*int64(len(c.Wallets))) / 2,
|
Amount: initialCommitteeGASAmount(c),
|
||||||
},
|
},
|
||||||
transferTarget{
|
transferTarget{
|
||||||
Token: neo.Hash,
|
Token: neo.Hash,
|
||||||
|
@ -79,12 +80,19 @@ func transferFunds(c *helper.InitializeContext) error {
|
||||||
return c.AwaitTx()
|
return c.AwaitTx()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transferFundsFinished checks balances of accounts we transfer GAS to.
|
||||||
|
// The stage is considered finished if the balance is greater than the half of what we need to transfer.
|
||||||
func transferFundsFinished(c *helper.InitializeContext) (bool, error) {
|
func transferFundsFinished(c *helper.InitializeContext) (bool, error) {
|
||||||
acc := c.Accounts[0]
|
acc := c.Accounts[0]
|
||||||
|
|
||||||
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)
|
||||||
res, err := r.BalanceOf(acc.Contract.ScriptHash())
|
res, err := r.BalanceOf(acc.Contract.ScriptHash())
|
||||||
return res.Cmp(big.NewInt(alphabetGasRatio*native.GASFactor)) == 1, err
|
if err != nil || res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) != 1 {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = r.BalanceOf(c.CommitteeAcc.ScriptHash())
|
||||||
|
return res != nil && res.Cmp(big.NewInt(initialCommitteeGASAmount(c)/2)) == 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferGASToProxy(c *helper.InitializeContext) error {
|
func transferGASToProxy(c *helper.InitializeContext) error {
|
||||||
|
@ -144,5 +152,17 @@ func createNEP17MultiTransferTx(c helper.Client, acc *wallet.Account, recipients
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create actor: %w", err)
|
return nil, fmt.Errorf("can't create actor: %w", err)
|
||||||
}
|
}
|
||||||
return act.MakeRun(w.Bytes())
|
tx, err := act.MakeRun(w.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
sum := make(map[util.Uint160]int64)
|
||||||
|
for _, recipient := range recipients {
|
||||||
|
sum[recipient.Token] += recipient.Amount
|
||||||
|
}
|
||||||
|
detail := make([]string, 0, len(sum))
|
||||||
|
for _, value := range sum {
|
||||||
|
detail = append(detail, fmt.Sprintf("amount=%v", value))
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("transfer failed: from=%s(%s) %s: %w", acc.Label, acc.Address, strings.Join(detail, " "), err)
|
||||||
|
}
|
||||||
|
return tx, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const deltaFlag = "delta"
|
||||||
|
|
||||||
func ForceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
func ForceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||||
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
wCtx, err := helper.NewInitializeContext(cmd, viper.GetViper())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,7 +32,8 @@ func ForceNewEpochCmd(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
delta, _ := cmd.Flags().GetInt64(deltaFlag)
|
||||||
|
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash, delta); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ func initForceNewEpochCmd() {
|
||||||
ForceNewEpoch.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
ForceNewEpoch.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
ForceNewEpoch.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
ForceNewEpoch.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
ForceNewEpoch.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
ForceNewEpoch.Flags().String(commonflags.LocalDumpFlag, "", "Path to the blocks dump file")
|
||||||
|
ForceNewEpoch.Flags().Int64(deltaFlag, 1, "Number of epochs to increase the current epoch")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -42,3 +42,23 @@ func registerDomain(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "register domain error: %w", err)
|
commonCmd.ExitOnErr(cmd, "register domain error: %w", err)
|
||||||
cmd.Println("Domain registered successfully")
|
cmd.Println("Domain registered successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initDeleteCmd() {
|
||||||
|
Cmd.AddCommand(deleteCmd)
|
||||||
|
deleteCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
|
deleteCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
|
deleteCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||||
|
|
||||||
|
_ = cobra.MarkFlagRequired(deleteCmd.Flags(), nnsNameFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteDomain(cmd *cobra.Command, _ []string) {
|
||||||
|
c, actor, _ := getRPCClient(cmd)
|
||||||
|
|
||||||
|
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||||
|
h, vub, err := c.DeleteDomain(name)
|
||||||
|
|
||||||
|
_, err = actor.Wait(h, vub, err)
|
||||||
|
commonCmd.ExitOnErr(cmd, "delete domain error: %w", err)
|
||||||
|
cmd.Println("Domain deleted successfully")
|
||||||
|
}
|
|
@ -47,6 +47,19 @@ func initDelRecordsCmd() {
|
||||||
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsRecordTypeFlag)
|
_ = cobra.MarkFlagRequired(delRecordsCmd.Flags(), nnsRecordTypeFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initDelRecordCmd() {
|
||||||
|
Cmd.AddCommand(delRecordCmd)
|
||||||
|
delRecordCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
|
delRecordCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
|
delRecordCmd.Flags().String(nnsNameFlag, "", nnsNameFlagDesc)
|
||||||
|
delRecordCmd.Flags().String(nnsRecordTypeFlag, "", nnsRecordTypeFlagDesc)
|
||||||
|
delRecordCmd.Flags().String(nnsRecordDataFlag, "", nnsRecordDataFlagDesc)
|
||||||
|
|
||||||
|
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsNameFlag)
|
||||||
|
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordTypeFlag)
|
||||||
|
_ = cobra.MarkFlagRequired(delRecordCmd.Flags(), nnsRecordDataFlag)
|
||||||
|
}
|
||||||
|
|
||||||
func addRecord(cmd *cobra.Command, _ []string) {
|
func addRecord(cmd *cobra.Command, _ []string) {
|
||||||
c, actor, _ := getRPCClient(cmd)
|
c, actor, _ := getRPCClient(cmd)
|
||||||
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||||
|
@ -115,6 +128,22 @@ func delRecords(cmd *cobra.Command, _ []string) {
|
||||||
cmd.Println("Records removed successfully")
|
cmd.Println("Records removed successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func delRecord(cmd *cobra.Command, _ []string) {
|
||||||
|
c, actor, _ := getRPCClient(cmd)
|
||||||
|
name, _ := cmd.Flags().GetString(nnsNameFlag)
|
||||||
|
data, _ := cmd.Flags().GetString(nnsRecordDataFlag)
|
||||||
|
recordType, _ := cmd.Flags().GetString(nnsRecordTypeFlag)
|
||||||
|
typ, err := getRecordType(recordType)
|
||||||
|
commonCmd.ExitOnErr(cmd, "unable to parse record type: %w", err)
|
||||||
|
h, vub, err := c.DeleteRecord(name, typ, data)
|
||||||
|
commonCmd.ExitOnErr(cmd, "unable to delete record: %w", err)
|
||||||
|
|
||||||
|
cmd.Println("Waiting for transaction to persist...")
|
||||||
|
_, err = actor.Wait(h, vub, err)
|
||||||
|
commonCmd.ExitOnErr(cmd, "delete records error: %w", err)
|
||||||
|
cmd.Println("Record removed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
func getRecordType(recordType string) (*big.Int, error) {
|
func getRecordType(recordType string) (*big.Int, error) {
|
||||||
switch strings.ToUpper(recordType) {
|
switch strings.ToUpper(recordType) {
|
||||||
case "A":
|
case "A":
|
||||||
|
|
|
@ -42,6 +42,15 @@ var (
|
||||||
},
|
},
|
||||||
Run: registerDomain,
|
Run: registerDomain,
|
||||||
}
|
}
|
||||||
|
deleteCmd = &cobra.Command{
|
||||||
|
Use: "delete",
|
||||||
|
Short: "Delete a domain by name",
|
||||||
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
},
|
||||||
|
Run: deleteDomain,
|
||||||
|
}
|
||||||
renewCmd = &cobra.Command{
|
renewCmd = &cobra.Command{
|
||||||
Use: "renew",
|
Use: "renew",
|
||||||
Short: "Increases domain expiration date",
|
Short: "Increases domain expiration date",
|
||||||
|
@ -86,14 +95,25 @@ var (
|
||||||
},
|
},
|
||||||
Run: delRecords,
|
Run: delRecords,
|
||||||
}
|
}
|
||||||
|
delRecordCmd = &cobra.Command{
|
||||||
|
Use: "delete-record",
|
||||||
|
Short: "Removes domain record with the specified type and data",
|
||||||
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
|
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
|
||||||
|
_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
|
||||||
|
},
|
||||||
|
Run: delRecord,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
initTokensCmd()
|
initTokensCmd()
|
||||||
initRegisterCmd()
|
initRegisterCmd()
|
||||||
|
initDeleteCmd()
|
||||||
initRenewCmd()
|
initRenewCmd()
|
||||||
initUpdateCmd()
|
initUpdateCmd()
|
||||||
initAddRecordCmd()
|
initAddRecordCmd()
|
||||||
initGetRecordsCmd()
|
initGetRecordsCmd()
|
||||||
initDelRecordsCmd()
|
initDelRecordsCmd()
|
||||||
|
initDelRecordCmd()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
package nns
|
package nns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||||
|
client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/nns"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
verboseDesc = "Include additional information about CNAME record."
|
||||||
|
)
|
||||||
|
|
||||||
func initTokensCmd() {
|
func initTokensCmd() {
|
||||||
Cmd.AddCommand(tokensCmd)
|
Cmd.AddCommand(tokensCmd)
|
||||||
tokensCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
tokensCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
tokensCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
tokensCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
|
tokensCmd.Flags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, verboseDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTokens(cmd *cobra.Command, _ []string) {
|
func listTokens(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -18,7 +28,39 @@ func listTokens(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "unable to get tokens: %w", err)
|
commonCmd.ExitOnErr(cmd, "unable to get tokens: %w", err)
|
||||||
for toks, err := it.Next(10); err == nil && len(toks) > 0; toks, err = it.Next(10) {
|
for toks, err := it.Next(10); err == nil && len(toks) > 0; toks, err = it.Next(10) {
|
||||||
for _, token := range toks {
|
for _, token := range toks {
|
||||||
cmd.Println(string(token))
|
output := string(token)
|
||||||
|
if verbose, _ := cmd.Flags().GetBool(commonflags.Verbose); verbose {
|
||||||
|
cname, err := getCnameRecord(c, token)
|
||||||
|
commonCmd.ExitOnErr(cmd, "", err)
|
||||||
|
if cname != "" {
|
||||||
|
output += " (CNAME: " + cname + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Println(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCnameRecord(c *client.Contract, token []byte) (string, error) {
|
||||||
|
items, err := c.GetRecords(string(token), big.NewInt(int64(nns.CNAME)))
|
||||||
|
|
||||||
|
// GetRecords returns the error "not an array" if the domain does not contain records.
|
||||||
|
if err != nil && strings.Contains(err.Error(), "not an array") {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := items[0].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(record), nil
|
||||||
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ func RemoveNodesCmd(cmd *cobra.Command, args []string) error {
|
||||||
int64(netmapcontract.NodeStateOffline), nodeKeys[i].Bytes())
|
int64(netmapcontract.NodeStateOffline), nodeKeys[i].Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash); err != nil {
|
if err := helper.EmitNewEpochCall(bw, wCtx, nmHash, 1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,13 @@ var (
|
||||||
func initProxyAddAccount() {
|
func initProxyAddAccount() {
|
||||||
AddAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
AddAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
AddAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
AddAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||||
|
AddAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initProxyRemoveAccount() {
|
func initProxyRemoveAccount() {
|
||||||
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
RemoveAccountCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
|
||||||
RemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
RemoveAccountCmd.Flags().String(accountAddressFlag, "", "Wallet address string")
|
||||||
|
RemoveAccountCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/metabase"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||||
|
@ -41,6 +42,7 @@ func init() {
|
||||||
rootCmd.AddCommand(config.RootCmd)
|
rootCmd.AddCommand(config.RootCmd)
|
||||||
rootCmd.AddCommand(morph.RootCmd)
|
rootCmd.AddCommand(morph.RootCmd)
|
||||||
rootCmd.AddCommand(storagecfg.RootCmd)
|
rootCmd.AddCommand(storagecfg.RootCmd)
|
||||||
|
rootCmd.AddCommand(metabase.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, gendoc.Options{}))
|
||||||
|
|
|
@ -2,10 +2,13 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -14,7 +17,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -189,31 +191,6 @@ func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteCon
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EACLPrm groups parameters of EACL operation.
|
|
||||||
type EACLPrm struct {
|
|
||||||
Client *client.Client
|
|
||||||
ClientParams client.PrmContainerEACL
|
|
||||||
}
|
|
||||||
|
|
||||||
// EACLRes groups the resulting values of EACL operation.
|
|
||||||
type EACLRes struct {
|
|
||||||
cliRes *client.ResContainerEACL
|
|
||||||
}
|
|
||||||
|
|
||||||
// EACL returns requested eACL table.
|
|
||||||
func (x EACLRes) EACL() eacl.Table {
|
|
||||||
return x.cliRes.Table()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EACL reads eACL table from FrostFS by container ID.
|
|
||||||
//
|
|
||||||
// Returns any error which prevented the operation from completing correctly in error return.
|
|
||||||
func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) {
|
|
||||||
res.cliRes, err = prm.Client.ContainerEACL(ctx, prm.ClientParams)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
||||||
type NetworkInfoPrm struct {
|
type NetworkInfoPrm struct {
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
|
@ -588,13 +565,6 @@ type HeadObjectPrm struct {
|
||||||
commonObjectPrm
|
commonObjectPrm
|
||||||
objectAddressPrm
|
objectAddressPrm
|
||||||
rawPrm
|
rawPrm
|
||||||
|
|
||||||
mainOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMainOnlyFlag sets flag to get only main fields of an object header in terms of FrostFS API.
|
|
||||||
func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
|
|
||||||
x.mainOnly = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeadObjectRes groups the resulting values of HeadObject operation.
|
// HeadObjectRes groups the resulting values of HeadObject operation.
|
||||||
|
@ -689,9 +659,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, ok = rdr.Read(buf)
|
n, ok = rdr.Read(buf)
|
||||||
for i := 0; i < n; i++ {
|
list = append(list, buf[:n]...)
|
||||||
list = append(list, buf[i])
|
|
||||||
}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -869,3 +837,65 @@ func SyncContainerSettings(ctx context.Context, prm SyncContainerPrm) (*SyncCont
|
||||||
|
|
||||||
return new(SyncContainerRes), nil
|
return new(SyncContainerRes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PatchObjectPrm groups parameters of PatchObject operation.
|
||||||
|
type PatchObjectPrm struct {
|
||||||
|
commonObjectPrm
|
||||||
|
objectAddressPrm
|
||||||
|
|
||||||
|
NewAttributes []objectSDK.Attribute
|
||||||
|
|
||||||
|
ReplaceAttribute bool
|
||||||
|
|
||||||
|
PayloadPatches []PayloadPatch
|
||||||
|
}
|
||||||
|
|
||||||
|
type PayloadPatch struct {
|
||||||
|
Range objectSDK.Range
|
||||||
|
|
||||||
|
PayloadPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatchRes struct {
|
||||||
|
OID oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func Patch(ctx context.Context, prm PatchObjectPrm) (*PatchRes, error) {
|
||||||
|
patchPrm := client.PrmObjectPatch{
|
||||||
|
XHeaders: prm.xHeaders,
|
||||||
|
BearerToken: prm.bearerToken,
|
||||||
|
Session: prm.sessionToken,
|
||||||
|
Address: prm.objAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(prm.PayloadPatches, func(a, b PayloadPatch) int {
|
||||||
|
return cmp.Compare(a.Range.GetOffset(), b.Range.GetOffset())
|
||||||
|
})
|
||||||
|
|
||||||
|
patcher, err := prm.cli.ObjectPatchInit(ctx, patchPrm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("init payload reading: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if patcher.PatchAttributes(ctx, prm.NewAttributes, prm.ReplaceAttribute) {
|
||||||
|
for _, pp := range prm.PayloadPatches {
|
||||||
|
payloadFile, err := os.OpenFile(pp.PayloadPath, os.O_RDONLY, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
applied := patcher.PatchPayload(ctx, &pp.Range, payloadFile)
|
||||||
|
_ = payloadFile.Close()
|
||||||
|
if !applied {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := patcher.Close(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &PatchRes{
|
||||||
|
OID: res.ObjectID(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
|
||||||
GRPCDialOptions: []grpc.DialOption{
|
GRPCDialOptions: []grpc.DialOption{
|
||||||
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
||||||
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
||||||
|
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
|
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
|
||||||
|
|
|
@ -24,6 +24,8 @@ var testCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_getOrGenerate(t *testing.T) {
|
func Test_getOrGenerate(t *testing.T) {
|
||||||
|
t.Cleanup(viper.Reset)
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
wallPath := filepath.Join(dir, "wallet.json")
|
wallPath := filepath.Join(dir, "wallet.json")
|
||||||
|
|
|
@ -31,11 +31,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultNamespace = ""
|
namespaceTarget = "namespace"
|
||||||
namespaceTarget = "namespace"
|
containerTarget = "container"
|
||||||
containerTarget = "container"
|
userTarget = "user"
|
||||||
userTarget = "user"
|
groupTarget = "group"
|
||||||
groupTarget = "group"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errUnknownTargetType = errors.New("unknown target type")
|
var errUnknownTargetType = errors.New("unknown target type")
|
||||||
|
|
|
@ -139,7 +139,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < awaitTimeout; i++ {
|
for range awaitTimeout {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||||
|
|
|
@ -110,7 +110,7 @@ Only owner of the container has a permission to remove container.`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < awaitTimeout; i++ {
|
for range awaitTimeout {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
|
||||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var getExtendedACLCmd = &cobra.Command{
|
|
||||||
Use: "get-eacl",
|
|
||||||
Short: "Get extended ACL table of container",
|
|
||||||
Long: `Get extended ACL table of container`,
|
|
||||||
Run: func(cmd *cobra.Command, _ []string) {
|
|
||||||
id := parseContainerID(cmd)
|
|
||||||
pk := key.GetOrGenerate(cmd)
|
|
||||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
|
||||||
|
|
||||||
eaclPrm := internalclient.EACLPrm{
|
|
||||||
Client: cli,
|
|
||||||
ClientParams: client.PrmContainerEACL{
|
|
||||||
ContainerID: &id,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := internalclient.EACL(cmd.Context(), eaclPrm)
|
|
||||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
|
||||||
|
|
||||||
eaclTable := res.EACL()
|
|
||||||
|
|
||||||
if containerPathTo == "" {
|
|
||||||
cmd.Println("eACL: ")
|
|
||||||
common.PrettyPrintJSON(cmd, &eaclTable, "eACL")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
|
|
||||||
if containerJSON {
|
|
||||||
data, err = eaclTable.MarshalJSON()
|
|
||||||
commonCmd.ExitOnErr(cmd, "can't encode to JSON: %w", err)
|
|
||||||
} else {
|
|
||||||
data, err = eaclTable.Marshal()
|
|
||||||
commonCmd.ExitOnErr(cmd, "can't encode to binary: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Println("dumping data to file:", containerPathTo)
|
|
||||||
|
|
||||||
err = os.WriteFile(containerPathTo, data, 0o644)
|
|
||||||
commonCmd.ExitOnErr(cmd, "could not write eACL to file: %w", err)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func initContainerGetEACLCmd() {
|
|
||||||
commonflags.Init(getExtendedACLCmd)
|
|
||||||
|
|
||||||
flags := getExtendedACLCmd.Flags()
|
|
||||||
|
|
||||||
flags.StringVar(&containerID, commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
|
||||||
flags.StringVar(&containerPathTo, "to", "", "Path to dump encoded container (default: binary encoded)")
|
|
||||||
flags.BoolVar(&containerJSON, commonflags.JSON, false, "Encode EACL table in json format")
|
|
||||||
}
|
|
|
@ -1,9 +1,6 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
|
||||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
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/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
|
@ -70,7 +67,6 @@ var listContainersCmd = &cobra.Command{
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cnrID := cnrID
|
|
||||||
prmGet.ClientParams.ContainerID = &cnrID
|
prmGet.ClientParams.ContainerID = &cnrID
|
||||||
res, err := internalclient.GetContainer(cmd.Context(), prmGet)
|
res, err := internalclient.GetContainer(cmd.Context(), prmGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,12 +81,8 @@ var listContainersCmd = &cobra.Command{
|
||||||
cmd.Println(cnrID.String())
|
cmd.Println(cnrID.String())
|
||||||
|
|
||||||
if flagVarListPrintAttr {
|
if flagVarListPrintAttr {
|
||||||
cnr.IterateAttributes(func(key, val string) {
|
cnr.IterateUserAttributes(func(key, val string) {
|
||||||
if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
|
cmd.Printf(" %s: %s\n", key, val)
|
||||||
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97
|
|
||||||
// Use dedicated method to skip system attributes.
|
|
||||||
cmd.Printf(" %s: %s\n", key, val)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
|
||||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
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/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
|
@ -67,14 +64,8 @@ var listContainerObjectsCmd = &cobra.Command{
|
||||||
|
|
||||||
resHead, err := internalclient.HeadObject(cmd.Context(), prmHead)
|
resHead, err := internalclient.HeadObject(cmd.Context(), prmHead)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
attrs := resHead.Header().Attributes()
|
for _, attr := range resHead.Header().UserAttributes() {
|
||||||
for i := range attrs {
|
cmd.Printf(" %s: %s\n", attr.Key(), attr.Value())
|
||||||
attrKey := attrs[i].Key()
|
|
||||||
if !strings.HasPrefix(attrKey, v2object.SysAttributePrefix) && !strings.HasPrefix(attrKey, v2object.SysAttributePrefixNeoFS) {
|
|
||||||
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97
|
|
||||||
// Use dedicated method to skip system attributes.
|
|
||||||
cmd.Printf(" %s: %s\n", attrKey, attrs[i].Value())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cmd.Printf(" failed to read attributes: %v\n", err)
|
cmd.Printf(" failed to read attributes: %v\n", err)
|
||||||
|
|
|
@ -25,7 +25,6 @@ func init() {
|
||||||
deleteContainerCmd,
|
deleteContainerCmd,
|
||||||
listContainerObjectsCmd,
|
listContainerObjectsCmd,
|
||||||
getContainerInfoCmd,
|
getContainerInfoCmd,
|
||||||
getExtendedACLCmd,
|
|
||||||
containerNodesCmd,
|
containerNodesCmd,
|
||||||
policyPlaygroundCmd,
|
policyPlaygroundCmd,
|
||||||
}
|
}
|
||||||
|
@ -37,7 +36,6 @@ func init() {
|
||||||
initContainerDeleteCmd()
|
initContainerDeleteCmd()
|
||||||
initContainerListObjectsCmd()
|
initContainerListObjectsCmd()
|
||||||
initContainerInfoCmd()
|
initContainerInfoCmd()
|
||||||
initContainerGetEACLCmd()
|
|
||||||
initContainerNodesCmd()
|
initContainerNodesCmd()
|
||||||
initContainerPolicyPlaygroundCmd()
|
initContainerPolicyPlaygroundCmd()
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ const (
|
||||||
awaitFlag = "await"
|
awaitFlag = "await"
|
||||||
noProgressFlag = "no-progress"
|
noProgressFlag = "no-progress"
|
||||||
scopeFlag = "scope"
|
scopeFlag = "scope"
|
||||||
|
repOneOnlyFlag = "rep-one-only"
|
||||||
|
|
||||||
|
containerWorkerCountFlag = "container-worker-count"
|
||||||
|
objectWorkerCountFlag = "object-worker-count"
|
||||||
|
|
||||||
scopeAll = "all"
|
scopeAll = "all"
|
||||||
scopeObjects = "objects"
|
scopeObjects = "objects"
|
||||||
|
@ -64,12 +68,18 @@ func startEvacuateShard(cmd *cobra.Command, _ []string) {
|
||||||
pk := key.Get(cmd)
|
pk := key.Get(cmd)
|
||||||
|
|
||||||
ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag)
|
ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag)
|
||||||
|
containerWorkerCount, _ := cmd.Flags().GetUint32(containerWorkerCountFlag)
|
||||||
|
objectWorkerCount, _ := cmd.Flags().GetUint32(objectWorkerCountFlag)
|
||||||
|
repOneOnly, _ := cmd.Flags().GetBool(repOneOnlyFlag)
|
||||||
|
|
||||||
req := &control.StartShardEvacuationRequest{
|
req := &control.StartShardEvacuationRequest{
|
||||||
Body: &control.StartShardEvacuationRequest_Body{
|
Body: &control.StartShardEvacuationRequest_Body{
|
||||||
Shard_ID: getShardIDList(cmd),
|
Shard_ID: getShardIDList(cmd),
|
||||||
IgnoreErrors: ignoreErrors,
|
IgnoreErrors: ignoreErrors,
|
||||||
Scope: getEvacuationScope(cmd),
|
Scope: getEvacuationScope(cmd),
|
||||||
|
ContainerWorkerCount: containerWorkerCount,
|
||||||
|
ObjectWorkerCount: objectWorkerCount,
|
||||||
|
RepOneOnly: repOneOnly,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,6 +381,9 @@ func initControlStartEvacuationShardCmd() {
|
||||||
flags.String(scopeFlag, scopeAll, fmt.Sprintf("Evacuation scope; possible values: %s, %s, %s", scopeTrees, scopeObjects, scopeAll))
|
flags.String(scopeFlag, scopeAll, fmt.Sprintf("Evacuation scope; possible values: %s, %s, %s", scopeTrees, scopeObjects, scopeAll))
|
||||||
flags.Bool(awaitFlag, false, "Block execution until evacuation is completed")
|
flags.Bool(awaitFlag, false, "Block execution until evacuation is completed")
|
||||||
flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag))
|
flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag))
|
||||||
|
flags.Uint32(containerWorkerCountFlag, 0, "Count of concurrent container evacuation workers")
|
||||||
|
flags.Uint32(objectWorkerCountFlag, 0, "Count of concurrent object evacuation workers")
|
||||||
|
flags.Bool(repOneOnlyFlag, false, "Evacuate objects only from containers with policy 'REP 1 ...'")
|
||||||
|
|
||||||
startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
||||||
}
|
}
|
||||||
|
|
88
cmd/frostfs-cli/modules/control/rebuild_shards.go
Normal file
88
cmd/frostfs-cli/modules/control/rebuild_shards.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package control
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||||
|
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||||
|
"github.com/mr-tron/base58"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fillPercentFlag = "fill_percent"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shardsRebuildCmd = &cobra.Command{
|
||||||
|
Use: "rebuild",
|
||||||
|
Short: "Rebuild shards",
|
||||||
|
Long: "Rebuild reclaims storage occupied by dead objects and adjusts the storage structure according to the configuration (for blobovnicza only now)",
|
||||||
|
Run: shardsRebuild,
|
||||||
|
}
|
||||||
|
|
||||||
|
func shardsRebuild(cmd *cobra.Command, _ []string) {
|
||||||
|
pk := key.Get(cmd)
|
||||||
|
|
||||||
|
req := &control.StartShardRebuildRequest{
|
||||||
|
Body: &control.StartShardRebuildRequest_Body{
|
||||||
|
Shard_ID: getShardIDList(cmd),
|
||||||
|
TargetFillPercent: getFillPercentValue(cmd),
|
||||||
|
ConcurrencyLimit: getConcurrencyValue(cmd),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
signRequest(cmd, pk, req)
|
||||||
|
|
||||||
|
cli := getClient(cmd, pk)
|
||||||
|
|
||||||
|
var resp *control.StartShardRebuildResponse
|
||||||
|
var err error
|
||||||
|
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||||
|
resp, err = control.StartShardRebuild(client, req)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||||
|
|
||||||
|
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||||
|
|
||||||
|
var success, failed uint
|
||||||
|
for _, res := range resp.GetBody().GetResults() {
|
||||||
|
if res.GetSuccess() {
|
||||||
|
success++
|
||||||
|
cmd.Printf("Shard %s: OK\n", base58.Encode(res.GetShard_ID()))
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
cmd.Printf("Shard %s: failed with error %q\n", base58.Encode(res.GetShard_ID()), res.GetError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Printf("Total: %d success, %d failed\n", success, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFillPercentValue(cmd *cobra.Command) uint32 {
|
||||||
|
v, _ := cmd.Flags().GetUint32(fillPercentFlag)
|
||||||
|
if v <= 0 || v > 100 {
|
||||||
|
commonCmd.ExitOnErr(cmd, "invalid fill_percent value", fmt.Errorf("fill_percent value must be (0, 100], current value: %d", v))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConcurrencyValue(cmd *cobra.Command) uint32 {
|
||||||
|
v, _ := cmd.Flags().GetUint32(concurrencyFlag)
|
||||||
|
if v <= 0 || v > 10000 {
|
||||||
|
commonCmd.ExitOnErr(cmd, "invalid concurrency value", fmt.Errorf("concurrency value must be (0, 10 000], current value: %d", v))
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func initControlShardRebuildCmd() {
|
||||||
|
initControlFlags(shardsRebuildCmd)
|
||||||
|
|
||||||
|
flags := shardsRebuildCmd.Flags()
|
||||||
|
flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding")
|
||||||
|
flags.Bool(shardAllFlag, false, "Process all shards")
|
||||||
|
flags.Uint32(fillPercentFlag, 80, "Target fill percent to reclaim space")
|
||||||
|
flags.Uint32(concurrencyFlag, 20, "Maximum count of concurrently rebuilding files")
|
||||||
|
setShardModeCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
||||||
|
}
|
|
@ -84,7 +84,7 @@ func setNetmapStatus(cmd *cobra.Command, _ []string) {
|
||||||
body.SetStatus(control.NetmapStatus_MAINTENANCE)
|
body.SetStatus(control.NetmapStatus_MAINTENANCE)
|
||||||
|
|
||||||
if force {
|
if force {
|
||||||
body.SetForceMaintenance()
|
body.SetForceMaintenance(true)
|
||||||
common.PrintVerbose(cmd, "Local maintenance will be forced.")
|
common.PrintVerbose(cmd, "Local maintenance will be forced.")
|
||||||
}
|
}
|
||||||
targetStatus = control.NetmapStatus_MAINTENANCE
|
targetStatus = control.NetmapStatus_MAINTENANCE
|
||||||
|
|
|
@ -19,6 +19,7 @@ func initControlShardsCmd() {
|
||||||
shardsCmd.AddCommand(doctorCmd)
|
shardsCmd.AddCommand(doctorCmd)
|
||||||
shardsCmd.AddCommand(writecacheShardCmd)
|
shardsCmd.AddCommand(writecacheShardCmd)
|
||||||
shardsCmd.AddCommand(shardsDetachCmd)
|
shardsCmd.AddCommand(shardsDetachCmd)
|
||||||
|
shardsCmd.AddCommand(shardsRebuildCmd)
|
||||||
|
|
||||||
initControlShardsListCmd()
|
initControlShardsListCmd()
|
||||||
initControlSetShardModeCmd()
|
initControlSetShardModeCmd()
|
||||||
|
@ -28,4 +29,5 @@ func initControlShardsCmd() {
|
||||||
initControlDoctorCmd()
|
initControlDoctorCmd()
|
||||||
initControlShardsWritecacheCmd()
|
initControlShardsWritecacheCmd()
|
||||||
initControlShardsDetachCmd()
|
initControlShardsDetachCmd()
|
||||||
|
initControlShardRebuildCmd()
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,17 +61,18 @@ func listShards(cmd *cobra.Command, _ []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prettyPrintShardsJSON(cmd *cobra.Command, ii []*control.ShardInfo) {
|
func prettyPrintShardsJSON(cmd *cobra.Command, ii []control.ShardInfo) {
|
||||||
out := make([]map[string]any, 0, len(ii))
|
out := make([]map[string]any, 0, len(ii))
|
||||||
for _, i := range ii {
|
for _, i := range ii {
|
||||||
out = append(out, map[string]any{
|
out = append(out, map[string]any{
|
||||||
"shard_id": base58.Encode(i.GetShard_ID()),
|
"shard_id": base58.Encode(i.GetShard_ID()),
|
||||||
"mode": shardModeToString(i.GetMode()),
|
"mode": shardModeToString(i.GetMode()),
|
||||||
"metabase": i.GetMetabasePath(),
|
"metabase": i.GetMetabasePath(),
|
||||||
"blobstor": i.GetBlobstor(),
|
"blobstor": i.GetBlobstor(),
|
||||||
"writecache": i.GetWritecachePath(),
|
"writecache": i.GetWritecachePath(),
|
||||||
"pilorama": i.GetPiloramaPath(),
|
"pilorama": i.GetPiloramaPath(),
|
||||||
"error_count": i.GetErrorCount(),
|
"error_count": i.GetErrorCount(),
|
||||||
|
"evacuation_in_progress": i.GetEvacuationInProgress(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ func prettyPrintShardsJSON(cmd *cobra.Command, ii []*control.ShardInfo) {
|
||||||
cmd.Print(buf.String()) // pretty printer emits newline, so no need for Println
|
cmd.Print(buf.String()) // pretty printer emits newline, so no need for Println
|
||||||
}
|
}
|
||||||
|
|
||||||
func prettyPrintShards(cmd *cobra.Command, ii []*control.ShardInfo) {
|
func prettyPrintShards(cmd *cobra.Command, ii []control.ShardInfo) {
|
||||||
for _, i := range ii {
|
for _, i := range ii {
|
||||||
pathPrinter := func(name, path string) string {
|
pathPrinter := func(name, path string) string {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
@ -105,7 +106,8 @@ func prettyPrintShards(cmd *cobra.Command, ii []*control.ShardInfo) {
|
||||||
sb.String()+
|
sb.String()+
|
||||||
pathPrinter("Write-cache", i.GetWritecachePath())+
|
pathPrinter("Write-cache", i.GetWritecachePath())+
|
||||||
pathPrinter("Pilorama", i.GetPiloramaPath())+
|
pathPrinter("Pilorama", i.GetPiloramaPath())+
|
||||||
fmt.Sprintf("Error count: %d\n", i.GetErrorCount()),
|
fmt.Sprintf("Error count: %d\n", i.GetErrorCount())+
|
||||||
|
fmt.Sprintf("Evacuation in progress: %t\n", i.GetEvacuationInProgress()),
|
||||||
base58.Encode(i.GetShard_ID()),
|
base58.Encode(i.GetShard_ID()),
|
||||||
shardModeToString(i.GetMode()),
|
shardModeToString(i.GetMode()),
|
||||||
)
|
)
|
||||||
|
@ -121,7 +123,7 @@ func shardModeToString(m control.ShardMode) string {
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortShardsByID(ii []*control.ShardInfo) {
|
func sortShardsByID(ii []control.ShardInfo) {
|
||||||
sort.Slice(ii, func(i, j int) bool {
|
sort.Slice(ii, func(i, j int) bool {
|
||||||
return bytes.Compare(ii[i].GetShard_ID(), ii[j].GetShard_ID()) < 0
|
return bytes.Compare(ii[i].GetShard_ID(), ii[j].GetShard_ID()) < 0
|
||||||
})
|
})
|
||||||
|
|
|
@ -117,10 +117,10 @@ func setShardMode(cmd *cobra.Command, _ []string) {
|
||||||
req.SetBody(body)
|
req.SetBody(body)
|
||||||
|
|
||||||
body.SetMode(mode)
|
body.SetMode(mode)
|
||||||
body.SetShardIDList(getShardIDList(cmd))
|
body.SetShard_ID(getShardIDList(cmd))
|
||||||
|
|
||||||
reset, _ := cmd.Flags().GetBool(shardClearErrorsFlag)
|
reset, _ := cmd.Flags().GetBool(shardClearErrorsFlag)
|
||||||
body.ClearErrorCounter(reset)
|
body.SetResetErrorCounter(reset)
|
||||||
|
|
||||||
signRequest(cmd, pk, req)
|
signRequest(cmd, pk, req)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ func verifyResponse(cmd *cobra.Command,
|
||||||
GetSign() []byte
|
GetSign() []byte
|
||||||
},
|
},
|
||||||
body interface {
|
body interface {
|
||||||
StableMarshal([]byte) []byte
|
MarshalProtobuf([]byte) []byte
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if sigControl == nil {
|
if sigControl == nil {
|
||||||
|
@ -60,7 +60,7 @@ func verifyResponse(cmd *cobra.Command,
|
||||||
var sig frostfscrypto.Signature
|
var sig frostfscrypto.Signature
|
||||||
commonCmd.ExitOnErr(cmd, "can't read signature: %w", sig.ReadFromV2(sigV2))
|
commonCmd.ExitOnErr(cmd, "can't read signature: %w", sig.ReadFromV2(sigV2))
|
||||||
|
|
||||||
if !sig.Verify(body.StableMarshal(nil)) {
|
if !sig.Verify(body.MarshalProtobuf(nil)) {
|
||||||
commonCmd.ExitOnErr(cmd, "", errors.New("invalid response signature"))
|
commonCmd.ExitOnErr(cmd, "", errors.New("invalid response signature"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,14 +49,14 @@ func prettyPrintNodeInfo(cmd *cobra.Command, i netmap.NodeInfo) {
|
||||||
cmd.Println("key:", hex.EncodeToString(i.PublicKey()))
|
cmd.Println("key:", hex.EncodeToString(i.PublicKey()))
|
||||||
|
|
||||||
var stateWord string
|
var stateWord string
|
||||||
switch {
|
switch i.Status() {
|
||||||
default:
|
default:
|
||||||
stateWord = "<undefined>"
|
stateWord = "<undefined>"
|
||||||
case i.IsOnline():
|
case netmap.Online:
|
||||||
stateWord = "online"
|
stateWord = "online"
|
||||||
case i.IsOffline():
|
case netmap.Offline:
|
||||||
stateWord = "offline"
|
stateWord = "offline"
|
||||||
case i.IsMaintenance():
|
case netmap.Maintenance:
|
||||||
stateWord = "maintenance"
|
stateWord = "maintenance"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ func initObjectHeadCmd() {
|
||||||
_ = objectHeadCmd.MarkFlagRequired(commonflags.OIDFlag)
|
_ = objectHeadCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||||
|
|
||||||
flags.String(fileFlag, "", "File to write header to. Default: stdout.")
|
flags.String(fileFlag, "", "File to write header to. Default: stdout.")
|
||||||
flags.Bool("main-only", false, "Return only main fields")
|
|
||||||
flags.Bool(commonflags.JSON, false, "Marshal output in JSON")
|
flags.Bool(commonflags.JSON, false, "Marshal output in JSON")
|
||||||
flags.Bool("proto", false, "Marshal output in Protobuf")
|
flags.Bool("proto", false, "Marshal output in Protobuf")
|
||||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||||
|
@ -49,7 +48,6 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
||||||
var obj oid.ID
|
var obj oid.ID
|
||||||
|
|
||||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||||
mainOnly, _ := cmd.Flags().GetBool("main-only")
|
|
||||||
pk := key.GetOrGenerate(cmd)
|
pk := key.GetOrGenerate(cmd)
|
||||||
|
|
||||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||||
|
@ -62,7 +60,6 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
||||||
raw, _ := cmd.Flags().GetBool(rawFlag)
|
raw, _ := cmd.Flags().GetBool(rawFlag)
|
||||||
prm.SetRawFlag(raw)
|
prm.SetRawFlag(raw)
|
||||||
prm.SetAddress(objAddr)
|
prm.SetAddress(objAddr)
|
||||||
prm.SetMainOnlyFlag(mainOnly)
|
|
||||||
|
|
||||||
res, err := internalclient.HeadObject(cmd.Context(), prm)
|
res, err := internalclient.HeadObject(cmd.Context(), prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -172,7 +172,7 @@ func getComplexObjectParts(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *
|
||||||
func getCompexObjectMembers(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, prmHead internalclient.HeadObjectPrm, errSplitInfo *objectSDK.SplitInfoError) []oid.ID {
|
func getCompexObjectMembers(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, prmHead internalclient.HeadObjectPrm, errSplitInfo *objectSDK.SplitInfoError) []oid.ID {
|
||||||
splitInfo := errSplitInfo.SplitInfo()
|
splitInfo := errSplitInfo.SplitInfo()
|
||||||
|
|
||||||
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnrID, false); ok {
|
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnrID); ok {
|
||||||
return members
|
return members
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +185,7 @@ func getCompexObjectMembers(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli
|
||||||
|
|
||||||
func flattenComplexMembersIfECContainer(cmd *cobra.Command, cnrID cid.ID, members []oid.ID, prmHead internalclient.HeadObjectPrm) []phyObject {
|
func flattenComplexMembersIfECContainer(cmd *cobra.Command, cnrID cid.ID, members []oid.ID, prmHead internalclient.HeadObjectPrm) []phyObject {
|
||||||
result := make([]phyObject, 0, len(members))
|
result := make([]phyObject, 0, len(members))
|
||||||
|
var hasNonEC, hasEC bool
|
||||||
var resultGuard sync.Mutex
|
var resultGuard sync.Mutex
|
||||||
|
|
||||||
if len(members) == 0 {
|
if len(members) == 0 {
|
||||||
|
@ -193,31 +194,8 @@ func flattenComplexMembersIfECContainer(cmd *cobra.Command, cnrID cid.ID, member
|
||||||
|
|
||||||
prmHead.SetRawFlag(true) // to get an error instead of whole object
|
prmHead.SetRawFlag(true) // to get an error instead of whole object
|
||||||
|
|
||||||
first := members[0]
|
|
||||||
var addrObj oid.Address
|
|
||||||
addrObj.SetContainer(cnrID)
|
|
||||||
addrObj.SetObject(first)
|
|
||||||
prmHead.SetAddress(addrObj)
|
|
||||||
|
|
||||||
_, err := internalclient.HeadObject(cmd.Context(), prmHead)
|
|
||||||
var ecInfoError *objectSDK.ECInfoError
|
|
||||||
if errors.As(err, &ecInfoError) {
|
|
||||||
chunks := getECObjectChunks(cmd, cnrID, first, ecInfoError)
|
|
||||||
result = append(result, chunks...)
|
|
||||||
} else if err == nil { // not EC object, so all members must be phy objects
|
|
||||||
for _, member := range members {
|
|
||||||
result = append(result, phyObject{
|
|
||||||
containerID: cnrID,
|
|
||||||
objectID: member,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
commonCmd.ExitOnErr(cmd, "failed to flatten parts of complex object: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
eg, egCtx := errgroup.WithContext(cmd.Context())
|
eg, egCtx := errgroup.WithContext(cmd.Context())
|
||||||
for idx := 1; idx < len(members); idx++ {
|
for idx := range members {
|
||||||
partObjID := members[idx]
|
partObjID := members[idx]
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
|
@ -227,24 +205,44 @@ func flattenComplexMembersIfECContainer(cmd *cobra.Command, cnrID cid.ID, member
|
||||||
partAddr.SetObject(partObjID)
|
partAddr.SetObject(partObjID)
|
||||||
partHeadPrm.SetAddress(partAddr)
|
partHeadPrm.SetAddress(partAddr)
|
||||||
|
|
||||||
_, err := internalclient.HeadObject(egCtx, partHeadPrm)
|
obj, err := internalclient.HeadObject(egCtx, partHeadPrm)
|
||||||
var ecInfoError *objectSDK.ECInfoError
|
if err != nil {
|
||||||
if errors.As(err, &ecInfoError) {
|
var ecInfoError *objectSDK.ECInfoError
|
||||||
chunks := getECObjectChunks(cmd, cnrID, partObjID, ecInfoError)
|
if errors.As(err, &ecInfoError) {
|
||||||
|
resultGuard.Lock()
|
||||||
resultGuard.Lock()
|
defer resultGuard.Unlock()
|
||||||
defer resultGuard.Unlock()
|
result = append(result, getECObjectChunks(cmd, cnrID, partObjID, ecInfoError)...)
|
||||||
result = append(result, chunks...)
|
hasEC = true
|
||||||
|
return nil
|
||||||
return nil
|
}
|
||||||
} else if err == nil {
|
return err
|
||||||
return errMalformedComplexObject
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
if obj.Header().Type() != objectSDK.TypeRegular {
|
||||||
|
commonCmd.ExitOnErr(cmd, "failed to flatten parts of complex object: %w", fmt.Errorf("object '%s' with type '%s' is not supported as part of complex object", partAddr, obj.Header().Type()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.Header().Children()) > 0 {
|
||||||
|
// linking object is not data object, so skip it
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resultGuard.Lock()
|
||||||
|
defer resultGuard.Unlock()
|
||||||
|
result = append(result, phyObject{
|
||||||
|
containerID: cnrID,
|
||||||
|
objectID: partObjID,
|
||||||
|
})
|
||||||
|
hasNonEC = true
|
||||||
|
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
commonCmd.ExitOnErr(cmd, "failed to flatten parts of complex object: %w", eg.Wait())
|
commonCmd.ExitOnErr(cmd, "failed to flatten parts of complex object: %w", eg.Wait())
|
||||||
|
if hasEC && hasNonEC {
|
||||||
|
commonCmd.ExitOnErr(cmd, "failed to flatten parts of complex object: %w", errMalformedComplexObject)
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,8 +393,6 @@ func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, pk *ecdsa.
|
||||||
|
|
||||||
eg, egCtx := errgroup.WithContext(cmd.Context())
|
eg, egCtx := errgroup.WithContext(cmd.Context())
|
||||||
for _, cand := range candidates {
|
for _, cand := range candidates {
|
||||||
cand := cand
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
cli, err := createClient(egCtx, cmd, cand, pk)
|
cli, err := createClient(egCtx, cmd, cand, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -407,7 +403,6 @@ func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, pk *ecdsa.
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, object := range objects {
|
for _, object := range objects {
|
||||||
object := object
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
stored, err := isObjectStoredOnNode(egCtx, cmd, object.containerID, object.objectID, cli, pk)
|
stored, err := isObjectStoredOnNode(egCtx, cmd, object.containerID, object.objectID, cli, pk)
|
||||||
resultMtx.Lock()
|
resultMtx.Lock()
|
||||||
|
|
151
cmd/frostfs-cli/modules/object/patch.go
Normal file
151
cmd/frostfs-cli/modules/object/patch.go
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"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"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
newAttrsFlagName = "new-attrs"
|
||||||
|
replaceAttrsFlagName = "replace-attrs"
|
||||||
|
rangeFlagName = "range"
|
||||||
|
payloadFlagName = "payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
var objectPatchCmd = &cobra.Command{
|
||||||
|
Use: "patch",
|
||||||
|
Run: patch,
|
||||||
|
Short: "Patch FrostFS object",
|
||||||
|
Long: "Patch FrostFS object. Each range passed to the command requires to pass a corresponding patch payload.",
|
||||||
|
Example: `
|
||||||
|
frostfs-cli -c config.yml -r 127.0.0.1:8080 object patch --cid <CID> --oid <OID> --new-attrs 'key1=val1,key2=val2' --replace-attrs
|
||||||
|
frostfs-cli -c config.yml -r 127.0.0.1:8080 object patch --cid <CID> --oid <OID> --range offX:lnX --payload /path/to/payloadX --range offY:lnY --payload /path/to/payloadY
|
||||||
|
frostfs-cli -c config.yml -r 127.0.0.1:8080 object patch --cid <CID> --oid <OID> --new-attrs 'key1=val1,key2=val2' --replace-attrs --range offX:lnX --payload /path/to/payload
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func initObjectPatchCmd() {
|
||||||
|
commonflags.Init(objectPatchCmd)
|
||||||
|
initFlagSession(objectPatchCmd, "PATCH")
|
||||||
|
|
||||||
|
flags := objectPatchCmd.Flags()
|
||||||
|
|
||||||
|
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||||
|
_ = objectRangeCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||||
|
|
||||||
|
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||||
|
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||||
|
|
||||||
|
flags.String(newAttrsFlagName, "", "New object attributes in form of Key1=Value1,Key2=Value2")
|
||||||
|
flags.Bool(replaceAttrsFlagName, false, "Replace object attributes by new ones.")
|
||||||
|
flags.StringSlice(rangeFlagName, []string{}, "Range to which patch payload is applied. Format: offset:length")
|
||||||
|
flags.StringSlice(payloadFlagName, []string{}, "Path to file with patch payload.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func patch(cmd *cobra.Command, _ []string) {
|
||||||
|
var cnr cid.ID
|
||||||
|
var obj oid.ID
|
||||||
|
|
||||||
|
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||||
|
|
||||||
|
ranges, err := getRangeSlice(cmd)
|
||||||
|
commonCmd.ExitOnErr(cmd, "", err)
|
||||||
|
|
||||||
|
payloads := patchPayloadPaths(cmd)
|
||||||
|
|
||||||
|
if len(ranges) != len(payloads) {
|
||||||
|
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("the number of ranges and payloads are not equal: ranges = %d, payloads = %d", len(ranges), len(payloads)))
|
||||||
|
}
|
||||||
|
|
||||||
|
newAttrs, err := parseNewObjectAttrs(cmd)
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't parse new object attributes: %w", err)
|
||||||
|
replaceAttrs, _ := cmd.Flags().GetBool(replaceAttrsFlagName)
|
||||||
|
|
||||||
|
pk := key.GetOrGenerate(cmd)
|
||||||
|
|
||||||
|
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||||
|
|
||||||
|
var prm internalclient.PatchObjectPrm
|
||||||
|
prm.SetClient(cli)
|
||||||
|
Prepare(cmd, &prm)
|
||||||
|
ReadOrOpenSession(cmd, &prm, pk, cnr, nil)
|
||||||
|
|
||||||
|
prm.SetAddress(objAddr)
|
||||||
|
prm.NewAttributes = newAttrs
|
||||||
|
prm.ReplaceAttribute = replaceAttrs
|
||||||
|
|
||||||
|
for i := range ranges {
|
||||||
|
prm.PayloadPatches = append(prm.PayloadPatches, internalclient.PayloadPatch{
|
||||||
|
Range: ranges[i],
|
||||||
|
PayloadPath: payloads[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := internalclient.Patch(cmd.Context(), prm)
|
||||||
|
if err != nil {
|
||||||
|
commonCmd.ExitOnErr(cmd, "can't patch the object: %w", err)
|
||||||
|
}
|
||||||
|
cmd.Println("Patched object ID: ", res.OID.EncodeToString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNewObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||||
|
var rawAttrs []string
|
||||||
|
|
||||||
|
raw := cmd.Flag(newAttrsFlagName).Value.String()
|
||||||
|
if len(raw) != 0 {
|
||||||
|
rawAttrs = strings.Split(raw, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||||
|
for i := range rawAttrs {
|
||||||
|
k, v, found := strings.Cut(rawAttrs[i], "=")
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("invalid attribute format: %s", rawAttrs[i])
|
||||||
|
}
|
||||||
|
attrs[i].SetKey(k)
|
||||||
|
attrs[i].SetValue(v)
|
||||||
|
}
|
||||||
|
return attrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRangeSlice(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
||||||
|
v, _ := cmd.Flags().GetStringSlice(rangeFlagName)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return []objectSDK.Range{}, nil
|
||||||
|
}
|
||||||
|
rs := make([]objectSDK.Range, len(v))
|
||||||
|
for i := range v {
|
||||||
|
before, after, found := strings.Cut(v[i], rangeSep)
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("invalid range specifier: %s", v[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err := strconv.ParseUint(before, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid '%s' range offset specifier: %w", v[i], err)
|
||||||
|
}
|
||||||
|
length, err := strconv.ParseUint(after, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid '%s' range length specifier: %w", v[i], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs[i].SetOffset(offset)
|
||||||
|
rs[i].SetLength(length)
|
||||||
|
}
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func patchPayloadPaths(cmd *cobra.Command) []string {
|
||||||
|
v, _ := cmd.Flags().GetStringSlice(payloadFlagName)
|
||||||
|
return v
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ func init() {
|
||||||
objectRangeCmd,
|
objectRangeCmd,
|
||||||
objectLockCmd,
|
objectLockCmd,
|
||||||
objectNodesCmd,
|
objectNodesCmd,
|
||||||
|
objectPatchCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
Cmd.AddCommand(objectChildCommands...)
|
Cmd.AddCommand(objectChildCommands...)
|
||||||
|
@ -39,6 +40,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
initObjectPutCmd()
|
initObjectPutCmd()
|
||||||
|
initObjectPatchCmd()
|
||||||
initObjectDeleteCmd()
|
initObjectDeleteCmd()
|
||||||
initObjectGetCmd()
|
initObjectGetCmd()
|
||||||
initObjectSearchCmd()
|
initObjectSearchCmd()
|
||||||
|
|
|
@ -306,6 +306,8 @@ func finalizeSession(cmd *cobra.Command, dst SessionPrm, tok *session.Object, ke
|
||||||
case *internal.PutObjectPrm:
|
case *internal.PutObjectPrm:
|
||||||
common.PrintVerbose(cmd, "Binding session to object PUT...")
|
common.PrintVerbose(cmd, "Binding session to object PUT...")
|
||||||
tok.ForVerb(session.VerbObjectPut)
|
tok.ForVerb(session.VerbObjectPut)
|
||||||
|
case *internal.PatchObjectPrm:
|
||||||
|
tok.ForVerb(session.VerbObjectPatch)
|
||||||
case *internal.DeleteObjectPrm:
|
case *internal.DeleteObjectPrm:
|
||||||
common.PrintVerbose(cmd, "Binding session to object DELETE...")
|
common.PrintVerbose(cmd, "Binding session to object DELETE...")
|
||||||
tok.ForVerb(session.VerbObjectDelete)
|
tok.ForVerb(session.VerbObjectDelete)
|
||||||
|
@ -372,7 +374,7 @@ func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID,
|
||||||
common.PrintVerbose(cmd, "Split information received - object is virtual.")
|
common.PrintVerbose(cmd, "Split information received - object is virtual.")
|
||||||
splitInfo := errSplit.SplitInfo()
|
splitInfo := errSplit.SplitInfo()
|
||||||
|
|
||||||
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnr, true); ok {
|
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnr); ok {
|
||||||
return members
|
return members
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,7 +390,7 @@ func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, prmHead internal.HeadObjectPrm, cnr cid.ID, withLinking bool) ([]oid.ID, bool) {
|
func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, prmHead internal.HeadObjectPrm, cnr cid.ID) ([]oid.ID, bool) {
|
||||||
// collect split chain by the descending ease of operations (ease is evaluated heuristically).
|
// collect split chain by the descending ease of operations (ease is evaluated heuristically).
|
||||||
// If any approach fails, we don't try the next since we assume that it will fail too.
|
// If any approach fails, we don't try the next since we assume that it will fail too.
|
||||||
|
|
||||||
|
@ -409,10 +411,7 @@ func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *objectSDK.
|
||||||
|
|
||||||
common.PrintVerbose(cmd, "Received split members from the linking object: %v", children)
|
common.PrintVerbose(cmd, "Received split members from the linking object: %v", children)
|
||||||
|
|
||||||
if withLinking {
|
return append(children, idLinking), true
|
||||||
return append(children, idLinking), true
|
|
||||||
}
|
|
||||||
return children, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// linking object is not required for
|
// linking object is not required for
|
||||||
|
|
|
@ -114,12 +114,14 @@ func initConfig() {
|
||||||
} else {
|
} else {
|
||||||
// Find home directory.
|
// Find home directory.
|
||||||
home, err := homedir.Dir()
|
home, err := homedir.Dir()
|
||||||
commonCmd.ExitOnErr(rootCmd, "", err)
|
if err != nil {
|
||||||
|
common.PrintVerbose(rootCmd, "Get homedir: %s", err)
|
||||||
// Search config in `$HOME/.config/frostfs-cli/` with name "config.yaml"
|
} else {
|
||||||
viper.AddConfigPath(filepath.Join(home, ".config", "frostfs-cli"))
|
// Search config in `$HOME/.config/frostfs-cli/` with name "config.yaml"
|
||||||
viper.SetConfigName("config")
|
viper.AddConfigPath(filepath.Join(home, ".config", "frostfs-cli"))
|
||||||
viper.SetConfigType("yaml")
|
viper.SetConfigName("config")
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.SetEnvPrefix(envPrefix)
|
viper.SetEnvPrefix(envPrefix)
|
||||||
|
|
|
@ -30,8 +30,6 @@ func initAddCmd() {
|
||||||
ff := addCmd.Flags()
|
ff := addCmd.Flags()
|
||||||
ff.StringSlice(metaFlagKey, nil, "Meta pairs in the form of Key1=[0x]Value1,Key2=[0x]Value2")
|
ff.StringSlice(metaFlagKey, nil, "Meta pairs in the form of Key1=[0x]Value1,Key2=[0x]Value2")
|
||||||
ff.Uint64(parentIDFlagKey, 0, "Parent node ID")
|
ff.Uint64(parentIDFlagKey, 0, "Parent node ID")
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(cmd *cobra.Command, _ []string) {
|
func add(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -47,9 +45,10 @@ func add(cmd *cobra.Command, _ []string) {
|
||||||
meta, err := parseMeta(cmd)
|
meta, err := parseMeta(cmd)
|
||||||
commonCmd.ExitOnErr(cmd, "meta data parsing: %w", err)
|
commonCmd.ExitOnErr(cmd, "meta data parsing: %w", err)
|
||||||
|
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
@ -77,13 +76,13 @@ func add(cmd *cobra.Command, _ []string) {
|
||||||
cmd.Println("Node ID: ", resp.GetBody().GetNodeId())
|
cmd.Println("Node ID: ", resp.GetBody().GetNodeId())
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMeta(cmd *cobra.Command) ([]*tree.KeyValue, error) {
|
func parseMeta(cmd *cobra.Command) ([]tree.KeyValue, error) {
|
||||||
raws, _ := cmd.Flags().GetStringSlice(metaFlagKey)
|
raws, _ := cmd.Flags().GetStringSlice(metaFlagKey)
|
||||||
if len(raws) == 0 {
|
if len(raws) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := make([]*tree.KeyValue, 0, len(raws))
|
pairs := make([]tree.KeyValue, 0, len(raws))
|
||||||
for i := range raws {
|
for i := range raws {
|
||||||
k, v, found := strings.Cut(raws[i], "=")
|
k, v, found := strings.Cut(raws[i], "=")
|
||||||
if !found {
|
if !found {
|
||||||
|
@ -94,7 +93,7 @@ func parseMeta(cmd *cobra.Command) ([]*tree.KeyValue, error) {
|
||||||
pair.Key = k
|
pair.Key = k
|
||||||
pair.Value = []byte(v)
|
pair.Value = []byte(v)
|
||||||
|
|
||||||
pairs = append(pairs, &pair)
|
pairs = append(pairs, pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pairs, nil
|
return pairs, nil
|
||||||
|
|
|
@ -36,7 +36,6 @@ func initAddByPathCmd() {
|
||||||
ff.String(pathFlagKey, "", "Path to a node")
|
ff.String(pathFlagKey, "", "Path to a node")
|
||||||
ff.StringSlice(metaFlagKey, nil, "Meta pairs in the form of Key1=[0x]Value1,Key2=[0x]Value2")
|
ff.StringSlice(metaFlagKey, nil, "Meta pairs in the form of Key1=[0x]Value1,Key2=[0x]Value2")
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
_ = cobra.MarkFlagRequired(ff, pathFlagKey)
|
_ = cobra.MarkFlagRequired(ff, pathFlagKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,9 +49,10 @@ func addByPath(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
|
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
|
|
@ -2,14 +2,16 @@ package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
|
||||||
metrics "git.frostfs.info/TrueCloudLab/frostfs-observability/metrics/grpc"
|
metrics "git.frostfs.info/TrueCloudLab/frostfs-observability/metrics/grpc"
|
||||||
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
@ -17,15 +19,20 @@ import (
|
||||||
|
|
||||||
// _client returns grpc Tree service client. Should be removed
|
// _client returns grpc Tree service client. Should be removed
|
||||||
// after making Tree API public.
|
// after making Tree API public.
|
||||||
func _client(ctx context.Context) (tree.TreeServiceClient, error) {
|
func _client() (tree.TreeServiceClient, error) {
|
||||||
var netAddr network.Address
|
var netAddr network.Address
|
||||||
err := netAddr.FromString(viper.GetString(commonflags.RPC))
|
|
||||||
|
rpcEndpoint := viper.GetString(commonflags.RPC)
|
||||||
|
if rpcEndpoint == "" {
|
||||||
|
return nil, fmt.Errorf("%s is not defined", commonflags.RPC)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := netAddr.FromString(rpcEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []grpc.DialOption{
|
opts := []grpc.DialOption{
|
||||||
grpc.WithBlock(),
|
|
||||||
grpc.WithChainUnaryInterceptor(
|
grpc.WithChainUnaryInterceptor(
|
||||||
metrics.NewUnaryClientInterceptor(),
|
metrics.NewUnaryClientInterceptor(),
|
||||||
tracing.NewUnaryClientInteceptor(),
|
tracing.NewUnaryClientInteceptor(),
|
||||||
|
@ -34,18 +41,21 @@ func _client(ctx context.Context) (tree.TreeServiceClient, error) {
|
||||||
metrics.NewStreamClientInterceptor(),
|
metrics.NewStreamClientInterceptor(),
|
||||||
tracing.NewStreamClientInterceptor(),
|
tracing.NewStreamClientInterceptor(),
|
||||||
),
|
),
|
||||||
|
grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(netAddr.URIAddr(), "grpcs:") {
|
if !strings.HasPrefix(netAddr.URIAddr(), "grpcs:") {
|
||||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// a default connection establishing timeout
|
cc, err := grpc.NewClient(netAddr.URIAddr(), opts...)
|
||||||
const defaultClientConnectTimeout = time.Second * 2
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, defaultClientConnectTimeout)
|
|
||||||
cc, err := grpc.DialContext(ctx, netAddr.URIAddr(), opts...)
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
return tree.NewTreeServiceClient(cc), err
|
return tree.NewTreeServiceClient(cc), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contextWithTimeout(cmd *cobra.Command) (context.Context, context.CancelFunc) {
|
||||||
|
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
|
||||||
|
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
|
||||||
|
return context.WithTimeout(cmd.Context(), timeout)
|
||||||
|
}
|
||||||
|
return context.WithTimeout(cmd.Context(), commonflags.TimeoutDefault)
|
||||||
|
}
|
||||||
|
|
|
@ -36,8 +36,6 @@ func initGetByPathCmd() {
|
||||||
ff.String(pathFlagKey, "", "Path to a node")
|
ff.String(pathFlagKey, "", "Path to a node")
|
||||||
|
|
||||||
ff.Bool(latestOnlyFlagKey, false, "Look only for the latest version of a node")
|
ff.Bool(latestOnlyFlagKey, false, "Look only for the latest version of a node")
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getByPath(cmd *cobra.Command, _ []string) {
|
func getByPath(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -50,9 +48,10 @@ func getByPath(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
|
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
|
|
@ -30,8 +30,6 @@ func initGetOpLogCmd() {
|
||||||
ff := getOpLogCmd.Flags()
|
ff := getOpLogCmd.Flags()
|
||||||
ff.Uint64(heightFlagKey, 0, "Height to start with")
|
ff.Uint64(heightFlagKey, 0, "Height to start with")
|
||||||
ff.Uint64(countFlagKey, 10, "Logged operations count")
|
ff.Uint64(countFlagKey, 10, "Logged operations count")
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOpLog(cmd *cobra.Command, _ []string) {
|
func getOpLog(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -44,9 +42,10 @@ func getOpLog(cmd *cobra.Command, _ []string) {
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
|
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
|
|
@ -20,15 +20,14 @@ var healthcheckCmd = &cobra.Command{
|
||||||
|
|
||||||
func initHealthcheckCmd() {
|
func initHealthcheckCmd() {
|
||||||
commonflags.Init(healthcheckCmd)
|
commonflags.Init(healthcheckCmd)
|
||||||
ff := healthcheckCmd.Flags()
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func healthcheck(cmd *cobra.Command, _ []string) {
|
func healthcheck(cmd *cobra.Command, _ []string) {
|
||||||
pk := key.GetOrGenerate(cmd)
|
pk := key.GetOrGenerate(cmd)
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
req := &tree.HealthcheckRequest{
|
req := &tree.HealthcheckRequest{
|
||||||
|
|
|
@ -26,8 +26,6 @@ func initListCmd() {
|
||||||
ff := listCmd.Flags()
|
ff := listCmd.Flags()
|
||||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||||
_ = listCmd.MarkFlagRequired(commonflags.CIDFlag)
|
_ = listCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(cmd *cobra.Command, _ []string) {
|
func list(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -38,9 +36,10 @@ func list(cmd *cobra.Command, _ []string) {
|
||||||
err := cnr.DecodeString(cidString)
|
err := cnr.DecodeString(cidString)
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
|
|
@ -33,8 +33,6 @@ func initMoveCmd() {
|
||||||
|
|
||||||
_ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey)
|
_ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey)
|
||||||
_ = getSubtreeCmd.MarkFlagRequired(parentIDFlagKey)
|
_ = getSubtreeCmd.MarkFlagRequired(parentIDFlagKey)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func move(cmd *cobra.Command, _ []string) {
|
func move(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -45,9 +43,10 @@ func move(cmd *cobra.Command, _ []string) {
|
||||||
err := cnr.DecodeString(cidString)
|
err := cnr.DecodeString(cidString)
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
@ -75,7 +74,7 @@ func move(cmd *cobra.Command, _ []string) {
|
||||||
resp, err := cli.GetSubTree(ctx, subTreeReq)
|
resp, err := cli.GetSubTree(ctx, subTreeReq)
|
||||||
commonCmd.ExitOnErr(cmd, "rpc call: %w", err)
|
commonCmd.ExitOnErr(cmd, "rpc call: %w", err)
|
||||||
|
|
||||||
var meta []*tree.KeyValue
|
var meta []tree.KeyValue
|
||||||
subtreeResp, err := resp.Recv()
|
subtreeResp, err := resp.Recv()
|
||||||
for ; err == nil; subtreeResp, err = resp.Recv() {
|
for ; err == nil; subtreeResp, err = resp.Recv() {
|
||||||
meta = subtreeResp.GetBody().GetMeta()
|
meta = subtreeResp.GetBody().GetMeta()
|
||||||
|
|
|
@ -29,8 +29,6 @@ func initRemoveCmd() {
|
||||||
ff.Uint64(nodeIDFlagKey, 0, "Node ID.")
|
ff.Uint64(nodeIDFlagKey, 0, "Node ID.")
|
||||||
|
|
||||||
_ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey)
|
_ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(cmd *cobra.Command, _ []string) {
|
func remove(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -41,9 +39,10 @@ func remove(cmd *cobra.Command, _ []string) {
|
||||||
err := cnr.DecodeString(cidString)
|
err := cnr.DecodeString(cidString)
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
|
|
@ -49,6 +49,7 @@ const (
|
||||||
heightFlagKey = "height"
|
heightFlagKey = "height"
|
||||||
countFlagKey = "count"
|
countFlagKey = "count"
|
||||||
depthFlagKey = "depth"
|
depthFlagKey = "depth"
|
||||||
|
orderFlagKey = "ordered"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCTID(cmd *cobra.Command) {
|
func initCTID(cmd *cobra.Command) {
|
||||||
|
|
|
@ -30,11 +30,10 @@ func initGetSubtreeCmd() {
|
||||||
ff := getSubtreeCmd.Flags()
|
ff := getSubtreeCmd.Flags()
|
||||||
ff.Uint64(rootIDFlagKey, 0, "Root ID to traverse from.")
|
ff.Uint64(rootIDFlagKey, 0, "Root ID to traverse from.")
|
||||||
ff.Uint32(depthFlagKey, 10, "Traversal depth.")
|
ff.Uint32(depthFlagKey, 10, "Traversal depth.")
|
||||||
|
ff.Bool(orderFlagKey, false, "Sort output by ascending FileName.")
|
||||||
|
|
||||||
_ = getSubtreeCmd.MarkFlagRequired(commonflags.CIDFlag)
|
_ = getSubtreeCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||||
_ = getSubtreeCmd.MarkFlagRequired(treeIDFlagKey)
|
_ = getSubtreeCmd.MarkFlagRequired(treeIDFlagKey)
|
||||||
|
|
||||||
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSubTree(cmd *cobra.Command, _ []string) {
|
func getSubTree(cmd *cobra.Command, _ []string) {
|
||||||
|
@ -45,9 +44,10 @@ func getSubTree(cmd *cobra.Command, _ []string) {
|
||||||
err := cnr.DecodeString(cidString)
|
err := cnr.DecodeString(cidString)
|
||||||
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||||
|
|
||||||
ctx := cmd.Context()
|
ctx, cancel := contextWithTimeout(cmd)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
cli, err := _client(ctx)
|
cli, err := _client()
|
||||||
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
|
||||||
|
|
||||||
rawCID := make([]byte, sha256.Size)
|
rawCID := make([]byte, sha256.Size)
|
||||||
|
@ -59,6 +59,13 @@ func getSubTree(cmd *cobra.Command, _ []string) {
|
||||||
|
|
||||||
depth, _ := cmd.Flags().GetUint32(depthFlagKey)
|
depth, _ := cmd.Flags().GetUint32(depthFlagKey)
|
||||||
|
|
||||||
|
order, _ := cmd.Flags().GetBool(orderFlagKey)
|
||||||
|
|
||||||
|
bodyOrder := tree.GetSubTreeRequest_Body_Order_None
|
||||||
|
if order {
|
||||||
|
bodyOrder = tree.GetSubTreeRequest_Body_Order_Asc
|
||||||
|
}
|
||||||
|
|
||||||
var bt []byte
|
var bt []byte
|
||||||
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
|
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
|
||||||
bt = t.Marshal()
|
bt = t.Marshal()
|
||||||
|
@ -71,6 +78,9 @@ func getSubTree(cmd *cobra.Command, _ []string) {
|
||||||
RootId: []uint64{rid},
|
RootId: []uint64{rid},
|
||||||
Depth: depth,
|
Depth: depth,
|
||||||
BearerToken: bt,
|
BearerToken: bt,
|
||||||
|
OrderBy: &tree.GetSubTreeRequest_Body_Order{
|
||||||
|
Direction: bodyOrder,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ func PrettyPrintTableBACL(cmd *cobra.Command, bacl *acl.Basic) {
|
||||||
fmt.Fprintln(w, strings.Join(bits, "\t"))
|
fmt.Fprintln(w, strings.Join(bits, "\t"))
|
||||||
// Footer
|
// Footer
|
||||||
footer := []string{"X F"}
|
footer := []string{"X F"}
|
||||||
for i := 0; i < 7; i++ {
|
for range 7 {
|
||||||
footer = append(footer, "U S O B")
|
footer = append(footer, "U S O B")
|
||||||
}
|
}
|
||||||
fmt.Fprintln(w, strings.Join(footer, "\t"))
|
fmt.Fprintln(w, strings.Join(footer, "\t"))
|
||||||
|
|
|
@ -239,6 +239,8 @@ func parseAction(lexeme string) ([]string, bool, error) {
|
||||||
return []string{nativeschema.MethodRangeObject}, true, nil
|
return []string{nativeschema.MethodRangeObject}, true, nil
|
||||||
case "object.hash":
|
case "object.hash":
|
||||||
return []string{nativeschema.MethodHashObject}, true, nil
|
return []string{nativeschema.MethodHashObject}, true, nil
|
||||||
|
case "object.patch":
|
||||||
|
return []string{nativeschema.MethodPatchObject}, true, nil
|
||||||
case "object.*":
|
case "object.*":
|
||||||
return []string{
|
return []string{
|
||||||
nativeschema.MethodPutObject,
|
nativeschema.MethodPutObject,
|
||||||
|
@ -248,6 +250,7 @@ func parseAction(lexeme string) ([]string, bool, error) {
|
||||||
nativeschema.MethodSearchObject,
|
nativeschema.MethodSearchObject,
|
||||||
nativeschema.MethodRangeObject,
|
nativeschema.MethodRangeObject,
|
||||||
nativeschema.MethodHashObject,
|
nativeschema.MethodHashObject,
|
||||||
|
nativeschema.MethodPatchObject,
|
||||||
}, true, nil
|
}, true, nil
|
||||||
case "container.put":
|
case "container.put":
|
||||||
return []string{nativeschema.MethodPutContainer}, false, nil
|
return []string{nativeschema.MethodPutContainer}, false, nil
|
||||||
|
@ -255,10 +258,6 @@ func parseAction(lexeme string) ([]string, bool, error) {
|
||||||
return []string{nativeschema.MethodDeleteContainer}, false, nil
|
return []string{nativeschema.MethodDeleteContainer}, false, nil
|
||||||
case "container.get":
|
case "container.get":
|
||||||
return []string{nativeschema.MethodGetContainer}, false, nil
|
return []string{nativeschema.MethodGetContainer}, false, nil
|
||||||
case "container.setcontainereacl":
|
|
||||||
return []string{nativeschema.MethodSetContainerEACL}, false, nil
|
|
||||||
case "container.getcontainereacl":
|
|
||||||
return []string{nativeschema.MethodGetContainerEACL}, false, nil
|
|
||||||
case "container.list":
|
case "container.list":
|
||||||
return []string{nativeschema.MethodListContainers}, false, nil
|
return []string{nativeschema.MethodListContainers}, false, nil
|
||||||
case "container.*":
|
case "container.*":
|
||||||
|
@ -266,8 +265,6 @@ func parseAction(lexeme string) ([]string, bool, error) {
|
||||||
nativeschema.MethodPutContainer,
|
nativeschema.MethodPutContainer,
|
||||||
nativeschema.MethodDeleteContainer,
|
nativeschema.MethodDeleteContainer,
|
||||||
nativeschema.MethodGetContainer,
|
nativeschema.MethodGetContainer,
|
||||||
nativeschema.MethodSetContainerEACL,
|
|
||||||
nativeschema.MethodGetContainerEACL,
|
|
||||||
nativeschema.MethodListContainers,
|
nativeschema.MethodListContainers,
|
||||||
}, false, nil
|
}, false, nil
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -41,6 +41,8 @@ func reloadConfig() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logPrm.PrependTimestamp = cfg.GetBool("logger.timestamp")
|
||||||
|
|
||||||
return logPrm.Reload()
|
return logPrm.Reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
func defaultConfiguration(cfg *viper.Viper) {
|
func defaultConfiguration(cfg *viper.Viper) {
|
||||||
cfg.SetDefault("logger.level", "info")
|
cfg.SetDefault("logger.level", "info")
|
||||||
cfg.SetDefault("logger.destination", "stdout")
|
cfg.SetDefault("logger.destination", "stdout")
|
||||||
|
cfg.SetDefault("logger.timestamp", false)
|
||||||
|
|
||||||
setPprofDefaults(cfg)
|
setPprofDefaults(cfg)
|
||||||
|
|
||||||
|
@ -47,6 +48,8 @@ func defaultConfiguration(cfg *viper.Viper) {
|
||||||
cfg.SetDefault("node.kludge_compatibility_mode", false)
|
cfg.SetDefault("node.kludge_compatibility_mode", false)
|
||||||
|
|
||||||
cfg.SetDefault("audit.enabled", false)
|
cfg.SetDefault("audit.enabled", false)
|
||||||
|
|
||||||
|
setMultinetDefaults(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setControlDefaults(cfg *viper.Viper) {
|
func setControlDefaults(cfg *viper.Viper) {
|
||||||
|
@ -130,3 +133,11 @@ func setMorphDefaults(cfg *viper.Viper) {
|
||||||
cfg.SetDefault("morph.validators", []string{})
|
cfg.SetDefault("morph.validators", []string{})
|
||||||
cfg.SetDefault("morph.switch_interval", 2*time.Minute)
|
cfg.SetDefault("morph.switch_interval", 2*time.Minute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setMultinetDefaults(cfg *viper.Viper) {
|
||||||
|
cfg.SetDefault("multinet.enabled", false)
|
||||||
|
cfg.SetDefault("multinet.balancer", "")
|
||||||
|
cfg.SetDefault("multinet.restrict", false)
|
||||||
|
cfg.SetDefault("multinet.fallback_delay", "0s")
|
||||||
|
cfg.SetDefault("multinet.subnets", "")
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sdnotify"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -78,6 +79,8 @@ func main() {
|
||||||
)
|
)
|
||||||
exitErr(err)
|
exitErr(err)
|
||||||
logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook()
|
logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook()
|
||||||
|
logPrm.PrependTimestamp = cfg.GetBool("logger.timestamp")
|
||||||
|
|
||||||
log, err = logger.NewLogger(logPrm)
|
log, err = logger.NewLogger(logPrm)
|
||||||
exitErr(err)
|
exitErr(err)
|
||||||
|
|
||||||
|
@ -124,4 +127,8 @@ func shutdown() {
|
||||||
zap.String("error", err.Error()),
|
zap.String("error", err.Error()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := sdnotify.ClearStatus(); err != nil {
|
||||||
|
log.Error(logs.FailedToReportStatusToSystemd, zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ var Root = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Root.AddCommand(listCMD, inspectCMD)
|
Root.AddCommand(listCMD, inspectCMD, tuiCMD)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openBlobovnicza(cmd *cobra.Command) *blobovnicza.Blobovnicza {
|
func openBlobovnicza(cmd *cobra.Command) *blobovnicza.Blobovnicza {
|
||||||
|
|
68
cmd/frostfs-lens/internal/blobovnicza/tui.go
Normal file
68
cmd/frostfs-lens/internal/blobovnicza/tui.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package blobovnicza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
||||||
|
schema "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/blobovnicza"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tuiCMD = &cobra.Command{
|
||||||
|
Use: "explore",
|
||||||
|
Short: "Blobovnicza exploration with a terminal UI",
|
||||||
|
Long: `Launch a terminal UI to explore blobovnicza and search for data.
|
||||||
|
|
||||||
|
Available search filters:
|
||||||
|
- cid CID
|
||||||
|
- oid OID
|
||||||
|
- addr CID/OID
|
||||||
|
`,
|
||||||
|
Run: tuiFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialPrompt string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.AddComponentPathFlag(tuiCMD, &vPath)
|
||||||
|
|
||||||
|
tuiCMD.Flags().StringVar(
|
||||||
|
&initialPrompt,
|
||||||
|
"filter",
|
||||||
|
"",
|
||||||
|
"Filter prompt to start with, format 'tag:value [+ tag:value]...'",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuiFunc(cmd *cobra.Command, _ []string) {
|
||||||
|
common.ExitOnErr(cmd, runTUI(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTUI(cmd *cobra.Command) error {
|
||||||
|
db, err := tui.OpenDB(vPath, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't open database: %w", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
app := tview.NewApplication()
|
||||||
|
ui := tui.NewUI(ctx, app, db, schema.BlobovniczaParser, nil)
|
||||||
|
|
||||||
|
_ = ui.AddFilter("cid", tui.CIDParser, "CID")
|
||||||
|
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
||||||
|
_ = ui.AddCompositeFilter("addr", tui.AddressParser, "CID/OID")
|
||||||
|
|
||||||
|
err = ui.WithPrompt(initialPrompt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid filter prompt: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.SetRoot(ui, true).SetFocus(ui)
|
||||||
|
return app.Run()
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ func init() {
|
||||||
inspectCMD,
|
inspectCMD,
|
||||||
listGraveyardCMD,
|
listGraveyardCMD,
|
||||||
listGarbageCMD,
|
listGarbageCMD,
|
||||||
|
tuiCMD,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
71
cmd/frostfs-lens/internal/meta/tui.go
Normal file
71
cmd/frostfs-lens/internal/meta/tui.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package meta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
||||||
|
schema "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tuiCMD = &cobra.Command{
|
||||||
|
Use: "explore",
|
||||||
|
Short: "Metabase exploration with a terminal UI",
|
||||||
|
Long: `Launch a terminal UI to explore metabase and search for data.
|
||||||
|
|
||||||
|
Available search filters:
|
||||||
|
- cid CID
|
||||||
|
- oid OID
|
||||||
|
- addr CID/OID
|
||||||
|
- attr key[/value]
|
||||||
|
`,
|
||||||
|
Run: tuiFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialPrompt string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
common.AddComponentPathFlag(tuiCMD, &vPath)
|
||||||
|
|
||||||
|
tuiCMD.Flags().StringVar(
|
||||||
|
&initialPrompt,
|
||||||
|
"filter",
|
||||||
|
"",
|
||||||
|
"Filter prompt to start with, format 'tag:value [+ tag:value]...'",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuiFunc(cmd *cobra.Command, _ []string) {
|
||||||
|
common.ExitOnErr(cmd, runTUI(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTUI(cmd *cobra.Command) error {
|
||||||
|
db, err := tui.OpenDB(vPath, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't open database: %w", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Need if app was stopped with Ctrl-C.
|
||||||
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
app := tview.NewApplication()
|
||||||
|
ui := tui.NewUI(ctx, app, db, schema.MetabaseParser, nil)
|
||||||
|
|
||||||
|
_ = ui.AddFilter("cid", tui.CIDParser, "CID")
|
||||||
|
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
||||||
|
_ = ui.AddCompositeFilter("addr", tui.AddressParser, "CID/OID")
|
||||||
|
_ = ui.AddCompositeFilter("attr", tui.AttributeParser, "key[/value]")
|
||||||
|
|
||||||
|
err = ui.WithPrompt(initialPrompt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid filter prompt: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.SetRoot(ui, true).SetFocus(ui)
|
||||||
|
return app.Run()
|
||||||
|
}
|
96
cmd/frostfs-lens/internal/schema/blobovnicza/parsers.go
Normal file
96
cmd/frostfs-lens/internal/schema/blobovnicza/parsers.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package blobovnicza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/mr-tron/base58"
|
||||||
|
)
|
||||||
|
|
||||||
|
var BlobovniczaParser = common.WithFallback(
|
||||||
|
common.Any(
|
||||||
|
MetaBucketParser,
|
||||||
|
BucketParser,
|
||||||
|
),
|
||||||
|
common.RawParser.ToFallbackParser(),
|
||||||
|
)
|
||||||
|
|
||||||
|
func MetaBucketParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, errors.New("not a bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(key) != "META" {
|
||||||
|
return nil, nil, errors.New("invalid bucket name")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MetaBucket{}, MetaRecordParser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetaRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var r MetaRecord
|
||||||
|
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil, nil, errors.New("invalid key")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.label = string(key)
|
||||||
|
r.count = binary.LittleEndian.Uint64(value)
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BucketParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, errors.New("not a bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
size, n := binary.Varint(key)
|
||||||
|
if n <= 0 {
|
||||||
|
return nil, nil, errors.New("invalid size")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bucket{size: size}, RecordParser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
parts := strings.Split(string(key), "/")
|
||||||
|
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, nil, errors.New("invalid key, expected address string <CID>/<OID>")
|
||||||
|
}
|
||||||
|
|
||||||
|
cnrRaw, err := base58.Decode(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.New("can't decode CID string")
|
||||||
|
}
|
||||||
|
objRaw, err := base58.Decode(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.New("can't decode OID string")
|
||||||
|
}
|
||||||
|
|
||||||
|
cnr := cid.ID{}
|
||||||
|
if err := cnr.Decode(cnrRaw); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("can't decode CID: %w", err)
|
||||||
|
}
|
||||||
|
obj := oid.ID{}
|
||||||
|
if err := obj.Decode(objRaw); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("can't decode OID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r Record
|
||||||
|
|
||||||
|
r.addr.SetContainer(cnr)
|
||||||
|
r.addr.SetObject(obj)
|
||||||
|
|
||||||
|
if err := r.object.Unmarshal(value); err != nil {
|
||||||
|
return nil, nil, errors.New("can't unmarshal object")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
101
cmd/frostfs-lens/internal/schema/blobovnicza/types.go
Normal file
101
cmd/frostfs-lens/internal/schema/blobovnicza/types.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package blobovnicza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
MetaBucket struct{}
|
||||||
|
|
||||||
|
MetaRecord struct {
|
||||||
|
label string
|
||||||
|
count uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
Bucket struct {
|
||||||
|
size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
Record struct {
|
||||||
|
addr oid.Address
|
||||||
|
object objectSDK.Object
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *MetaBucket) String() string {
|
||||||
|
return common.FormatSimple("META", tcell.ColorLime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MetaBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MetaBucket) Filter(string, any) common.FilterResult {
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MetaRecord) String() string {
|
||||||
|
return fmt.Sprintf("%-11s %c %d", r.label, tview.Borders.Vertical, r.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MetaRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MetaRecord) Filter(string, any) common.FilterResult {
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bucket) String() string {
|
||||||
|
return common.FormatSimple(strconv.FormatInt(b.size, 10), tcell.ColorLime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bucket) Filter(typ string, _ any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
return common.Maybe
|
||||||
|
case "oid":
|
||||||
|
return common.Maybe
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"CID %s OID %s %c Object {...}",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.addr.Container()), tcell.ColorAqua),
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.addr.Object()), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Record) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(r.addr.Container().Equals(id), common.Yes, common.No)
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.addr.Object().Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
43
cmd/frostfs-lens/internal/schema/common/format.go
Normal file
43
cmd/frostfs-lens/internal/schema/common/format.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FormatOptions struct {
|
||||||
|
Color tcell.Color
|
||||||
|
|
||||||
|
Bold,
|
||||||
|
Italic,
|
||||||
|
Underline,
|
||||||
|
StrikeThrough bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Format(s string, opts FormatOptions) string {
|
||||||
|
var boldTag, italicTag, underlineTag, strikeThroughTag string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case opts.Bold:
|
||||||
|
boldTag = "b"
|
||||||
|
case opts.Italic:
|
||||||
|
italicTag = "i"
|
||||||
|
case opts.Underline:
|
||||||
|
underlineTag = "u"
|
||||||
|
case opts.StrikeThrough:
|
||||||
|
strikeThroughTag = "s"
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := fmt.Sprintf(
|
||||||
|
"%s%s%s%s", boldTag, italicTag, underlineTag, strikeThroughTag,
|
||||||
|
)
|
||||||
|
color := strconv.FormatInt(int64(opts.Color.Hex()), 16)
|
||||||
|
|
||||||
|
return fmt.Sprintf("[#%06s::%s]%s[-::-]", color, attrs, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatSimple(s string, c tcell.Color) string {
|
||||||
|
return Format(s, FormatOptions{Color: c})
|
||||||
|
}
|
31
cmd/frostfs-lens/internal/schema/common/raw.go
Normal file
31
cmd/frostfs-lens/internal/schema/common/raw.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/mr-tron/base58"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawEntry struct {
|
||||||
|
// key and value used for record dump.
|
||||||
|
// nolint:unused
|
||||||
|
key, value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var RawParser Parser = rawParser
|
||||||
|
|
||||||
|
func rawParser(key, value []byte) (SchemaEntry, Parser, error) {
|
||||||
|
return &RawEntry{key: key, value: value}, rawParser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawEntry) String() string {
|
||||||
|
return FormatSimple(base58.Encode(r.key), tcell.ColorRed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawEntry) DetailedString() string {
|
||||||
|
return spew.Sdump(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawEntry) Filter(string, any) FilterResult {
|
||||||
|
return No
|
||||||
|
}
|
81
cmd/frostfs-lens/internal/schema/common/schema.go
Normal file
81
cmd/frostfs-lens/internal/schema/common/schema.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilterResult byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
No FilterResult = iota
|
||||||
|
Maybe
|
||||||
|
Yes
|
||||||
|
)
|
||||||
|
|
||||||
|
func IfThenElse(condition bool, onSuccess, onFailure FilterResult) FilterResult {
|
||||||
|
var res FilterResult
|
||||||
|
if condition {
|
||||||
|
res = onSuccess
|
||||||
|
} else {
|
||||||
|
res = onFailure
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchemaEntry interface {
|
||||||
|
String() string
|
||||||
|
DetailedString() string
|
||||||
|
Filter(typ string, val any) FilterResult
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Parser func(key, value []byte) (SchemaEntry, Parser, error)
|
||||||
|
FallbackParser func(key, value []byte) (SchemaEntry, Parser)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Any(parsers ...Parser) Parser {
|
||||||
|
return func(key, value []byte) (SchemaEntry, Parser, error) {
|
||||||
|
var errs error
|
||||||
|
for _, parser := range parsers {
|
||||||
|
ret, next, err := parser(key, value)
|
||||||
|
if err == nil {
|
||||||
|
return ret, next, nil
|
||||||
|
}
|
||||||
|
errs = errors.Join(errs, err)
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("no parser succeeded: %w", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFallback(parser Parser, fallback FallbackParser) Parser {
|
||||||
|
if parser == nil {
|
||||||
|
return fallback.ToParser()
|
||||||
|
}
|
||||||
|
return func(key, value []byte) (SchemaEntry, Parser, error) {
|
||||||
|
entry, next, err := parser(key, value)
|
||||||
|
if err == nil {
|
||||||
|
return entry, WithFallback(next, fallback), nil
|
||||||
|
}
|
||||||
|
return fallback.ToParser()(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp FallbackParser) ToParser() Parser {
|
||||||
|
return func(key, value []byte) (SchemaEntry, Parser, error) {
|
||||||
|
entry, next := fp(key, value)
|
||||||
|
return entry, next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Parser) ToFallbackParser() FallbackParser {
|
||||||
|
return func(key, value []byte) (SchemaEntry, Parser) {
|
||||||
|
entry, next, err := p(key, value)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf(
|
||||||
|
"couldn't use that parser as a fallback parser, it returned an error: %w", err,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return entry, next
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package buckets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *PrefixBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PrefixContainerBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ContainerBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserAttributeKeyBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserAttributeValueBucket) DetailedString() string {
|
||||||
|
return spew.Sdump(*b)
|
||||||
|
}
|
81
cmd/frostfs-lens/internal/schema/metabase/buckets/filter.go
Normal file
81
cmd/frostfs-lens/internal/schema/metabase/buckets/filter.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package buckets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *PrefixBucket) Filter(typ string, _ any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
return b.resolvers.cidResolver(false)
|
||||||
|
case "oid":
|
||||||
|
return b.resolvers.oidResolver(false)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PrefixContainerBucket) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return b.resolvers.cidResolver(b.id.Equals(id))
|
||||||
|
case "oid":
|
||||||
|
return b.resolvers.oidResolver(false)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserBucket) Filter(typ string, _ any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
return b.resolvers.cidResolver(false)
|
||||||
|
case "oid":
|
||||||
|
return b.resolvers.oidResolver(false)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ContainerBucket) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return b.resolvers.cidResolver(b.id.Equals(id))
|
||||||
|
case "oid":
|
||||||
|
return b.resolvers.oidResolver(false)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserAttributeKeyBucket) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(b.id.Equals(id), common.Yes, common.No)
|
||||||
|
case "oid":
|
||||||
|
return common.Maybe
|
||||||
|
case "key":
|
||||||
|
key := val.(string)
|
||||||
|
return common.IfThenElse(b.key == key, common.Yes, common.No)
|
||||||
|
case "value":
|
||||||
|
return common.Maybe
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserAttributeValueBucket) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
return common.Maybe
|
||||||
|
case "value":
|
||||||
|
value := val.(string)
|
||||||
|
return common.IfThenElse(b.value == value, common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
111
cmd/frostfs-lens/internal/schema/metabase/buckets/parsers.go
Normal file
111
cmd/frostfs-lens/internal/schema/metabase/buckets/parsers.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package buckets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/records"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
GraveyardParser = NewPrefixBucketParser(Graveyard, records.GraveyardRecordParser, Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
GarbageParser = NewPrefixBucketParser(Garbage, records.GarbageRecordParser, Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
ContainerVolumeParser = NewPrefixBucketParser(ContainerVolume, records.ContainerVolumeRecordParser, Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: StrictResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
LockedParser = NewPrefixBucketParser(
|
||||||
|
Locked,
|
||||||
|
NewContainerBucketParser(
|
||||||
|
records.LockedRecordParser,
|
||||||
|
Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ShardInfoParser = NewPrefixBucketParser(ShardInfo, records.ShardInfoRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: StrictResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
PrimaryParser = NewPrefixContainerBucketParser(Primary, records.ObjectRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
LockersParser = NewPrefixContainerBucketParser(Lockers, records.ObjectRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
TombstoneParser = NewPrefixContainerBucketParser(Tombstone, records.ObjectRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
SmallParser = NewPrefixContainerBucketParser(Small, records.SmallRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
RootParser = NewPrefixContainerBucketParser(Root, records.RootRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
OwnerParser = NewPrefixContainerBucketParser(
|
||||||
|
Owner,
|
||||||
|
NewUserBucketParser(
|
||||||
|
records.OwnerRecordParser,
|
||||||
|
Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
UserAttributeParser = NewUserAttributeKeyBucketParser(
|
||||||
|
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||||
|
)
|
||||||
|
|
||||||
|
PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: StrictResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
ParentParser = NewPrefixContainerBucketParser(Parent, records.ParentRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
SplitParser = NewPrefixContainerBucketParser(Split, records.SplitRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: StrictResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
ContainerCountersParser = NewPrefixBucketParser(ContainerCounters, records.ContainerCountersRecordParser, Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: StrictResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
ECInfoParser = NewPrefixContainerBucketParser(ECInfo, records.ECInfoRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
)
|
53
cmd/frostfs-lens/internal/schema/metabase/buckets/prefix.go
Normal file
53
cmd/frostfs-lens/internal/schema/metabase/buckets/prefix.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package buckets
|
||||||
|
|
||||||
|
type Prefix byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
Graveyard Prefix = iota
|
||||||
|
Garbage
|
||||||
|
ToMoveIt
|
||||||
|
ContainerVolume
|
||||||
|
Locked
|
||||||
|
ShardInfo
|
||||||
|
Primary
|
||||||
|
Lockers
|
||||||
|
_
|
||||||
|
Tombstone
|
||||||
|
Small
|
||||||
|
Root
|
||||||
|
Owner
|
||||||
|
UserAttribute
|
||||||
|
PayloadHash
|
||||||
|
Parent
|
||||||
|
Split
|
||||||
|
ContainerCounters
|
||||||
|
ECInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
var x = map[Prefix]string{
|
||||||
|
Graveyard: "Graveyard",
|
||||||
|
Garbage: "Garbage",
|
||||||
|
ToMoveIt: "To Move It",
|
||||||
|
ContainerVolume: "Container Volume",
|
||||||
|
Locked: "Locked",
|
||||||
|
ShardInfo: "Shard Info",
|
||||||
|
Primary: "Primary",
|
||||||
|
Lockers: "Lockers",
|
||||||
|
Tombstone: "Tombstone",
|
||||||
|
Small: "Small",
|
||||||
|
Root: "Root",
|
||||||
|
Owner: "Owner",
|
||||||
|
UserAttribute: "User Attribute",
|
||||||
|
PayloadHash: "Payload Hash",
|
||||||
|
Parent: "Parent",
|
||||||
|
Split: "Split",
|
||||||
|
ContainerCounters: "Container Counters",
|
||||||
|
ECInfo: "EC Info",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Prefix) String() string {
|
||||||
|
if s, ok := x[p]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return "Unknown Prefix"
|
||||||
|
}
|
48
cmd/frostfs-lens/internal/schema/metabase/buckets/string.go
Normal file
48
cmd/frostfs-lens/internal/schema/metabase/buckets/string.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package buckets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *PrefixBucket) String() string {
|
||||||
|
return common.FormatSimple(
|
||||||
|
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *PrefixContainerBucket) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s CID %s",
|
||||||
|
common.FormatSimple(
|
||||||
|
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
|
),
|
||||||
|
common.FormatSimple(b.id.String(), tcell.ColorAqua),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserBucket) String() string {
|
||||||
|
return "UID " + common.FormatSimple(b.id.String(), tcell.ColorAqua)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ContainerBucket) String() string {
|
||||||
|
return "CID " + common.FormatSimple(b.id.String(), tcell.ColorAqua)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserAttributeKeyBucket) String() string {
|
||||||
|
return fmt.Sprintf("%s CID %s ATTR-KEY %s",
|
||||||
|
common.FormatSimple(
|
||||||
|
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
|
),
|
||||||
|
common.FormatSimple(
|
||||||
|
fmt.Sprintf("%-44s", b.id), tcell.ColorAqua,
|
||||||
|
),
|
||||||
|
common.FormatSimple(b.key, tcell.ColorAqua),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *UserAttributeValueBucket) String() string {
|
||||||
|
return "ATTR-VALUE " + common.FormatSimple(b.value, tcell.ColorAqua)
|
||||||
|
}
|
166
cmd/frostfs-lens/internal/schema/metabase/buckets/types.go
Normal file
166
cmd/frostfs-lens/internal/schema/metabase/buckets/types.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package buckets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
"github.com/mr-tron/base58"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
PrefixBucket struct {
|
||||||
|
prefix Prefix
|
||||||
|
resolvers Resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
PrefixContainerBucket struct {
|
||||||
|
prefix Prefix
|
||||||
|
id cid.ID
|
||||||
|
resolvers Resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerBucket struct {
|
||||||
|
id cid.ID
|
||||||
|
resolvers Resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
UserBucket struct {
|
||||||
|
id user.ID
|
||||||
|
resolvers Resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAttributeKeyBucket struct {
|
||||||
|
prefix Prefix
|
||||||
|
id cid.ID
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAttributeValueBucket struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
FilterResolver = func(result bool) common.FilterResult
|
||||||
|
|
||||||
|
Resolvers struct {
|
||||||
|
cidResolver FilterResolver
|
||||||
|
oidResolver FilterResolver
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
StrictResolver = func(x bool) common.FilterResult { return common.IfThenElse(x, common.Yes, common.No) }
|
||||||
|
LenientResolver = func(x bool) common.FilterResult { return common.IfThenElse(x, common.Yes, common.Maybe) }
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotBucket = errors.New("not a bucket")
|
||||||
|
ErrInvalidKeyLength = errors.New("invalid key length")
|
||||||
|
ErrInvalidValueLength = errors.New("invalid value length")
|
||||||
|
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPrefixBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser {
|
||||||
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, ErrNotBucket
|
||||||
|
}
|
||||||
|
if len(key) != 1 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var b PrefixBucket
|
||||||
|
if b.prefix = Prefix(key[0]); b.prefix != prefix {
|
||||||
|
return nil, nil, ErrInvalidPrefix
|
||||||
|
}
|
||||||
|
b.resolvers = resolvers
|
||||||
|
return &b, next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrefixContainerBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser {
|
||||||
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, ErrNotBucket
|
||||||
|
}
|
||||||
|
if len(key) != 33 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var b PrefixContainerBucket
|
||||||
|
if b.prefix = Prefix(key[0]); b.prefix != prefix {
|
||||||
|
return nil, nil, ErrInvalidPrefix
|
||||||
|
}
|
||||||
|
if err := b.id.Decode(key[1:]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
b.resolvers = resolvers
|
||||||
|
return &b, next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserBucketParser(next common.Parser, resolvers Resolvers) common.Parser {
|
||||||
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, ErrNotBucket
|
||||||
|
}
|
||||||
|
var b UserBucket
|
||||||
|
if err := b.id.DecodeString(base58.Encode(key)); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
b.resolvers = resolvers
|
||||||
|
return &b, next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainerBucketParser(next common.Parser, resolvers Resolvers) common.Parser {
|
||||||
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, ErrNotBucket
|
||||||
|
}
|
||||||
|
if len(key) != 32 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var b ContainerBucket
|
||||||
|
if err := b.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
b.resolvers = resolvers
|
||||||
|
return &b, next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
||||||
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, ErrNotBucket
|
||||||
|
}
|
||||||
|
if len(key) < 34 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var b UserAttributeKeyBucket
|
||||||
|
if b.prefix = Prefix(key[0]); b.prefix != UserAttribute {
|
||||||
|
return nil, nil, ErrInvalidPrefix
|
||||||
|
}
|
||||||
|
if err := b.id.Decode(key[1:33]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
b.key = string(key[33:])
|
||||||
|
return &b, next, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAttributeValueBucketParser(next common.Parser) common.Parser {
|
||||||
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, ErrNotBucket
|
||||||
|
}
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var b UserAttributeValueBucket
|
||||||
|
b.value = string(key)
|
||||||
|
return &b, next, nil
|
||||||
|
}
|
||||||
|
}
|
29
cmd/frostfs-lens/internal/schema/metabase/parser.go
Normal file
29
cmd/frostfs-lens/internal/schema/metabase/parser.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package metabase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/buckets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MetabaseParser = common.WithFallback(
|
||||||
|
common.Any(
|
||||||
|
buckets.GraveyardParser,
|
||||||
|
buckets.GarbageParser,
|
||||||
|
buckets.ContainerVolumeParser,
|
||||||
|
buckets.LockedParser,
|
||||||
|
buckets.ShardInfoParser,
|
||||||
|
buckets.PrimaryParser,
|
||||||
|
buckets.LockersParser,
|
||||||
|
buckets.TombstoneParser,
|
||||||
|
buckets.SmallParser,
|
||||||
|
buckets.RootParser,
|
||||||
|
buckets.OwnerParser,
|
||||||
|
buckets.UserAttributeParser,
|
||||||
|
buckets.PayloadHashParser,
|
||||||
|
buckets.ParentParser,
|
||||||
|
buckets.SplitParser,
|
||||||
|
buckets.ContainerCountersParser,
|
||||||
|
buckets.ECInfoParser,
|
||||||
|
),
|
||||||
|
common.RawParser.ToFallbackParser(),
|
||||||
|
)
|
|
@ -0,0 +1,65 @@
|
||||||
|
package records
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *GraveyardRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GarbageRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContainerVolumeRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LockedRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ShardInfoRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ObjectRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SmallRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RootRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OwnerRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserAttributeRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PayloadHashRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ParentRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SplitRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContainerCountersRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ECInfoRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
145
cmd/frostfs-lens/internal/schema/metabase/records/filter.go
Normal file
145
cmd/frostfs-lens/internal/schema/metabase/records/filter.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package records
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *GraveyardRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(r.object.Container().Equals(id), common.Yes, common.No)
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.object.Object().Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GarbageRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(r.addr.Container().Equals(id), common.Yes, common.No)
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.addr.Object().Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContainerVolumeRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ShardInfoRecord) Filter(string, any) common.FilterResult {
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LockedRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ObjectRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SmallRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RootRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OwnerRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserAttributeRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PayloadHashRecord) Filter(string, any) common.FilterResult {
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ParentRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.parent.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SplitRecord) Filter(string, any) common.FilterResult {
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContainerCountersRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ECInfoRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.id.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
251
cmd/frostfs-lens/internal/schema/metabase/records/parsers.go
Normal file
251
cmd/frostfs-lens/internal/schema/metabase/records/parsers.go
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
package records
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidKeyLength = errors.New("invalid key length")
|
||||||
|
ErrInvalidValueLength = errors.New("invalid value length")
|
||||||
|
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||||
|
)
|
||||||
|
|
||||||
|
func GraveyardRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 64 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
if len(value) != 64 {
|
||||||
|
return nil, nil, ErrInvalidValueLength
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
cnr cid.ID
|
||||||
|
obj oid.ID
|
||||||
|
r GraveyardRecord
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = cnr.Decode(key[:32])
|
||||||
|
_ = obj.Decode(key[32:])
|
||||||
|
|
||||||
|
r.object.SetContainer(cnr)
|
||||||
|
r.object.SetObject(obj)
|
||||||
|
|
||||||
|
_ = cnr.Decode(value[:32])
|
||||||
|
_ = obj.Decode(value[32:])
|
||||||
|
|
||||||
|
r.tombstone.SetContainer(cnr)
|
||||||
|
r.tombstone.SetObject(obj)
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GarbageRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 64 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
cnr cid.ID
|
||||||
|
obj oid.ID
|
||||||
|
r GarbageRecord
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = cnr.Decode(key[:32])
|
||||||
|
_ = obj.Decode(key[32:])
|
||||||
|
|
||||||
|
r.addr.SetContainer(cnr)
|
||||||
|
r.addr.SetObject(obj)
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContainerVolumeRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 32 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
if len(value) != 8 {
|
||||||
|
return nil, nil, ErrInvalidValueLength
|
||||||
|
}
|
||||||
|
var r ContainerVolumeRecord
|
||||||
|
|
||||||
|
_ = r.id.Decode(key)
|
||||||
|
r.volume = binary.LittleEndian.Uint64(value)
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LockedRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var (
|
||||||
|
r LockedRecord
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if r.ids, err = DecodeOIDs(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShardInfoRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
var r ShardInfoRecord
|
||||||
|
if string(key) == "id" {
|
||||||
|
r.label = string(key)
|
||||||
|
r.value = shard.ID(value).String()
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(value) != 8 {
|
||||||
|
return nil, nil, ErrInvalidValueLength
|
||||||
|
}
|
||||||
|
r.label = string(key)
|
||||||
|
r.value = strconv.FormatUint(binary.LittleEndian.Uint64(value), 10)
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObjectRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 32 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var r ObjectRecord
|
||||||
|
|
||||||
|
_ = r.id.Decode(key)
|
||||||
|
if err := r.object.Unmarshal(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SmallRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var r SmallRecord
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(value) != 0 {
|
||||||
|
x := string(value)
|
||||||
|
r.storageID = &x
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var r RootRecord
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(value) == 0 {
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
r.info = &objectSDK.SplitInfo{}
|
||||||
|
if err := r.info.Unmarshal(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func OwnerRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var r OwnerRecord
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserAttributeRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var r UserAttributeRecord
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PayloadHashRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 32 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
r PayloadHashRecord
|
||||||
|
)
|
||||||
|
|
||||||
|
r.checksum.SetSHA256([32]byte(key))
|
||||||
|
if r.ids, err = DecodeOIDs(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParentRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var (
|
||||||
|
r ParentRecord
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if err = r.parent.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if r.ids, err = DecodeOIDs(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
r SplitRecord
|
||||||
|
)
|
||||||
|
if err = r.id.UnmarshalBinary(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if r.ids, err = DecodeOIDs(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContainerCountersRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(value) != 24 {
|
||||||
|
return nil, nil, ErrInvalidValueLength
|
||||||
|
}
|
||||||
|
|
||||||
|
var r ContainerCountersRecord
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logical = binary.LittleEndian.Uint64(value[:8])
|
||||||
|
r.physical = binary.LittleEndian.Uint64(value[8:16])
|
||||||
|
r.user = binary.LittleEndian.Uint64(value[16:24])
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ECInfoRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
var (
|
||||||
|
r ECInfoRecord
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := r.id.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if r.ids, err = DecodeOIDs(value); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
135
cmd/frostfs-lens/internal/schema/metabase/records/string.go
Normal file
135
cmd/frostfs-lens/internal/schema/metabase/records/string.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package records
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *GraveyardRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Object CID %s OID %s %c Tombstone CID %s OID %s",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.object.Container()), tcell.ColorAqua),
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.object.Object()), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.tombstone.Container()), tcell.ColorAqua),
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.tombstone.Object()), tcell.ColorAqua),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GarbageRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"CID %-44s OID %-44s",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.addr.Container()), tcell.ColorAqua),
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.addr.Object()), tcell.ColorAqua),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContainerVolumeRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"CID %-44s %c %d",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
r.volume,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LockedRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Object OID %s %c Lockers [%d]OID {...}",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
len(r.ids),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ShardInfoRecord) String() string {
|
||||||
|
return fmt.Sprintf("%-13s %c %s", r.label, tview.Borders.Vertical, r.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ObjectRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"OID %s %c Object {...}",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SmallRecord) String() string {
|
||||||
|
s := fmt.Sprintf(
|
||||||
|
"OID %s %c",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
)
|
||||||
|
if r.storageID != nil {
|
||||||
|
s = fmt.Sprintf("%s %s", s, *r.storageID)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RootRecord) String() string {
|
||||||
|
s := fmt.Sprintf(
|
||||||
|
"Root OID %s %c",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
)
|
||||||
|
if r.info != nil {
|
||||||
|
s += " Split info {...}"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OwnerRecord) String() string {
|
||||||
|
return "OID " + common.FormatSimple(r.id.String(), tcell.ColorAqua)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserAttributeRecord) String() string {
|
||||||
|
return "OID " + common.FormatSimple(r.id.String(), tcell.ColorAqua)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PayloadHashRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Checksum %s %c [%d]OID {...}",
|
||||||
|
common.FormatSimple(r.checksum.String(), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
len(r.ids),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ParentRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Parent OID %s %c [%d]OID {...}",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.parent), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
len(r.ids),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SplitRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Split ID %s %c [%d]OID {...}",
|
||||||
|
common.FormatSimple(r.id.String(), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
len(r.ids),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContainerCountersRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"CID %s %c logical %d, physical %d, user %d",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
r.logical, r.physical, r.user,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ECInfoRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"OID %s %c [%d]OID {...}",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
len(r.ids),
|
||||||
|
)
|
||||||
|
}
|
82
cmd/frostfs-lens/internal/schema/metabase/records/types.go
Normal file
82
cmd/frostfs-lens/internal/schema/metabase/records/types.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package records
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
GraveyardRecord struct {
|
||||||
|
object, tombstone oid.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
GarbageRecord struct {
|
||||||
|
addr oid.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerVolumeRecord struct {
|
||||||
|
id cid.ID
|
||||||
|
volume uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
LockedRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
ids []oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
ShardInfoRecord struct {
|
||||||
|
label string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
object objectSDK.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
storageID *string // optional
|
||||||
|
}
|
||||||
|
|
||||||
|
RootRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
info *objectSDK.SplitInfo // optional
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnerRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAttributeRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
PayloadHashRecord struct {
|
||||||
|
checksum checksum.Checksum
|
||||||
|
ids []oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
ParentRecord struct {
|
||||||
|
parent oid.ID
|
||||||
|
ids []oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitRecord struct {
|
||||||
|
id uuid.UUID
|
||||||
|
ids []oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerCountersRecord struct {
|
||||||
|
id cid.ID
|
||||||
|
logical, physical, user uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
ECInfoRecord struct {
|
||||||
|
id oid.ID
|
||||||
|
ids []oid.ID
|
||||||
|
}
|
||||||
|
)
|
20
cmd/frostfs-lens/internal/schema/metabase/records/util.go
Normal file
20
cmd/frostfs-lens/internal/schema/metabase/records/util.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package records
|
||||||
|
|
||||||
|
import (
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DecodeOIDs(data []byte) ([]oid.ID, error) {
|
||||||
|
r := io.NewBinReaderFromBuf(data)
|
||||||
|
|
||||||
|
size := r.ReadVarUint()
|
||||||
|
oids := make([]oid.ID, size)
|
||||||
|
|
||||||
|
for i := range size {
|
||||||
|
if err := oids[i].Decode(r.ReadVarBytes()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oids, nil
|
||||||
|
}
|
63
cmd/frostfs-lens/internal/schema/writecache/parsers.go
Normal file
63
cmd/frostfs-lens/internal/schema/writecache/parsers.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package writecache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
"github.com/mr-tron/base58"
|
||||||
|
)
|
||||||
|
|
||||||
|
var WritecacheParser = common.WithFallback(
|
||||||
|
DefaultBucketParser,
|
||||||
|
common.RawParser.ToFallbackParser(),
|
||||||
|
)
|
||||||
|
|
||||||
|
func DefaultBucketParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if value != nil {
|
||||||
|
return nil, nil, errors.New("not a bucket")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(key, []byte{0}) {
|
||||||
|
return nil, nil, errors.New("invalid key")
|
||||||
|
}
|
||||||
|
return &DefaultBucket{}, DefaultRecordParser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
parts := strings.Split(string(key), "/")
|
||||||
|
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, nil, errors.New("invalid key, expected address string <CID>/<OID>")
|
||||||
|
}
|
||||||
|
|
||||||
|
cnrRaw, err := base58.Decode(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.New("can't decode CID string")
|
||||||
|
}
|
||||||
|
objRaw, err := base58.Decode(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.New("can't decode OID string")
|
||||||
|
}
|
||||||
|
|
||||||
|
cnr := cid.ID{}
|
||||||
|
if err := cnr.Decode(cnrRaw); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("can't decode CID: %w", err)
|
||||||
|
}
|
||||||
|
obj := oid.ID{}
|
||||||
|
if err := obj.Decode(objRaw); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("can't decode OID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r DefaultRecord
|
||||||
|
|
||||||
|
r.addr.SetContainer(cnr)
|
||||||
|
r.addr.SetObject(obj)
|
||||||
|
|
||||||
|
r.data = value[:]
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue