WIP: Morph: Add unit tests #2

Closed
dstepanov-yadro wants to merge 233 commits from TrueCloudLab/frostfs-node:master into object-3608-morph-unit-tests
538 changed files with 12747 additions and 7395 deletions

View file

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

View file

@ -1,4 +1,4 @@
FROM golang:1.19 FROM golang:1.20
WORKDIR /tmp WORKDIR /tmp

View file

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

View file

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

View file

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

View file

@ -1,19 +0,0 @@
FROM golang:1.18 as builder
ARG BUILD=now
ARG VERSION=dev
ARG REPO=repository
WORKDIR /src
COPY . /src
RUN make bin/frostfs-node
# Executable image
FROM alpine AS frostfs-node
RUN apk add --no-cache bash
WORKDIR /
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
COPY --from=builder /src/config/testnet/config.yml /config.yml
CMD ["frostfs-node", "--config", "/config.yml"]

View file

@ -0,0 +1,38 @@
name: Build
on: [pull_request]
jobs:
build:
name: Build Components
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.19', '1.20' ]
steps:
- uses: actions/checkout@v3
with:
# Allows to fetch all history for all branches and tags.
# Need this for proper versioning.
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.go_versions }}'
- name: Build CLI
run: make bin/frostfs-cli
- name: Build NODE
run: make bin/frostfs-node
- name: Build IR
run: make bin/frostfs-ir
- name: Build ADM
run: make bin/frostfs-adm
- name: Build LENS
run: make bin/frostfs-lens

View file

@ -0,0 +1,72 @@
name: Tests and linters
on: [pull_request]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
cache: true
- name: golangci-lint
uses: https://github.com/golangci/golangci-lint-action@v3
with:
version: latest
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
go_versions: [ '1.19', '1.20' ]
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '${{ matrix.go_versions }}'
cache: true
- name: Run tests
run: make test
tests-race:
name: Tests with -race
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
cache: true
- name: Run tests
run: go test ./... -count=1 -race
staticcheck:
name: Staticcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
cache: true
- name: Install staticcheck
run: make staticcheck-install
- name: Run staticcheck
run: make staticcheck-run

View file

@ -0,0 +1,22 @@
name: Vulncheck
on: [pull_request]
jobs:
vulncheck:
name: Vulncheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20'
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: govulncheck ./...

View file

@ -4,7 +4,7 @@
# options for analysis running # options for analysis running
run: run:
# timeout for analysis, e.g. 30s, 5m, default is 1m # timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 10m timeout: 20m
# include test files or not, default is true # include test files or not, default is true
tests: false tests: false
@ -31,6 +31,12 @@ linters-settings:
statements: 60 # default 40 statements: 60 # default 40
gocognit: gocognit:
min-complexity: 40 # default 30 min-complexity: 40 # default 30
importas:
no-unaliased: true
no-extra-aliases: false
alias:
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
alias: objectSDK
linters: linters:
enable: enable:
@ -62,5 +68,6 @@ linters:
- funlen - funlen
- gocognit - gocognit
- contextcheck - contextcheck
- importas
disable-all: true disable-all: true
fast: false fast: false

View file

@ -6,17 +6,44 @@ Changelog for FrostFS Node
### Added ### Added
- Support impersonate bearer token (#229) - Support impersonate bearer token (#229)
- Change log level on SIGHUP for ir (#125) - Change log level on SIGHUP for ir (#125)
- Reload pprof and metrics on SIGHUP for ir (#125)
- Support copies number parameter in `frostfs-cli object put` (#351)
- Set extra wallets on SIGHUP for ir (#125)
- Writecache metrics (#312)
- Add tree service metrics (#370)
### Changed ### Changed
- `frostfs-cli util locode generate` is now much faster (#309)
### Fixed ### Fixed
- Take network settings into account during netmap contract update (#100) - Take network settings into account during netmap contract update (#100)
- Read config files from dir even if config file not provided via `--config` for node (#238) - Read config files from dir even if config file not provided via `--config` for node (#238)
- Notary requests parsing according to `neo-go`'s updates (#268) - Notary requests parsing according to `neo-go`'s updates (#268)
- Tree service panic in its internal client cache (#322) - Tree service panic in its internal client cache (#322)
- Iterate over endpoints when create ws client in morph's constructor (#304) - Iterate over endpoints when create ws client in morph's constructor (#304)
- Delete complex objects with GC (#332)
### Removed ### Removed
### Updated ### Updated
- `neo-go` to `v0.101.1`
- `google.golang.org/grpc` to `v1.55.0`
- `paulmach/orb` to `v0.9.2`
- `go.etcd.io/bbolt` to `v1.3.7`
- `github.com/nats-io/nats.go` to `v1.25.0`
- `golang.org/x/sync` to `v0.2.0`
- `golang.org/x/term` to `v0.8.0`
- `github.com/spf13/cobra` to `v1.7.0`
- `github.com/panjf2000/ants/v2` `v2.7.4`
- `github.com/multiformats/go-multiaddr` to `v0.9.0`
- `github.com/hashicorp/golang-lru/v2` to `v2.0.2`
- `go.uber.org/atomic` to `v1.11.0`
- Minimum go version to v1.19
- `github.com/prometheus/client_golang` to `v1.15.1`
- `github.com/prometheus/client_model` to `v0.4.0`
- `go.opentelemetry.io/otel` to `v1.15.1`
- `go.opentelemetry.io/otel/trace` to `v1.15.1`
- `github.com/spf13/cast` to `v1.5.1`
- `git.frostfs.info/TrueCloudLab/hrw` to `v1.2.1`
### Updating from v0.36.0 ### Updating from v0.36.0
## [v0.36.0] - 2023-04-12 - Furtwängler ## [v0.36.0] - 2023-04-12 - Furtwängler

View file

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

View file

@ -95,7 +95,7 @@ image-%:
-t $(HUB_IMAGE)-$*:$(HUB_TAG) . -t $(HUB_IMAGE)-$*:$(HUB_TAG) .
# Build all Docker images # Build all Docker images
images: image-storage image-ir image-cli image-adm image-storage-testnet images: image-storage image-ir image-cli image-adm
# Build dirty local Docker images # Build dirty local Docker images
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
@ -126,7 +126,7 @@ imports:
# Run Unit Test with go test # Run Unit Test with go test
test: test:
@echo "⇒ Running go test" @echo "⇒ Running go test"
@go test ./... @go test ./... -count=1
pre-commit-run: pre-commit-run:
@pre-commit run -a --hook-stage manual @pre-commit run -a --hook-stage manual
@ -135,8 +135,12 @@ pre-commit-run:
lint: lint:
@golangci-lint --timeout=5m run @golangci-lint --timeout=5m run
# Install staticcheck
staticcheck-install:
@go install honnef.co/go/tools/cmd/staticcheck@latest
# Run staticcheck # Run staticcheck
staticcheck: staticcheck-run:
@staticcheck ./... @staticcheck ./...
# Run linters in Docker # Run linters in Docker

View file

@ -49,7 +49,7 @@ The latest version of frostfs-node works with frostfs-contract
# Building # Building
To make all binaries you need Go 1.18+ and `make`: To make all binaries you need Go 1.19+ and `make`:
``` ```
make all make all
``` ```

View file

@ -18,6 +18,7 @@ To start a network, you need a set of consensus nodes, the same number of
Alphabet nodes and any number of Storage nodes. While the number of Storage Alphabet nodes and any number of Storage nodes. While the number of Storage
nodes can be scaled almost infinitely, the number of consensus and Alphabet nodes can be scaled almost infinitely, the number of consensus and Alphabet
nodes can't be changed so easily right now. Consider this before going any further. nodes can't be changed so easily right now. Consider this before going any further.
Note also that there is an upper limit on the number of alphabet nodes (currently 22).
It is easier to use`frostfs-adm` with a predefined configuration. First, create It is easier to use`frostfs-adm` with a predefined configuration. First, create
a network configuration file. In this example, there is going to be only one a network configuration file. In this example, there is going to be only one

View file

@ -37,11 +37,6 @@ const (
dumpBalancesAlphabetFlag = "alphabet" dumpBalancesAlphabetFlag = "alphabet"
dumpBalancesProxyFlag = "proxy" dumpBalancesProxyFlag = "proxy"
dumpBalancesUseScriptHashFlag = "script-hash" dumpBalancesUseScriptHashFlag = "script-hash"
// notaryEnabled signifies whether contracts were deployed in a notary-enabled environment.
// The setting is here to simplify testing and building the command for testnet (notary currently disabled).
// It will be removed eventually.
notaryEnabled = true
) )
func dumpBalances(cmd *cobra.Command, _ []string) error { func dumpBalances(cmd *cobra.Command, _ []string) error {
@ -60,7 +55,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy { if dumpStorage || dumpAlphabet || dumpProxy {
nnsCs, err = c.GetContractStateByID(1) nnsCs, err = c.GetContractStateByID(1)
if err != nil { if err != nil {
return fmt.Errorf("can't get NNS contract info: %w", err) return fmt.Errorf("can't get NNS contract info: %w", err)
@ -72,7 +67,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error {
} }
} }
irList, err := fetchIRNodes(c, nmHash, rolemgmt.Hash) irList, err := fetchIRNodes(c, rolemgmt.Hash)
if err != nil { if err != nil {
return err return err
} }
@ -187,40 +182,22 @@ func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.In
return nil return nil
} }
func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, error) { func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
var irList []accBalancePair
inv := invoker.New(c, nil) inv := invoker.New(c, nil)
if notaryEnabled { height, err := c.GetBlockCount()
height, err := c.GetBlockCount() if err != nil {
if err != nil { return nil, fmt.Errorf("can't get block height: %w", err)
return nil, fmt.Errorf("can't get block height: %w", err) }
}
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height) arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
if err != nil { if err != nil {
return nil, errors.New("can't fetch list of IR nodes from the netmap contract") return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
} }
irList = make([]accBalancePair, len(arr)) irList := make([]accBalancePair, len(arr))
for i := range arr { for i := range arr {
irList[i].scriptHash = arr[i].GetScriptHash() irList[i].scriptHash = arr[i].GetScriptHash()
}
} else {
arr, err := unwrap.ArrayOfBytes(inv.Call(nmHash, "innerRingList"))
if err != nil {
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
}
irList = make([]accBalancePair, len(arr))
for i := range arr {
pub, err := keys.NewPublicKeyFromBytes(arr[i], elliptic.P256())
if err != nil {
return nil, fmt.Errorf("can't parse IR node public key: %w", err)
}
irList[i].scriptHash = pub.GetScriptHash()
}
} }
return irList, nil return irList, nil
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/sync/errgroup"
) )
const ( const (
@ -38,6 +39,9 @@ func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
if size == 0 { if size == 0 {
return errors.New("size must be > 0") return errors.New("size must be > 0")
} }
if size > maxAlphabetNodes {
return ErrTooManyAlphabetNodes
}
v := viper.GetViper() v := viper.GetViper()
walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag))
@ -92,28 +96,32 @@ func initializeWallets(v *viper.Viper, walletDir string, size int) ([]string, er
pubs[i] = w.Accounts[0].PrivateKey().PublicKey() pubs[i] = w.Accounts[0].PrivateKey().PublicKey()
} }
var errG errgroup.Group
// Create committee account with N/2+1 multi-signature. // Create committee account with N/2+1 multi-signature.
majCount := smartcontract.GetMajorityHonestNodeCount(size) majCount := smartcontract.GetMajorityHonestNodeCount(size)
for i, w := range wallets {
if err := addMultisigAccount(w, majCount, committeeAccountName, passwords[i], pubs); err != nil {
return nil, fmt.Errorf("can't create committee account: %w", err)
}
}
// Create consensus account with 2*N/3+1 multi-signature. // Create consensus account with 2*N/3+1 multi-signature.
bftCount := smartcontract.GetDefaultHonestNodeCount(size) bftCount := smartcontract.GetDefaultHonestNodeCount(size)
for i, w := range wallets { for i := range wallets {
if err := addMultisigAccount(w, bftCount, consensusAccountName, passwords[i], pubs); err != nil { i := i
return nil, fmt.Errorf("can't create consensus account: %w", err) ps := make(keys.PublicKeys, len(pubs))
} copy(ps, pubs)
errG.Go(func() error {
if err := addMultisigAccount(wallets[i], majCount, committeeAccountName, passwords[i], ps); err != nil {
return fmt.Errorf("can't create committee account: %w", err)
}
if err := addMultisigAccount(wallets[i], bftCount, consensusAccountName, passwords[i], ps); err != nil {
return fmt.Errorf("can't create consentus account: %w", err)
}
if err := wallets[i].SavePretty(); err != nil {
return fmt.Errorf("can't save wallet: %w", err)
}
return nil
})
} }
if err := errG.Wait(); err != nil {
for _, w := range wallets { return nil, err
if err := w.SavePretty(); err != nil {
return nil, fmt.Errorf("can't save wallet: %w", err)
}
} }
return passwords, nil return passwords, nil
} }

View file

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

View file

@ -23,6 +23,13 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const (
// maxAlphabetNodes is the maximum number of candidates allowed, which is currently limited by the size
// of the invocation script.
// See: https://github.com/nspcc-dev/neo-go/blob/740488f7f35e367eaa99a71c0a609c315fe2b0fc/pkg/core/transaction/witness.go#L10
maxAlphabetNodes = 22
)
type cache struct { type cache struct {
nnsCs *state.Contract nnsCs *state.Contract
groupKey *keys.PublicKey groupKey *keys.PublicKey
@ -45,6 +52,8 @@ type initializeContext struct {
ContractPath string ContractPath string
} }
var ErrTooManyAlphabetNodes = fmt.Errorf("too many alphabet nodes (maximum allowed is %d)", maxAlphabetNodes)
func initializeSideChainCmd(cmd *cobra.Command, _ []string) error { func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
initCtx, err := newInitializeContext(cmd, viper.GetViper()) initCtx, err := newInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
@ -111,6 +120,10 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
return nil, err return nil, err
} }
if len(wallets) > maxAlphabetNodes {
return nil, ErrTooManyAlphabetNodes
}
needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init" needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init"
var w *wallet.Wallet var w *wallet.Wallet
@ -197,11 +210,11 @@ func validateInit(cmd *cobra.Command) error {
func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) { func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) {
var c Client var c Client
var err error var err error
if v.GetString(localDumpFlag) != "" { if ldf := cmd.Flags().Lookup(localDumpFlag); ldf != nil && ldf.Changed {
if v.GetString(endpointFlag) != "" { if cmd.Flags().Changed(endpointFlag) {
return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag) return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag)
} }
c, err = newLocalClient(cmd, v, wallets) c, err = newLocalClient(cmd, v, wallets, ldf.Value.String())
} else { } else {
c, err = getN3Client(v) c, err = getN3Client(v)
} }

View file

@ -19,33 +19,24 @@ import (
) )
// initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes. // initialAlphabetNEOAmount represents the total amount of GAS distributed between alphabet nodes.
const initialAlphabetNEOAmount = native.NEOTotalSupply const (
initialAlphabetNEOAmount = native.NEOTotalSupply
func (c *initializeContext) registerCandidates() error { registerBatchSize = transaction.MaxAttributes - 1
neoHash := neo.Hash )
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neoHash, "getCandidates"))
if err != nil {
return fmt.Errorf("`getCandidates`: %w", err)
}
if len(cc) > 0 {
c.Command.Println("Candidates are already registered.")
return nil
}
func (c *initializeContext) registerCandidateRange(start, end int) error {
regPrice, err := c.getCandidateRegisterPrice() regPrice, err := c.getCandidateRegisterPrice()
if err != nil { if err != nil {
return fmt.Errorf("can't fetch registration price: %w", err) return fmt.Errorf("can't fetch registration price: %w", err)
} }
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, 1) emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, 1)
for _, acc := range c.Accounts { for _, acc := range c.Accounts[start:end] {
emit.AppCall(w.BinWriter, neoHash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes()) emit.AppCall(w.BinWriter, neo.Hash, "registerCandidate", callflag.States, acc.PrivateKey().PublicKey().Bytes())
emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.ASSERT)
} }
emit.AppCall(w.BinWriter, neoHash, "setRegisterPrice", callflag.States, regPrice) emit.AppCall(w.BinWriter, neo.Hash, "setRegisterPrice", callflag.States, regPrice)
if w.Err != nil { if w.Err != nil {
panic(fmt.Sprintf("BUG: %v", w.Err)) panic(fmt.Sprintf("BUG: %v", w.Err))
} }
@ -54,14 +45,14 @@ func (c *initializeContext) registerCandidates() error {
Signer: c.getSigner(false, c.CommitteeAcc), Signer: c.getSigner(false, c.CommitteeAcc),
Account: c.CommitteeAcc, Account: c.CommitteeAcc,
}} }}
for i := range c.Accounts { for _, acc := range c.Accounts[start:end] {
signers = append(signers, rpcclient.SignerAccount{ signers = append(signers, rpcclient.SignerAccount{
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: c.Accounts[i].Contract.ScriptHash(), Account: acc.Contract.ScriptHash(),
Scopes: transaction.CustomContracts, Scopes: transaction.CustomContracts,
AllowedContracts: []util.Uint160{neoHash}, AllowedContracts: []util.Uint160{neo.Hash},
}, },
Account: c.Accounts[i], Account: acc,
}) })
} }
@ -74,8 +65,8 @@ func (c *initializeContext) registerCandidates() error {
} }
network := c.CommitteeAct.GetNetwork() network := c.CommitteeAct.GetNetwork()
for i := range c.Accounts { for _, acc := range c.Accounts[start:end] {
if err := c.Accounts[i].SignTx(network, tx); err != nil { if err := acc.SignTx(network, tx); err != nil {
return fmt.Errorf("can't sign a transaction: %w", err) return fmt.Errorf("can't sign a transaction: %w", err)
} }
} }
@ -83,6 +74,39 @@ func (c *initializeContext) registerCandidates() error {
return c.sendTx(tx, c.Command, true) return c.sendTx(tx, c.Command, true)
} }
func (c *initializeContext) registerCandidates() error {
cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
if err != nil {
return fmt.Errorf("`getCandidates`: %w", err)
}
need := len(c.Accounts)
have := len(cc)
if need == have {
c.Command.Println("Candidates are already registered.")
return nil
}
// Register candidates in batches in order to overcome the signers amount limit.
// See: https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/transaction/transaction.go#L27
for i := 0; i < need; i += registerBatchSize {
start, end := i, i+registerBatchSize
if end > need {
end = need
}
// This check is sound because transactions are accepted/rejected atomically.
if have >= end {
continue
}
if err := c.registerCandidateRange(start, end); err != nil {
return fmt.Errorf("registering candidates %d..%d: %q", start, end-1, err)
}
}
return nil
}
func (c *initializeContext) transferNEOToAlphabetContracts() error { func (c *initializeContext) transferNEOToAlphabetContracts() error {
neoHash := neo.Hash neoHash := neo.Hash

View file

@ -2,6 +2,7 @@ package morph
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -37,13 +38,22 @@ func TestInitialize(t *testing.T) {
t.Run("7 nodes", func(t *testing.T) { t.Run("7 nodes", func(t *testing.T) {
testInitialize(t, 7) testInitialize(t, 7)
}) })
t.Run("16 nodes", func(t *testing.T) {
testInitialize(t, 16)
})
t.Run("max nodes", func(t *testing.T) {
testInitialize(t, maxAlphabetNodes)
})
t.Run("too many nodes", func(t *testing.T) {
require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes)
})
} }
func testInitialize(t *testing.T, committeeSize int) { func testInitialize(t *testing.T, committeeSize int) {
testdataDir := t.TempDir() testdataDir := t.TempDir()
v := viper.GetViper() v := viper.GetViper()
generateTestData(t, testdataDir, committeeSize) require.NoError(t, generateTestData(t, testdataDir, committeeSize))
v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName)) v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
// Set to the path or remove the next statement to download from the network. // Set to the path or remove the next statement to download from the network.
@ -74,25 +84,33 @@ func testInitialize(t *testing.T, committeeSize int) {
}) })
} }
func generateTestData(t *testing.T, dir string, size int) { func generateTestData(t *testing.T, dir string, size int) error {
v := viper.GetViper() v := viper.GetViper()
v.Set(alphabetWalletsFlag, dir) v.Set(alphabetWalletsFlag, dir)
sizeStr := strconv.FormatUint(uint64(size), 10) sizeStr := strconv.FormatUint(uint64(size), 10)
require.NoError(t, generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr)) if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil {
return err
}
setTestCredentials(v, size) setTestCredentials(v, size)
require.NoError(t, generateAlphabetCreds(generateAlphabetCmd, nil)) if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
return err
}
var pubs []string var pubs []string
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json") p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
w, err := wallet.NewWalletFromFile(p) w, err := wallet.NewWalletFromFile(p)
require.NoError(t, err, "wallet doesn't exist") if err != nil {
return fmt.Errorf("wallet doesn't exist: %w", err)
}
for _, acc := range w.Accounts { for _, acc := range w.Accounts {
if acc.Label == singleAccountName { if acc.Label == singleAccountName {
pub, ok := vm.ParseSignatureContract(acc.Contract.Script) pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
require.True(t, ok) if !ok {
return fmt.Errorf("could not parse signature script for %s", acc.Address)
}
pubs = append(pubs, hex.EncodeToString(pub)) pubs = append(pubs, hex.EncodeToString(pub))
continue continue
} }
@ -101,16 +119,18 @@ func generateTestData(t *testing.T, dir string, size int) {
cfg := config.Config{} cfg := config.Config{}
cfg.ProtocolConfiguration.Magic = 12345 cfg.ProtocolConfiguration.Magic = 12345
cfg.ProtocolConfiguration.ValidatorsCount = size cfg.ProtocolConfiguration.ValidatorsCount = uint32(size)
cfg.ProtocolConfiguration.TimePerBlock = time.Second cfg.ProtocolConfiguration.TimePerBlock = time.Second
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
cfg.ProtocolConfiguration.P2PSigExtensions = true cfg.ProtocolConfiguration.P2PSigExtensions = true
cfg.ProtocolConfiguration.VerifyTransactions = true cfg.ProtocolConfiguration.VerifyTransactions = true
data, err := yaml.Marshal(cfg) data, err := yaml.Marshal(cfg)
require.NoError(t, err) if err != nil {
return err
}
protoPath := filepath.Join(dir, protoFileName) protoPath := filepath.Join(dir, protoFileName)
require.NoError(t, os.WriteFile(protoPath, data, os.ModePerm)) return os.WriteFile(protoPath, data, os.ModePerm)
} }
func setTestCredentials(v *viper.Viper, size int) { func setTestCredentials(v *viper.Viper, size int) {

View file

@ -51,7 +51,7 @@ type localClient struct {
maxGasInvoke int64 maxGasInvoke int64
} }
func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) { func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet, dumpPath string) (*localClient, error) {
cfg, err := config.LoadFile(v.GetString(protoConfigPath)) cfg, err := config.LoadFile(v.GetString(protoConfigPath))
if err != nil { if err != nil {
return nil, err return nil, err
@ -62,7 +62,7 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
return nil, err return nil, err
} }
m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount) m := smartcontract.GetDefaultHonestNodeCount(int(cfg.ProtocolConfiguration.ValidatorsCount))
accounts := make([]*wallet.Account, len(wallets)) accounts := make([]*wallet.Account, len(wallets))
for i := range accounts { for i := range accounts {
accounts[i], err = getWalletAccount(wallets[i], consensusAccountName) accounts[i], err = getWalletAccount(wallets[i], consensusAccountName)
@ -87,7 +87,6 @@ func newLocalClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet
go bc.Run() go bc.Run()
dumpPath := v.GetString(localDumpFlag)
if cmd.Name() != "init" { if cmd.Name() != "init" {
f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600) f, err := os.OpenFile(dumpPath, os.O_RDONLY, 0600)
if err != nil { if err != nil {

View file

@ -77,7 +77,6 @@ var (
_ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag)) _ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
_ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag)) _ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
_ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath)) _ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath))
_ = viper.BindPFlag(localDumpFlag, cmd.Flags().Lookup(localDumpFlag))
}, },
RunE: initializeSideChainCmd, RunE: initializeSideChainCmd,
} }

View file

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

View file

@ -12,9 +12,11 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"google.golang.org/grpc"
) )
var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect") var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
@ -59,6 +61,9 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout) common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
} }
prmDial.SetGRPCDialOptions(
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()))
c.Init(prmInit) c.Init(prmInit)

View file

@ -0,0 +1,62 @@
package common
import (
"context"
"sort"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel/trace"
)
type spanKey struct{}
// StopClientCommandSpan stops tracing span for the command and prints trace ID on the standard output.
func StopClientCommandSpan(cmd *cobra.Command, _ []string) {
span, ok := cmd.Context().Value(spanKey{}).(trace.Span)
if !ok {
return
}
span.End()
// Noop provider cannot fail on flush.
_ = tracing.Shutdown(cmd.Context())
cmd.PrintErrf("Trace ID: %s\n", span.SpanContext().TraceID())
}
// StartClientCommandSpan starts tracing span for the command.
func StartClientCommandSpan(cmd *cobra.Command) {
enableTracing, err := cmd.Flags().GetBool(commonflags.TracingFlag)
if err != nil || !enableTracing {
return
}
_, err = tracing.Setup(cmd.Context(), tracing.Config{
Enabled: true,
Exporter: tracing.NoOpExporter,
Service: "frostfs-cli",
Version: misc.Version,
})
commonCmd.ExitOnErr(cmd, "init tracing: %w", err)
var components sort.StringSlice
for c := cmd; c != nil; c = c.Parent() {
components = append(components, c.Name())
}
for i, j := 0, len(components)-1; i < j; {
components.Swap(i, j)
i++
j--
}
operation := strings.Join(components, ".")
ctx, span := tracing.StartSpanFromContext(cmd.Context(), operation)
ctx = context.WithValue(ctx, spanKey{}, span)
cmd.SetContext(ctx)
}

View file

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

View file

@ -41,7 +41,7 @@ var accountingBalanceCmd = &cobra.Command{
prm.SetClient(cli) prm.SetClient(cli)
prm.SetAccount(idUser) prm.SetAccount(idUser)
res, err := internalclient.BalanceOf(prm) res, err := internalclient.BalanceOf(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
// print to stdout // print to stdout

View file

@ -1,6 +1,7 @@
package accounting package accounting
import ( import (
"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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -17,7 +18,9 @@ var Cmd = &cobra.Command{
_ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath)) _ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath))
_ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account)) _ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account))
_ = viper.BindPFlag(commonflags.RPC, flags.Lookup(commonflags.RPC)) _ = viper.BindPFlag(commonflags.RPC, flags.Lookup(commonflags.RPC))
common.StartClientCommandSpan(cmd)
}, },
PersistentPostRun: common.StopClientCommandSpan,
} }
func init() { func init() {

View file

@ -24,6 +24,7 @@ const (
ownerFlag = "owner" ownerFlag = "owner"
outFlag = "out" outFlag = "out"
jsonFlag = commonflags.JSON jsonFlag = commonflags.JSON
impersonateFlag = "impersonate"
) )
var createCmd = &cobra.Command{ var createCmd = &cobra.Command{
@ -39,19 +40,20 @@ is set to current epoch + n.
} }
func init() { func init() {
createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table") createCmd.Flags().StringP(eaclFlag, "e", "", "Path to the extended ACL table (mutually exclusive with --impersonate flag)")
createCmd.Flags().StringP(issuedAtFlag, "i", "", "Epoch to issue token at") createCmd.Flags().StringP(issuedAtFlag, "i", "+0", "Epoch to issue token at")
createCmd.Flags().StringP(notValidBeforeFlag, "n", "", "Not valid before epoch") createCmd.Flags().StringP(notValidBeforeFlag, "n", "+0", "Not valid before epoch")
createCmd.Flags().StringP(commonflags.ExpireAt, "x", "", "The last active epoch for the token") createCmd.Flags().StringP(commonflags.ExpireAt, "x", "", "The last active epoch for the token")
createCmd.Flags().StringP(ownerFlag, "o", "", "Token owner") createCmd.Flags().StringP(ownerFlag, "o", "", "Token owner")
createCmd.Flags().String(outFlag, "", "File to write token to") createCmd.Flags().String(outFlag, "", "File to write token to")
createCmd.Flags().Bool(jsonFlag, false, "Output token in JSON") createCmd.Flags().Bool(jsonFlag, false, "Output token in JSON")
createCmd.Flags().Bool(impersonateFlag, false, "Mark token as impersonate to consider the token signer as the request owner (mutually exclusive with --eacl flag)")
createCmd.Flags().StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage) createCmd.Flags().StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
createCmd.MarkFlagsMutuallyExclusive(eaclFlag, impersonateFlag)
_ = cobra.MarkFlagFilename(createCmd.Flags(), eaclFlag) _ = cobra.MarkFlagFilename(createCmd.Flags(), eaclFlag)
_ = cobra.MarkFlagRequired(createCmd.Flags(), issuedAtFlag)
_ = cobra.MarkFlagRequired(createCmd.Flags(), notValidBeforeFlag)
_ = cobra.MarkFlagRequired(createCmd.Flags(), commonflags.ExpireAt) _ = cobra.MarkFlagRequired(createCmd.Flags(), commonflags.ExpireAt)
_ = cobra.MarkFlagRequired(createCmd.Flags(), ownerFlag) _ = cobra.MarkFlagRequired(createCmd.Flags(), ownerFlag)
_ = cobra.MarkFlagRequired(createCmd.Flags(), outFlag) _ = cobra.MarkFlagRequired(createCmd.Flags(), outFlag)
@ -68,10 +70,14 @@ func createToken(cmd *cobra.Command, _ []string) {
commonCmd.ExitOnErr(cmd, "can't parse --"+notValidBeforeFlag+" flag: %w", err) commonCmd.ExitOnErr(cmd, "can't parse --"+notValidBeforeFlag+" flag: %w", err)
if iatRelative || expRelative || nvbRelative { if iatRelative || expRelative || nvbRelative {
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
if len(endpoint) == 0 {
commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", fmt.Errorf("'%s' flag value must be specified", commonflags.RPC))
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel() defer cancel()
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint) currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint)
commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", err) commonCmd.ExitOnErr(cmd, "can't fetch current epoch: %w", err)
@ -101,6 +107,9 @@ func createToken(cmd *cobra.Command, _ []string) {
b.SetIat(iat) b.SetIat(iat)
b.ForUser(ownerID) b.ForUser(ownerID)
impersonate, _ := cmd.Flags().GetBool(impersonateFlag)
b.SetImpersonate(impersonate)
eaclPath, _ := cmd.Flags().GetString(eaclFlag) eaclPath, _ := cmd.Flags().GetString(eaclFlag)
if eaclPath != "" { if eaclPath != "" {
table := eaclSDK.NewTable() table := eaclSDK.NewTable()

View file

@ -48,7 +48,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
var prm internalclient.NetMapSnapshotPrm var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli) prm.SetClient(cli)
resmap, err := internalclient.NetMapSnapshot(prm) resmap, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot to validate container placement, "+ commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot to validate container placement, "+
"use --force option to skip this check: %w", err) "use --force option to skip this check: %w", err)
@ -96,7 +96,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
syncContainerPrm.SetClient(cli) syncContainerPrm.SetClient(cli)
syncContainerPrm.SetContainer(&cnr) syncContainerPrm.SetContainer(&cnr)
_, err = internalclient.SyncContainerSettings(syncContainerPrm) _, err = internalclient.SyncContainerSettings(cmd.Context(), syncContainerPrm)
commonCmd.ExitOnErr(cmd, "syncing container's settings rpc error: %w", err) commonCmd.ExitOnErr(cmd, "syncing container's settings rpc error: %w", err)
var putPrm internalclient.PutContainerPrm var putPrm internalclient.PutContainerPrm
@ -107,7 +107,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
putPrm.WithinSession(*tok) putPrm.WithinSession(*tok)
} }
res, err := internalclient.PutContainer(putPrm) res, err := internalclient.PutContainer(cmd.Context(), putPrm)
commonCmd.ExitOnErr(cmd, "put container rpc error: %w", err) commonCmd.ExitOnErr(cmd, "put container rpc error: %w", err)
id := res.ID() id := res.ID()
@ -124,7 +124,7 @@ It will be stored in sidechain when inner ring will accepts it.`,
for i := 0; i < awaitTimeout; i++ { for i := 0; i < awaitTimeout; i++ {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
_, err := internalclient.GetContainer(getPrm) _, err := internalclient.GetContainer(cmd.Context(), getPrm)
if err == nil { if err == nil {
cmd.Println("container has been persisted on sidechain") cmd.Println("container has been persisted on sidechain")
return return
@ -141,6 +141,7 @@ func initContainerCreateCmd() {
// Init common flags // Init common flags
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage) flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
flags.Bool(commonflags.TracingFlag, false, commonflags.TracingFlagUsage)
flags.DurationP(commonflags.Timeout, commonflags.TimeoutShorthand, commonflags.TimeoutDefault, commonflags.TimeoutUsage) flags.DurationP(commonflags.Timeout, commonflags.TimeoutShorthand, commonflags.TimeoutDefault, commonflags.TimeoutUsage)
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage) flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage) flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)

View file

@ -34,7 +34,7 @@ Only owner of the container has a permission to remove container.`,
getPrm.SetClient(cli) getPrm.SetClient(cli)
getPrm.SetContainer(id) getPrm.SetContainer(id)
resGet, err := internalclient.GetContainer(getPrm) resGet, err := internalclient.GetContainer(cmd.Context(), getPrm)
commonCmd.ExitOnErr(cmd, "can't get the container: %w", err) commonCmd.ExitOnErr(cmd, "can't get the container: %w", err)
owner := resGet.Container().Owner() owner := resGet.Container().Owner()
@ -72,7 +72,7 @@ Only owner of the container has a permission to remove container.`,
common.PrintVerbose(cmd, "Searching for LOCK objects...") common.PrintVerbose(cmd, "Searching for LOCK objects...")
res, err := internalclient.SearchObjects(searchPrm) res, err := internalclient.SearchObjects(cmd.Context(), searchPrm)
commonCmd.ExitOnErr(cmd, "can't search for LOCK objects: %w", err) commonCmd.ExitOnErr(cmd, "can't search for LOCK objects: %w", err)
if len(res.IDList()) != 0 { if len(res.IDList()) != 0 {
@ -91,7 +91,7 @@ Only owner of the container has a permission to remove container.`,
delPrm.WithinSession(*tok) delPrm.WithinSession(*tok)
} }
_, err := internalclient.DeleteContainer(delPrm) _, err := internalclient.DeleteContainer(cmd.Context(), delPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
cmd.Println("container delete method invoked") cmd.Println("container delete method invoked")
@ -106,7 +106,7 @@ Only owner of the container has a permission to remove container.`,
for i := 0; i < awaitTimeout; i++ { for i := 0; i < awaitTimeout; i++ {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
_, err := internalclient.GetContainer(getPrm) _, err := internalclient.GetContainer(cmd.Context(), getPrm)
if err != nil { if err != nil {
cmd.Println("container has been removed:", containerID) cmd.Println("container has been removed:", containerID)
return return
@ -124,6 +124,7 @@ func initContainerDeleteCmd() {
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage) flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage) flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage) flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
flags.Bool(commonflags.TracingFlag, false, commonflags.TracingFlagUsage)
flags.StringVar(&containerID, commonflags.CIDFlag, "", commonflags.CIDFlagUsage) flags.StringVar(&containerID, commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
flags.BoolVar(&containerAwait, "await", false, "Block execution until container is removed") flags.BoolVar(&containerAwait, "await", false, "Block execution until container is removed")

View file

@ -151,7 +151,7 @@ func getContainer(cmd *cobra.Command) (container.Container, *ecdsa.PrivateKey) {
prm.SetClient(cli) prm.SetClient(cli)
prm.SetContainer(id) prm.SetContainer(id)
res, err := internalclient.GetContainer(prm) res, err := internalclient.GetContainer(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
cnr = res.Container() cnr = res.Container()

View file

@ -24,7 +24,7 @@ var getExtendedACLCmd = &cobra.Command{
eaclPrm.SetClient(cli) eaclPrm.SetClient(cli)
eaclPrm.SetContainer(id) eaclPrm.SetContainer(id)
res, err := internalclient.EACL(eaclPrm) res, err := internalclient.EACL(cmd.Context(), eaclPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
eaclTable := res.EACL() eaclTable := res.EACL()

View file

@ -49,7 +49,7 @@ var listContainersCmd = &cobra.Command{
prm.SetClient(cli) prm.SetClient(cli)
prm.SetAccount(idUser) prm.SetAccount(idUser)
res, err := internalclient.ListContainers(prm) res, err := internalclient.ListContainers(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
var prmGet internalclient.GetContainerPrm var prmGet internalclient.GetContainerPrm
@ -63,7 +63,7 @@ var listContainersCmd = &cobra.Command{
} }
prmGet.SetContainer(cnrID) prmGet.SetContainer(cnrID)
res, err := internalclient.GetContainer(prmGet) res, err := internalclient.GetContainer(cmd.Context(), prmGet)
if err != nil { if err != nil {
cmd.Printf(" failed to read attributes: %v\n", err) cmd.Printf(" failed to read attributes: %v\n", err)
continue continue
@ -78,7 +78,8 @@ var listContainersCmd = &cobra.Command{
if flagVarListPrintAttr { if flagVarListPrintAttr {
cnr.IterateAttributes(func(key, val string) { cnr.IterateAttributes(func(key, val string) {
if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) { if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
// FIXME(@cthulhu-rider): neofs-sdk-go#314 use dedicated method to skip system attributes // 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) cmd.Printf(" %s: %s\n", key, val)
} }
}) })

View file

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

View file

@ -31,7 +31,7 @@ var containerNodesCmd = &cobra.Command{
var prm internalclient.NetMapSnapshotPrm var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli) prm.SetClient(cli)
resmap, err := internalclient.NetMapSnapshot(prm) resmap, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot", err) commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot", err)
var id cid.ID var id cid.ID

View file

@ -0,0 +1,233 @@
package container
import (
"bufio"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"strings"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type policyPlaygroundREPL struct {
cmd *cobra.Command
args []string
nodes map[string]netmap.NodeInfo
}
func newPolicyPlaygroundREPL(cmd *cobra.Command, args []string) (*policyPlaygroundREPL, error) {
return &policyPlaygroundREPL{
cmd: cmd,
args: args,
nodes: map[string]netmap.NodeInfo{},
}, nil
}
func (repl *policyPlaygroundREPL) handleLs(args []string) error {
if len(args) > 0 {
return fmt.Errorf("too many arguments for command 'ls': got %d, want 0", len(args))
}
i := 1
for id, node := range repl.nodes {
var attrs []string
node.IterateAttributes(func(k, v string) {
attrs = append(attrs, fmt.Sprintf("%s:%q", k, v))
})
fmt.Printf("\t%2d: id=%s attrs={%v}\n", i, id, strings.Join(attrs, " "))
i++
}
return nil
}
func (repl *policyPlaygroundREPL) handleAdd(args []string) error {
if len(args) == 0 {
return fmt.Errorf("too few arguments for command 'add': got %d, want >0", len(args))
}
id := args[0]
key, err := hex.DecodeString(id)
if err != nil {
return fmt.Errorf("node id must be a hex string: got %q: %v", id, err)
}
node := repl.nodes[id]
node.SetPublicKey(key)
for _, attr := range args[1:] {
kv := strings.Split(attr, ":")
if len(kv) != 2 {
return fmt.Errorf("node attributes must be in the format 'KEY:VALUE': got %q", attr)
}
node.SetAttribute(kv[0], kv[1])
}
repl.nodes[id] = node
return nil
}
func (repl *policyPlaygroundREPL) handleLoad(args []string) error {
if len(args) != 1 {
return fmt.Errorf("too few arguments for command 'add': got %d, want 1", len(args))
}
jsonNetmap := map[string]map[string]string{}
b, err := os.ReadFile(args[0])
if err != nil {
return fmt.Errorf("reading netmap file %q: %v", args[0], err)
}
if err := json.Unmarshal(b, &jsonNetmap); err != nil {
return fmt.Errorf("decoding json netmap: %v", err)
}
repl.nodes = make(map[string]netmap.NodeInfo)
for id, attrs := range jsonNetmap {
key, err := hex.DecodeString(id)
if err != nil {
return fmt.Errorf("node id must be a hex string: got %q: %v", id, err)
}
node := repl.nodes[id]
node.SetPublicKey(key)
for k, v := range attrs {
node.SetAttribute(k, v)
}
repl.nodes[id] = node
}
return nil
}
func (repl *policyPlaygroundREPL) handleRemove(args []string) error {
if len(args) == 0 {
return fmt.Errorf("too few arguments for command 'remove': got %d, want >0", len(args))
}
id := args[0]
if _, exists := repl.nodes[id]; exists {
delete(repl.nodes, id)
return nil
}
return fmt.Errorf("node not found: id=%q", id)
}
func (repl *policyPlaygroundREPL) handleEval(args []string) error {
policyStr := strings.TrimSpace(strings.Join(args, " "))
var nodes [][]netmap.NodeInfo
nm := repl.netMap()
if strings.HasPrefix(policyStr, "CBF") || strings.HasPrefix(policyStr, "SELECT") || strings.HasPrefix(policyStr, "FILTER") {
// Assume that the input is a partial SELECT-FILTER expression.
// Full inline policies always start with UNIQUE or REP keywords,
// or different prefixes when it's the case of an external file.
sfExpr, err := netmap.DecodeSelectFilterString(policyStr)
if err != nil {
return fmt.Errorf("parsing select-filter expression: %v", err)
}
nodes, err = nm.SelectFilterNodes(sfExpr)
if err != nil {
return fmt.Errorf("building select-filter nodes: %v", err)
}
} else {
// Assume that the input is a full policy or input file otherwise.
placementPolicy, err := parseContainerPolicy(repl.cmd, policyStr)
if err != nil {
return fmt.Errorf("parsing placement policy: %v", err)
}
nodes, err = nm.ContainerNodes(*placementPolicy, nil)
if err != nil {
return fmt.Errorf("building container nodes: %v", err)
}
}
for i, ns := range nodes {
var ids []string
for _, node := range ns {
ids = append(ids, hex.EncodeToString(node.PublicKey()))
}
fmt.Printf("\t%2d: %v\n", i+1, ids)
}
return nil
}
func (repl *policyPlaygroundREPL) netMap() netmap.NetMap {
var nm netmap.NetMap
var nodes []netmap.NodeInfo
for _, node := range repl.nodes {
nodes = append(nodes, node)
}
nm.SetNodes(nodes)
return nm
}
func (repl *policyPlaygroundREPL) run() error {
if len(viper.GetString(commonflags.RPC)) > 0 {
key := key.GetOrGenerate(repl.cmd)
cli := internalclient.GetSDKClientByFlag(repl.cmd, key, commonflags.RPC)
var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli)
resp, err := internalclient.NetMapSnapshot(repl.cmd.Context(), prm)
commonCmd.ExitOnErr(repl.cmd, "unable to get netmap snapshot to populate initial netmap: %w", err)
for _, node := range resp.NetMap().Nodes() {
id := hex.EncodeToString(node.PublicKey())
repl.nodes[id] = node
}
}
cmdHandlers := map[string]func([]string) error{
"list": repl.handleLs,
"ls": repl.handleLs,
"add": repl.handleAdd,
"load": repl.handleLoad,
"remove": repl.handleRemove,
"rm": repl.handleRemove,
"eval": repl.handleEval,
}
for reader := bufio.NewReader(os.Stdin); ; {
fmt.Print("> ")
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return nil
}
return fmt.Errorf("reading line: %v", err)
}
parts := strings.Fields(line)
if len(parts) == 0 {
continue
}
cmd := parts[0]
handler, exists := cmdHandlers[cmd]
if exists {
if err := handler(parts[1:]); err != nil {
fmt.Printf("error: %v\n", err)
}
} else {
fmt.Printf("error: unknown command %q\n", cmd)
}
}
}
var policyPlaygroundCmd = &cobra.Command{
Use: "policy-playground",
Short: "A REPL for testing placement policies",
Long: `A REPL for testing placement policies.
If a wallet and endpoint is provided, the initial netmap data will be loaded from the snapshot of the node. Otherwise, an empty playground is created.`,
Run: func(cmd *cobra.Command, args []string) {
repl, err := newPolicyPlaygroundREPL(cmd, args)
commonCmd.ExitOnErr(cmd, "could not create policy playground: %w", err)
commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run())
},
}
func initContainerPolicyPlaygroundCmd() {
commonflags.Init(policyPlaygroundCmd)
}

View file

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

View file

@ -38,7 +38,7 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
if !flagVarsSetEACL.noPreCheck { if !flagVarsSetEACL.noPreCheck {
cmd.Println("Checking the ability to modify access rights in the container...") cmd.Println("Checking the ability to modify access rights in the container...")
extendable, err := internalclient.IsACLExtendable(cli, id) extendable, err := internalclient.IsACLExtendable(cmd.Context(), cli, id)
commonCmd.ExitOnErr(cmd, "Extensibility check failure: %w", err) commonCmd.ExitOnErr(cmd, "Extensibility check failure: %w", err)
if !extendable { if !extendable {
@ -56,7 +56,7 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
setEACLPrm.WithinSession(*tok) setEACLPrm.WithinSession(*tok)
} }
_, err := internalclient.SetEACL(setEACLPrm) _, err := internalclient.SetEACL(cmd.Context(), setEACLPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
if containerAwait { if containerAwait {
@ -72,7 +72,7 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
for i := 0; i < awaitTimeout; i++ { for i := 0; i < awaitTimeout; i++ {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
res, err := internalclient.EACL(getEACLPrm) res, err := internalclient.EACL(cmd.Context(), getEACLPrm)
if err == nil { if err == nil {
// compare binary values because EACL could have been set already // compare binary values because EACL could have been set already
table := res.EACL() table := res.EACL()

View file

@ -11,10 +11,11 @@ import (
const ignoreErrorsFlag = "no-errors" const ignoreErrorsFlag = "no-errors"
var evacuateShardCmd = &cobra.Command{ var evacuateShardCmd = &cobra.Command{
Use: "evacuate", Use: "evacuate",
Short: "Evacuate objects from shard", Short: "Evacuate objects from shard",
Long: "Evacuate objects from shard to other shards", Long: "Evacuate objects from shard to other shards",
Run: evacuateShard, Run: evacuateShard,
Deprecated: "use frostfs-cli control shards evacuation start",
} }
func evacuateShard(cmd *cobra.Command, _ []string) { func evacuateShard(cmd *cobra.Command, _ []string) {

View file

@ -0,0 +1,315 @@
package control
import (
"crypto/ecdsa"
"fmt"
"strings"
"sync/atomic"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
clientSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"github.com/spf13/cobra"
)
const (
awaitFlag = "await"
noProgressFlag = "no-progress"
)
var evacuationShardCmd = &cobra.Command{
Use: "evacuation",
Short: "Objects evacuation from shard",
Long: "Objects evacuation from shard to other shards",
}
var startEvacuationShardCmd = &cobra.Command{
Use: "start",
Short: "Start evacuate objects from shard",
Long: "Start evacuate objects from shard to other shards",
Run: startEvacuateShard,
}
var getEvacuationShardStatusCmd = &cobra.Command{
Use: "status",
Short: "Get evacuate objects from shard status",
Long: "Get evacuate objects from shard to other shards status",
Run: getEvacuateShardStatus,
}
var stopEvacuationShardCmd = &cobra.Command{
Use: "stop",
Short: "Stop running evacuate process",
Long: "Stop running evacuate process from shard to other shards",
Run: stopEvacuateShardStatus,
}
func startEvacuateShard(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag)
req := &control.StartShardEvacuationRequest{
Body: &control.StartShardEvacuationRequest_Body{
Shard_ID: getShardIDList(cmd),
IgnoreErrors: ignoreErrors,
},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.StartShardEvacuationResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.StartShardEvacuation(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "Start evacuate shards failed, rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
cmd.Println("Shard evacuation has been successfully started.")
if awaitCompletion, _ := cmd.Flags().GetBool(awaitFlag); awaitCompletion {
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
waitEvacuateCompletion(cmd, pk, cli, !noProgress, true)
}
}
func getEvacuateShardStatus(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
req := &control.GetShardEvacuationStatusRequest{
Body: &control.GetShardEvacuationStatusRequest_Body{},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.GetShardEvacuationStatusResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.GetShardEvacuationStatus(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "Get evacuate shards status failed, rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
printStatus(cmd, resp)
}
func stopEvacuateShardStatus(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
req := &control.StopShardEvacuationRequest{
Body: &control.StopShardEvacuationRequest_Body{},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.StopShardEvacuationResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.StopShardEvacuation(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "Stop evacuate shards failed, rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
waitEvacuateCompletion(cmd, pk, cli, false, false)
cmd.Println("Evacuation stopped.")
}
func waitEvacuateCompletion(cmd *cobra.Command, pk *ecdsa.PrivateKey, cli *clientSDK.Client, printProgress, printCompleted bool) {
const statusPollingInterval = 1 * time.Second
const reportIntervalSeconds = 5
var resp *control.GetShardEvacuationStatusResponse
reportResponse := new(atomic.Pointer[control.GetShardEvacuationStatusResponse])
pollingCompleted := make(chan struct{})
progressReportCompleted := make(chan struct{})
go func() {
defer close(progressReportCompleted)
if !printProgress {
return
}
cmd.Printf("Progress will be reported every %d seconds.\n", reportIntervalSeconds)
for {
select {
case <-pollingCompleted:
return
case <-time.After(reportIntervalSeconds * time.Second):
r := reportResponse.Load()
if r == nil || r.GetBody().GetStatus() == control.GetShardEvacuationStatusResponse_Body_COMPLETED {
continue
}
printStatus(cmd, r)
}
}
}()
for {
req := &control.GetShardEvacuationStatusRequest{
Body: &control.GetShardEvacuationStatusRequest_Body{},
}
signRequest(cmd, pk, req)
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.GetShardEvacuationStatus(client, req)
return err
})
reportResponse.Store(resp)
if err != nil {
commonCmd.ExitOnErr(cmd, "Failed to get evacuate status, rpc error: %w", err)
return
}
if resp.GetBody().GetStatus() != control.GetShardEvacuationStatusResponse_Body_RUNNING {
break
}
time.Sleep(statusPollingInterval)
}
close(pollingCompleted)
<-progressReportCompleted
if printCompleted {
printCompletedStatusMessage(cmd, resp)
}
}
func printCompletedStatusMessage(cmd *cobra.Command, resp *control.GetShardEvacuationStatusResponse) {
cmd.Println("Shard evacuation has been completed.")
sb := &strings.Builder{}
appendShardIDs(sb, resp)
appendCounts(sb, resp)
appendError(sb, resp)
appendStartedAt(sb, resp)
appendDuration(sb, resp)
cmd.Println(sb.String())
}
func printStatus(cmd *cobra.Command, resp *control.GetShardEvacuationStatusResponse) {
if resp.GetBody().GetStatus() == control.GetShardEvacuationStatusResponse_Body_EVACUATE_SHARD_STATUS_UNDEFINED {
cmd.Println("There is no running or completed evacuation.")
return
}
sb := &strings.Builder{}
appendShardIDs(sb, resp)
appendStatus(sb, resp)
appendCounts(sb, resp)
appendError(sb, resp)
appendStartedAt(sb, resp)
appendDuration(sb, resp)
appendEstimation(sb, resp)
cmd.Println(sb.String())
}
func appendEstimation(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
if resp.GetBody().GetStatus() != control.GetShardEvacuationStatusResponse_Body_RUNNING ||
resp.GetBody().GetDuration() == nil ||
resp.GetBody().GetTotal() == 0 ||
resp.GetBody().GetEvacuated()+resp.GetBody().GetFailed() == 0 {
return
}
durationSeconds := float64(resp.GetBody().GetDuration().GetSeconds())
evacuated := float64(resp.GetBody().GetEvacuated() + resp.GetBody().GetFailed())
avgObjEvacuationTimeSeconds := durationSeconds / evacuated
objectsLeft := float64(resp.GetBody().GetTotal()) - evacuated
leftSeconds := avgObjEvacuationTimeSeconds * objectsLeft
leftMinutes := int(leftSeconds / 60)
sb.WriteString(fmt.Sprintf(" Estimated time left: %d minutes.", leftMinutes))
}
func appendDuration(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
if resp.GetBody().GetDuration() != nil {
duration := time.Second * time.Duration(resp.GetBody().GetDuration().GetSeconds())
hour := int(duration.Seconds() / 3600)
minute := int(duration.Seconds()/60) % 60
second := int(duration.Seconds()) % 60
sb.WriteString(fmt.Sprintf(" Duration: %02d:%02d:%02d.", hour, minute, second))
}
}
func appendStartedAt(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
if resp.GetBody().GetStartedAt() != nil {
startedAt := time.Unix(resp.GetBody().GetStartedAt().GetValue(), 0).UTC()
sb.WriteString(fmt.Sprintf(" Started at: %s UTC.", startedAt.Format(time.RFC3339)))
}
}
func appendError(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
if len(resp.Body.GetErrorMessage()) > 0 {
sb.WriteString(fmt.Sprintf(" Error: %s.", resp.Body.GetErrorMessage()))
}
}
func appendStatus(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
var status string
switch resp.GetBody().GetStatus() {
case control.GetShardEvacuationStatusResponse_Body_COMPLETED:
status = "completed"
case control.GetShardEvacuationStatusResponse_Body_RUNNING:
status = "running"
default:
status = "undefined"
}
sb.WriteString(fmt.Sprintf(" Status: %s.", status))
}
func appendShardIDs(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
sb.WriteString("Shard IDs: ")
for idx, shardID := range resp.GetBody().GetShard_ID() {
shardIDStr := shard.NewIDFromBytes(shardID).String()
if idx > 0 {
sb.WriteString(", ")
}
sb.WriteString(shardIDStr)
if idx == len(resp.GetBody().GetShard_ID())-1 {
sb.WriteString(".")
}
}
}
func appendCounts(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
sb.WriteString(fmt.Sprintf(" Evacuated %d object out of %d, failed to evacuate %d objects.",
resp.GetBody().GetEvacuated(),
resp.Body.GetTotal(),
resp.Body.GetFailed()))
}
func initControlEvacuationShardCmd() {
evacuationShardCmd.AddCommand(startEvacuationShardCmd)
evacuationShardCmd.AddCommand(getEvacuationShardStatusCmd)
evacuationShardCmd.AddCommand(stopEvacuationShardCmd)
initControlStartEvacuationShardCmd()
initControlFlags(getEvacuationShardStatusCmd)
initControlFlags(stopEvacuationShardCmd)
}
func initControlStartEvacuationShardCmd() {
initControlFlags(startEvacuationShardCmd)
flags := startEvacuationShardCmd.Flags()
flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding")
flags.Bool(shardAllFlag, false, "Process all shards")
flags.Bool(ignoreErrorsFlag, true, "Skip invalid/unreadable objects")
flags.Bool(awaitFlag, false, "Block execution until evacuation is completed")
flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag))
startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
}

View file

@ -1,15 +1,10 @@
package control package control
import ( import (
"crypto/ecdsa"
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -19,8 +14,8 @@ const (
var healthCheckCmd = &cobra.Command{ var healthCheckCmd = &cobra.Command{
Use: "healthcheck", Use: "healthcheck",
Short: "Health check of the FrostFS node", Short: "Health check for FrostFS storage nodes",
Long: "Health check of the FrostFS node. Checks storage node by default, use --ir flag to work with Inner Ring.", Long: "Health check for FrostFS storage nodes.",
Run: healthCheck, Run: healthCheck,
} }
@ -29,18 +24,18 @@ func initControlHealthCheckCmd() {
flags := healthCheckCmd.Flags() flags := healthCheckCmd.Flags()
flags.Bool(healthcheckIRFlag, false, "Communicate with IR node") flags.Bool(healthcheckIRFlag, false, "Communicate with IR node")
_ = flags.MarkDeprecated(healthcheckIRFlag, "for health check of inner ring nodes, use the 'control ir healthcheck' command instead.")
} }
func healthCheck(cmd *cobra.Command, _ []string) { func healthCheck(cmd *cobra.Command, args []string) {
pk := key.Get(cmd)
cli := getClient(cmd, pk)
if isIR, _ := cmd.Flags().GetBool(healthcheckIRFlag); isIR { if isIR, _ := cmd.Flags().GetBool(healthcheckIRFlag); isIR {
healthCheckIR(cmd, pk, cli) irHealthCheck(cmd, args)
return return
} }
pk := key.Get(cmd)
cli := getClient(cmd, pk)
req := new(control.HealthCheckRequest) req := new(control.HealthCheckRequest)
req.SetBody(new(control.HealthCheckRequest_Body)) req.SetBody(new(control.HealthCheckRequest_Body))
@ -59,23 +54,3 @@ func healthCheck(cmd *cobra.Command, _ []string) {
cmd.Printf("Network status: %s\n", resp.GetBody().GetNetmapStatus()) cmd.Printf("Network status: %s\n", resp.GetBody().GetNetmapStatus())
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus()) cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
} }
func healthCheckIR(cmd *cobra.Command, key *ecdsa.PrivateKey, c *client.Client) {
req := new(ircontrol.HealthCheckRequest)
req.SetBody(new(ircontrol.HealthCheckRequest_Body))
err := ircontrolsrv.SignMessage(key, req)
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
var resp *ircontrol.HealthCheckResponse
err = c.ExecRaw(func(client *rawclient.Client) error {
resp, err = ircontrol.HealthCheck(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
}

View file

@ -10,6 +10,10 @@ var irCmd = &cobra.Command{
func initControlIRCmd() { func initControlIRCmd() {
irCmd.AddCommand(tickEpochCmd) irCmd.AddCommand(tickEpochCmd)
irCmd.AddCommand(removeNodeCmd)
irCmd.AddCommand(irHealthCheckCmd)
initControlIRTickEpochCmd() initControlIRTickEpochCmd()
initControlIRRemoveNodeCmd()
initControlIRHealthCheckCmd()
} }

View file

@ -0,0 +1,44 @@
package control
import (
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
"github.com/spf13/cobra"
)
var irHealthCheckCmd = &cobra.Command{
Use: "healthcheck",
Short: "Health check for FrostFS inner ring nodes",
Long: "Health check for FrostFS inner ring nodes.",
Run: irHealthCheck,
}
func initControlIRHealthCheckCmd() {
initControlFlags(irHealthCheckCmd)
}
func irHealthCheck(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
cli := getClient(cmd, pk)
req := new(ircontrol.HealthCheckRequest)
req.SetBody(new(ircontrol.HealthCheckRequest_Body))
err := ircontrolsrv.SignMessage(pk, req)
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
var resp *ircontrol.HealthCheckResponse
err = cli.ExecRaw(func(client *rawclient.Client) error {
resp, err = ircontrol.HealthCheck(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
}

View file

@ -0,0 +1,58 @@
package control
import (
"encoding/hex"
"errors"
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
"github.com/spf13/cobra"
)
var removeNodeCmd = &cobra.Command{
Use: "remove-node",
Short: "Forces a node removal from netmap",
Long: "Forces a node removal from netmap via a notary request. It should be executed on other IR nodes as well.",
Run: removeNode,
}
func initControlIRRemoveNodeCmd() {
initControlFlags(removeNodeCmd)
flags := removeNodeCmd.Flags()
flags.String("node", "", "Node public key as a hex string")
_ = removeNodeCmd.MarkFlagRequired("node")
}
func removeNode(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
c := getClient(cmd, pk)
nodeKeyStr, _ := cmd.Flags().GetString("node")
if len(nodeKeyStr) == 0 {
commonCmd.ExitOnErr(cmd, "parsing node public key: ", errors.New("key cannot be empty"))
}
nodeKey, err := hex.DecodeString(nodeKeyStr)
commonCmd.ExitOnErr(cmd, "can't decode node public key: %w", err)
req := new(ircontrol.RemoveNodeRequest)
req.SetBody(&ircontrol.RemoveNodeRequest_Body{
Key: nodeKey,
})
commonCmd.ExitOnErr(cmd, "could not sign request: %w", ircontrolsrv.SignMessage(pk, req))
var resp *ircontrol.RemoveNodeResponse
err = c.ExecRaw(func(client *rawclient.Client) error {
resp, err = ircontrol.RemoveNode(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
cmd.Println("Node removed")
}

View file

@ -13,13 +13,14 @@ var shardsCmd = &cobra.Command{
func initControlShardsCmd() { func initControlShardsCmd() {
shardsCmd.AddCommand(listShardsCmd) shardsCmd.AddCommand(listShardsCmd)
shardsCmd.AddCommand(setShardModeCmd) shardsCmd.AddCommand(setShardModeCmd)
shardsCmd.AddCommand(evacuateShardCmd) shardsCmd.AddCommand(evacuationShardCmd)
shardsCmd.AddCommand(flushCacheCmd) shardsCmd.AddCommand(flushCacheCmd)
shardsCmd.AddCommand(doctorCmd) shardsCmd.AddCommand(doctorCmd)
initControlShardsListCmd() initControlShardsListCmd()
initControlSetShardModeCmd() initControlSetShardModeCmd()
initControlEvacuateShardCmd() initControlEvacuateShardCmd()
initControlEvacuationShardCmd()
initControlFlushCacheCmd() initControlFlushCacheCmd()
initControlDoctorCmd() initControlDoctorCmd()
} }

View file

@ -40,7 +40,7 @@ func verifyResponse(cmd *cobra.Command,
commonCmd.ExitOnErr(cmd, "", errors.New("missing response signature")) commonCmd.ExitOnErr(cmd, "", errors.New("missing response signature"))
} }
// TODO(@cthulhu-rider): #1387 use Signature message from NeoFS API to avoid conversion // TODO(@cthulhu-rider): #468 use Signature message from FrostFS API to avoid conversion
var sigV2 refs.Signature var sigV2 refs.Signature
sigV2.SetScheme(refs.ECDSA_SHA512) sigV2.SetScheme(refs.ECDSA_SHA512)
sigV2.SetKey(sigControl.GetKey()) sigV2.SetKey(sigControl.GetKey())

View file

@ -19,7 +19,7 @@ var getEpochCmd = &cobra.Command{
var prm internalclient.NetworkInfoPrm var prm internalclient.NetworkInfoPrm
prm.SetClient(cli) prm.SetClient(cli)
res, err := internalclient.NetworkInfo(prm) res, err := internalclient.NetworkInfo(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
netInfo := res.NetworkInfo() netInfo := res.NetworkInfo()

View file

@ -23,7 +23,7 @@ var netInfoCmd = &cobra.Command{
var prm internalclient.NetworkInfoPrm var prm internalclient.NetworkInfoPrm
prm.SetClient(cli) prm.SetClient(cli)
res, err := internalclient.NetworkInfo(prm) res, err := internalclient.NetworkInfo(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
netInfo := res.NetworkInfo() netInfo := res.NetworkInfo()

View file

@ -25,7 +25,7 @@ var nodeInfoCmd = &cobra.Command{
var prm internalclient.NodeInfoPrm var prm internalclient.NodeInfoPrm
prm.SetClient(cli) prm.SetClient(cli)
res, err := internalclient.NodeInfo(prm) res, err := internalclient.NodeInfo(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
prettyPrintNodeInfo(cmd, res.NodeInfo()) prettyPrintNodeInfo(cmd, res.NodeInfo())

View file

@ -1,6 +1,7 @@
package netmap package netmap
import ( import (
"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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -14,7 +15,9 @@ var Cmd = &cobra.Command{
// the viper before execution // the viper before execution
commonflags.Bind(cmd) commonflags.Bind(cmd)
commonflags.BindAPI(cmd) commonflags.BindAPI(cmd)
common.StartClientCommandSpan(cmd)
}, },
PersistentPostRun: common.StopClientCommandSpan,
} }
func init() { func init() {

View file

@ -19,7 +19,7 @@ var snapshotCmd = &cobra.Command{
var prm internalclient.NetMapSnapshotPrm var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli) prm.SetClient(cli)
res, err := internalclient.NetMapSnapshot(prm) res, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
commonCmd.PrettyPrintNetMap(cmd, res.NetMap(), false) commonCmd.PrettyPrintNetMap(cmd, res.NetMap(), false)

View file

@ -65,7 +65,7 @@ func deleteObject(cmd *cobra.Command, _ []string) {
Prepare(cmd, &prm) Prepare(cmd, &prm)
prm.SetAddress(objAddr) prm.SetAddress(objAddr)
res, err := internalclient.DeleteObject(prm) res, err := internalclient.DeleteObject(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
tomb := res.Tombstone() tomb := res.Tombstone()

View file

@ -11,7 +11,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
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/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"
"github.com/cheggaaa/pb" "github.com/cheggaaa/pb"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -84,13 +84,13 @@ func getObject(cmd *cobra.Command, _ []string) {
p = pb.New64(0) p = pb.New64(0)
p.Output = cmd.OutOrStdout() p.Output = cmd.OutOrStdout()
prm.SetPayloadWriter(p.NewProxyWriter(payloadWriter)) prm.SetPayloadWriter(p.NewProxyWriter(payloadWriter))
prm.SetHeaderCallback(func(o *object.Object) { prm.SetHeaderCallback(func(o *objectSDK.Object) {
p.SetTotal64(int64(o.PayloadSize())) p.SetTotal64(int64(o.PayloadSize()))
p.Start() p.Start()
}) })
} }
res, err := internalclient.GetObject(prm) res, err := internalclient.GetObject(cmd.Context(), prm)
if p != nil { if p != nil {
p.Finish() p.Finish()
} }

View file

@ -75,7 +75,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
headPrm.SetAddress(objAddr) headPrm.SetAddress(objAddr)
// get hash of full payload through HEAD (may be user can do it through dedicated command?) // get hash of full payload through HEAD (may be user can do it through dedicated command?)
res, err := internalclient.HeadObject(headPrm) res, err := internalclient.HeadObject(cmd.Context(), headPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
var cs checksum.Checksum var cs checksum.Checksum
@ -108,7 +108,7 @@ func getObjectHash(cmd *cobra.Command, _ []string) {
hashPrm.TZ() hashPrm.TZ()
} }
res, err := internalclient.HashPayloadRanges(hashPrm) res, err := internalclient.HashPayloadRanges(cmd.Context(), hashPrm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
hs := res.HashList() hs := res.HashList()

View file

@ -13,7 +13,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
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/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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -64,7 +64,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
prm.SetAddress(objAddr) prm.SetAddress(objAddr)
prm.SetMainOnlyFlag(mainOnly) prm.SetMainOnlyFlag(mainOnly)
res, err := internalclient.HeadObject(prm) res, err := internalclient.HeadObject(cmd.Context(), prm)
if err != nil { if err != nil {
if ok := printSplitInfoErr(cmd, err); ok { if ok := printSplitInfoErr(cmd, err); ok {
return return
@ -77,7 +77,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
commonCmd.ExitOnErr(cmd, "", err) commonCmd.ExitOnErr(cmd, "", err)
} }
func saveAndPrintHeader(cmd *cobra.Command, obj *object.Object, filename string) error { func saveAndPrintHeader(cmd *cobra.Command, obj *objectSDK.Object, filename string) error {
bs, err := marshalHeader(cmd, obj) bs, err := marshalHeader(cmd, obj)
if err != nil { if err != nil {
return fmt.Errorf("could not marshal header: %w", err) return fmt.Errorf("could not marshal header: %w", err)
@ -97,7 +97,7 @@ func saveAndPrintHeader(cmd *cobra.Command, obj *object.Object, filename string)
return printHeader(cmd, obj) return printHeader(cmd, obj)
} }
func marshalHeader(cmd *cobra.Command, hdr *object.Object) ([]byte, error) { func marshalHeader(cmd *cobra.Command, hdr *objectSDK.Object) ([]byte, error) {
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON) toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
toProto, _ := cmd.Flags().GetBool("proto") toProto, _ := cmd.Flags().GetBool("proto")
switch { switch {
@ -138,7 +138,7 @@ func printContainerID(cmd *cobra.Command, recv func() (cid.ID, bool)) {
cmd.Printf("CID: %s\n", strID) cmd.Printf("CID: %s\n", strID)
} }
func printHeader(cmd *cobra.Command, obj *object.Object) error { func printHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
printObjectID(cmd, obj.ID) printObjectID(cmd, obj.ID)
printContainerID(cmd, obj.ContainerID) printContainerID(cmd, obj.ContainerID)
cmd.Printf("Owner: %s\n", obj.OwnerID()) cmd.Printf("Owner: %s\n", obj.OwnerID())
@ -150,7 +150,7 @@ func printHeader(cmd *cobra.Command, obj *object.Object) error {
cmd.Println("Attributes:") cmd.Println("Attributes:")
for _, attr := range obj.Attributes() { for _, attr := range obj.Attributes() {
if attr.Key() == object.AttributeTimestamp { if attr.Key() == objectSDK.AttributeTimestamp {
cmd.Printf(" %s=%s (%s)\n", cmd.Printf(" %s=%s (%s)\n",
attr.Key(), attr.Key(),
attr.Value(), attr.Value(),
@ -163,7 +163,7 @@ func printHeader(cmd *cobra.Command, obj *object.Object) error {
if signature := obj.Signature(); signature != nil { if signature := obj.Signature(); signature != nil {
cmd.Print("ID signature:\n") cmd.Print("ID signature:\n")
// TODO(@carpawell): #1387 implement and use another approach to avoid conversion // TODO(@carpawell): #468 implement and use another approach to avoid conversion
var sigV2 refs.Signature var sigV2 refs.Signature
signature.WriteToV2(&sigV2) signature.WriteToV2(&sigV2)
@ -174,7 +174,7 @@ func printHeader(cmd *cobra.Command, obj *object.Object) error {
return printSplitHeader(cmd, obj) return printSplitHeader(cmd, obj)
} }
func printSplitHeader(cmd *cobra.Command, obj *object.Object) error { func printSplitHeader(cmd *cobra.Command, obj *objectSDK.Object) error {
if splitID := obj.SplitID(); splitID != nil { if splitID := obj.SplitID(); splitID != nil {
cmd.Printf("Split ID: %s\n", splitID) cmd.Printf("Split ID: %s\n", splitID)
} }

View file

@ -104,7 +104,7 @@ var objectLockCmd = &cobra.Command{
Prepare(cmd, &prm) Prepare(cmd, &prm)
prm.SetHeader(obj) prm.SetHeader(obj)
res, err := internalclient.PutObject(prm) res, err := internalclient.PutObject(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "Store lock object in FrostFS: %w", err) commonCmd.ExitOnErr(cmd, "Store lock object in FrostFS: %w", err)
cmd.Printf("Lock object ID: %s\n", res.ID()) cmd.Printf("Lock object ID: %s\n", res.ID())

View file

@ -0,0 +1,348 @@
package object
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"strconv"
"sync"
"text/tabwriter"
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object_manager/placement"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)
const (
verifyPresenceAllFlag = "verify-presence-all"
)
type objectNodesInfo struct {
containerID cid.ID
objectID oid.ID
relatedObjectIDs []oid.ID
isLock bool
}
type boolError struct {
value bool
err error
}
var objectNodesCmd = &cobra.Command{
Use: "nodes",
Short: "List of nodes where the object is stored",
Long: `List of nodes where the object should be stored and where it is actually stored.
Lock objects must exist on all nodes of the container.
For complex objects, a node is considered to store an object if the node stores at least one part of the complex object.
By default, the actual storage of the object is checked only on the nodes that should store the object. To check all nodes, use the flag --verify-presence-all.`,
Run: objectNodes,
}
func initObjectNodesCmd() {
commonflags.Init(objectNodesCmd)
flags := objectNodesCmd.Flags()
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
_ = objectGetCmd.MarkFlagRequired(commonflags.CIDFlag)
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
_ = objectGetCmd.MarkFlagRequired(commonflags.OIDFlag)
flags.Bool("verify-presence-all", false, "Verify the actual presence of the object on all netmap nodes")
}
func objectNodes(cmd *cobra.Command, _ []string) {
var cnrID cid.ID
var objID oid.ID
readObjectAddress(cmd, &cnrID, &objID)
pk := key.GetOrGenerate(cmd)
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
objectInfo := getObjectInfo(cmd, cnrID, objID, cli, pk)
placementPolicy, netmap := getPlacementPolicyAndNetmap(cmd, cnrID, cli)
requiredPlacement := getRequiredPlacement(cmd, objectInfo, placementPolicy, netmap)
actualPlacement := getActualPlacement(cmd, netmap, requiredPlacement, pk, objectInfo)
printPlacement(cmd, netmap, requiredPlacement, actualPlacement)
}
func getObjectInfo(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) *objectNodesInfo {
var addrObj oid.Address
addrObj.SetContainer(cnrID)
addrObj.SetObject(objID)
var prmHead internalclient.HeadObjectPrm
prmHead.SetClient(cli)
prmHead.SetAddress(addrObj)
prmHead.SetRawFlag(true)
Prepare(cmd, &prmHead)
readSession(cmd, &prmHead, pk, cnrID, objID)
res, err := internalclient.HeadObject(cmd.Context(), prmHead)
if err == nil {
return &objectNodesInfo{
containerID: cnrID,
objectID: objID,
isLock: res.Header().Type() == objectSDK.TypeLock,
}
}
var errSplitInfo *objectSDK.SplitInfoError
if !errors.As(err, &errSplitInfo) {
commonCmd.ExitOnErr(cmd, "failed to get object info: %w", err)
return nil
}
splitInfo := errSplitInfo.SplitInfo()
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnrID); ok {
return &objectNodesInfo{
containerID: cnrID,
objectID: objID,
relatedObjectIDs: members,
}
}
if members, ok := tryGetSplitMembersBySplitID(cmd, splitInfo, cli, cnrID); ok {
return &objectNodesInfo{
containerID: cnrID,
objectID: objID,
relatedObjectIDs: members,
}
}
members := tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnrID, objID)
return &objectNodesInfo{
containerID: cnrID,
objectID: objID,
relatedObjectIDs: members,
}
}
func getPlacementPolicyAndNetmap(cmd *cobra.Command, cnrID cid.ID, cli *client.Client) (placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) {
eg, egCtx := errgroup.WithContext(cmd.Context())
eg.Go(func() (e error) {
placementPolicy, e = getPlacementPolicy(egCtx, cnrID, cli)
return
})
eg.Go(func() (e error) {
netmap, e = getNetMap(egCtx, cli)
return
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", eg.Wait())
return
}
func getPlacementPolicy(ctx context.Context, cnrID cid.ID, cli *client.Client) (netmapSDK.PlacementPolicy, error) {
var prm internalclient.GetContainerPrm
prm.SetClient(cli)
prm.SetContainer(cnrID)
res, err := internalclient.GetContainer(ctx, prm)
if err != nil {
return netmapSDK.PlacementPolicy{}, err
}
return res.Container().PlacementPolicy(), nil
}
func getNetMap(ctx context.Context, cli *client.Client) (*netmapSDK.NetMap, error) {
var prm internalclient.NetMapSnapshotPrm
prm.SetClient(cli)
res, err := internalclient.NetMapSnapshot(ctx, prm)
if err != nil {
return nil, err
}
nm := res.NetMap()
return &nm, nil
}
func getRequiredPlacement(cmd *cobra.Command, objInfo *objectNodesInfo, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) map[uint64]netmapSDK.NodeInfo {
nodes := make(map[uint64]netmapSDK.NodeInfo)
placementBuilder := placement.NewNetworkMapBuilder(netmap)
placement, err := placementBuilder.BuildPlacement(objInfo.containerID, &objInfo.objectID, placementPolicy)
commonCmd.ExitOnErr(cmd, "failed to get required placement: %w", err)
for repIdx, rep := range placement {
numOfReplicas := placementPolicy.ReplicaNumberByIndex(repIdx)
var nodeIdx uint32
for _, n := range rep {
if !objInfo.isLock && nodeIdx == numOfReplicas { //lock object should be on all container nodes
break
}
nodes[n.Hash()] = n
nodeIdx++
}
}
for _, relatedObjID := range objInfo.relatedObjectIDs {
placement, err = placementBuilder.BuildPlacement(objInfo.containerID, &relatedObjID, placementPolicy)
commonCmd.ExitOnErr(cmd, "failed to get required placement for related object: %w", err)
for _, rep := range placement {
for _, n := range rep {
nodes[n.Hash()] = n
}
}
}
return nodes
}
func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo,
pk *ecdsa.PrivateKey, objInfo *objectNodesInfo) map[uint64]boolError {
result := make(map[uint64]boolError)
resultMtx := &sync.Mutex{}
var candidates []netmapSDK.NodeInfo
checkAllNodes, _ := cmd.Flags().GetBool(verifyPresenceAllFlag)
if checkAllNodes {
candidates = netmap.Nodes()
} else {
for _, n := range requiredPlacement {
candidates = append(candidates, n)
}
}
eg, egCtx := errgroup.WithContext(cmd.Context())
for _, cand := range candidates {
cand := cand
eg.Go(func() error {
cli, err := createClient(egCtx, cmd, cand, pk)
if err != nil {
resultMtx.Lock()
defer resultMtx.Unlock()
result[cand.Hash()] = boolError{err: err}
return nil
}
eg.Go(func() error {
var v boolError
v.value, v.err = isObjectStoredOnNode(egCtx, cmd, objInfo.containerID, objInfo.objectID, cli, pk)
resultMtx.Lock()
defer resultMtx.Unlock()
if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) {
return nil
}
result[cand.Hash()] = v
return nil
})
for _, rObjID := range objInfo.relatedObjectIDs {
rObjID := rObjID
eg.Go(func() error {
var v boolError
v.value, v.err = isObjectStoredOnNode(egCtx, cmd, objInfo.containerID, rObjID, cli, pk)
resultMtx.Lock()
defer resultMtx.Unlock()
if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) {
return nil
}
result[cand.Hash()] = v
return nil
})
}
return nil
})
}
commonCmd.ExitOnErr(cmd, "failed to get actual placement: %w", eg.Wait())
return result
}
func createClient(ctx context.Context, cmd *cobra.Command, candidate netmapSDK.NodeInfo, pk *ecdsa.PrivateKey) (*client.Client, error) {
var cli *client.Client
var addresses []string
candidate.IterateNetworkEndpoints(func(s string) bool {
addresses = append(addresses, s)
return false
})
addresses = append(addresses, candidate.ExternalAddresses()...)
var lastErr error
for _, address := range addresses {
var networkAddr network.Address
lastErr = networkAddr.FromString(address)
if lastErr != nil {
continue
}
cli, lastErr = internalclient.GetSDKClient(ctx, cmd, pk, networkAddr)
if lastErr == nil {
break
}
}
if lastErr != nil {
return nil, lastErr
}
if cli == nil {
return nil, fmt.Errorf("failed to create client: no available endpoint")
}
return cli, nil
}
func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) (bool, error) {
var addrObj oid.Address
addrObj.SetContainer(cnrID)
addrObj.SetObject(objID)
var prmHead internalclient.HeadObjectPrm
prmHead.SetClient(cli)
prmHead.SetAddress(addrObj)
Prepare(cmd, &prmHead)
prmHead.SetTTL(1)
readSession(cmd, &prmHead, pk, cnrID, objID)
res, err := internalclient.HeadObject(ctx, prmHead)
if err == nil && res != nil {
return true, nil
}
var notFound *apistatus.ObjectNotFound
var removed *apistatus.ObjectAlreadyRemoved
if errors.As(err, &notFound) || errors.As(err, &removed) {
return false, nil
}
return false, err
}
func printPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo, actualPlacement map[uint64]boolError) {
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 1, ' ', tabwriter.AlignRight|tabwriter.Debug)
defer func() {
commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", w.Flush())
}()
fmt.Fprintln(w, "Node ID\tShould contain object\tActually contains object\t")
for _, n := range netmap.Nodes() {
nodeID := hex.EncodeToString(n.PublicKey())
_, required := requiredPlacement[n.Hash()]
actual, actualExists := actualPlacement[n.Hash()]
actualStr := ""
if actualExists {
if actual.err != nil {
actualStr = fmt.Sprintf("error: %v", actual.err)
} else {
actualStr = strconv.FormatBool(actual.value)
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t\n", nodeID, strconv.FormatBool(required), actualStr)
}
}

View file

@ -16,15 +16,17 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
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/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/cheggaaa/pb" "github.com/cheggaaa/pb"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const ( const (
noProgressFlag = "no-progress" noProgressFlag = "no-progress"
notificationFlag = "notify" notificationFlag = "notify"
copiesNumberFlag = "copies-number"
prepareLocallyFlag = "prepare-locally"
) )
var putExpiredOn uint64 var putExpiredOn uint64
@ -53,9 +55,12 @@ func initObjectPutCmd() {
flags.Bool("disable-timestamp", false, "Do not set well-known timestamp attribute") flags.Bool("disable-timestamp", false, "Do not set well-known timestamp attribute")
flags.Uint64VarP(&putExpiredOn, commonflags.ExpireAt, "e", 0, "The last active epoch in the life of the object") flags.Uint64VarP(&putExpiredOn, commonflags.ExpireAt, "e", 0, "The last active epoch in the life of the object")
flags.Bool(noProgressFlag, false, "Do not show progress bar") flags.Bool(noProgressFlag, false, "Do not show progress bar")
flags.Bool(prepareLocallyFlag, false, "Generate object header on the client side (for big object - split locally too)")
flags.String(notificationFlag, "", "Object notification in the form of *epoch*:*topic*; '-' topic means using default") flags.String(notificationFlag, "", "Object notification in the form of *epoch*:*topic*; '-' topic means using default")
flags.Bool(binaryFlag, false, "Deserialize object structure from given file.") flags.Bool(binaryFlag, false, "Deserialize object structure from given file.")
flags.String(copiesNumberFlag, "", "Number of copies of the object to store within the RPC call")
} }
func putObject(cmd *cobra.Command, _ []string) { func putObject(cmd *cobra.Command, _ []string) {
@ -76,7 +81,7 @@ func putObject(cmd *cobra.Command, _ []string) {
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err)) commonCmd.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
} }
var payloadReader io.Reader = f var payloadReader io.Reader = f
obj := object.New() obj := objectSDK.New()
if binary { if binary {
payloadReader, cnr, ownerID = readFilePayload(filename, cmd) payloadReader, cnr, ownerID = readFilePayload(filename, cmd)
@ -99,7 +104,12 @@ func putObject(cmd *cobra.Command, _ []string) {
} }
var prm internalclient.PutObjectPrm var prm internalclient.PutObjectPrm
ReadOrOpenSession(cmd, &prm, pk, cnr, nil) if prepareLocally, _ := cmd.Flags().GetBool(prepareLocallyFlag); prepareLocally {
prm.SetClient(internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC))
prm.PrepareLocally()
} else {
ReadOrOpenSession(cmd, &prm, pk, cnr, nil)
}
Prepare(cmd, &prm) Prepare(cmd, &prm)
prm.SetHeader(obj) prm.SetHeader(obj)
@ -116,7 +126,11 @@ func putObject(cmd *cobra.Command, _ []string) {
} }
} }
res, err := internalclient.PutObject(prm) copyNum, err := cmd.Flags().GetString(copiesNumberFlag)
commonCmd.ExitOnErr(cmd, "can't parse object copies numbers information: %w", err)
prm.SetCopiesNumberByVectors(parseCopyNumber(cmd, copyNum))
res, err := internalclient.PutObject(cmd.Context(), prm)
if p != nil { if p != nil {
p.Finish() p.Finish()
} }
@ -126,10 +140,22 @@ func putObject(cmd *cobra.Command, _ []string) {
cmd.Printf(" OID: %s\n CID: %s\n", res.ID(), cnr) cmd.Printf(" OID: %s\n CID: %s\n", res.ID(), cnr)
} }
func parseCopyNumber(cmd *cobra.Command, copyNum string) []uint32 {
var cn []uint32
if len(copyNum) > 0 {
for _, num := range strings.Split(copyNum, ",") {
val, err := strconv.ParseUint(num, 10, 32)
commonCmd.ExitOnErr(cmd, "can't parse object copies numbers information: %w", err)
cn = append(cn, uint32(val))
}
}
return cn
}
func readFilePayload(filename string, cmd *cobra.Command) (io.Reader, cid.ID, user.ID) { func readFilePayload(filename string, cmd *cobra.Command) (io.Reader, cid.ID, user.ID) {
buf, err := os.ReadFile(filename) buf, err := os.ReadFile(filename)
commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err) commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err)
objTemp := object.New() objTemp := objectSDK.New()
// TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload // TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload
commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf)) commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
payloadReader := bytes.NewReader(objTemp.Payload()) payloadReader := bytes.NewReader(objTemp.Payload())
@ -148,19 +174,19 @@ func setFilePayloadReader(cmd *cobra.Command, f *os.File, prm *internalclient.Pu
p := pb.New64(fi.Size()) p := pb.New64(fi.Size())
p.Output = cmd.OutOrStdout() p.Output = cmd.OutOrStdout()
prm.SetPayloadReader(p.NewProxyReader(f)) prm.SetPayloadReader(p.NewProxyReader(f))
prm.SetHeaderCallback(func(o *object.Object) { p.Start() }) prm.SetHeaderCallback(func(o *objectSDK.Object) { p.Start() })
return p return p
} }
func setBinaryPayloadReader(cmd *cobra.Command, obj *object.Object, prm *internalclient.PutObjectPrm, payloadReader io.Reader) *pb.ProgressBar { func setBinaryPayloadReader(cmd *cobra.Command, obj *objectSDK.Object, prm *internalclient.PutObjectPrm, payloadReader io.Reader) *pb.ProgressBar {
p := pb.New(len(obj.Payload())) p := pb.New(len(obj.Payload()))
p.Output = cmd.OutOrStdout() p.Output = cmd.OutOrStdout()
prm.SetPayloadReader(p.NewProxyReader(payloadReader)) prm.SetPayloadReader(p.NewProxyReader(payloadReader))
prm.SetHeaderCallback(func(o *object.Object) { p.Start() }) prm.SetHeaderCallback(func(o *objectSDK.Object) { p.Start() })
return p return p
} }
func getAllObjectAttributes(cmd *cobra.Command) []object.Attribute { func getAllObjectAttributes(cmd *cobra.Command) []objectSDK.Attribute {
attrs, err := parseObjectAttrs(cmd) attrs, err := parseObjectAttrs(cmd)
commonCmd.ExitOnErr(cmd, "can't parse object attributes: %w", err) commonCmd.ExitOnErr(cmd, "can't parse object attributes: %w", err)
@ -179,7 +205,7 @@ func getAllObjectAttributes(cmd *cobra.Command) []object.Attribute {
if !expAttrFound { if !expAttrFound {
index := len(attrs) index := len(attrs)
attrs = append(attrs, object.Attribute{}) attrs = append(attrs, objectSDK.Attribute{})
attrs[index].SetKey(objectV2.SysAttributeExpEpoch) attrs[index].SetKey(objectV2.SysAttributeExpEpoch)
attrs[index].SetValue(expAttrValue) attrs[index].SetValue(expAttrValue)
} }
@ -187,7 +213,7 @@ func getAllObjectAttributes(cmd *cobra.Command) []object.Attribute {
return attrs return attrs
} }
func parseObjectAttrs(cmd *cobra.Command) ([]object.Attribute, error) { func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
var rawAttrs []string var rawAttrs []string
raw := cmd.Flag("attributes").Value.String() raw := cmd.Flag("attributes").Value.String()
@ -195,7 +221,7 @@ func parseObjectAttrs(cmd *cobra.Command) ([]object.Attribute, error) {
rawAttrs = strings.Split(raw, ",") rawAttrs = strings.Split(raw, ",")
} }
attrs := make([]object.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
for i := range rawAttrs { for i := range rawAttrs {
k, v, found := strings.Cut(rawAttrs[i], "=") k, v, found := strings.Cut(rawAttrs[i], "=")
if !found { if !found {
@ -209,23 +235,23 @@ func parseObjectAttrs(cmd *cobra.Command) ([]object.Attribute, error) {
if !disableFilename { if !disableFilename {
filename := filepath.Base(cmd.Flag(fileFlag).Value.String()) filename := filepath.Base(cmd.Flag(fileFlag).Value.String())
index := len(attrs) index := len(attrs)
attrs = append(attrs, object.Attribute{}) attrs = append(attrs, objectSDK.Attribute{})
attrs[index].SetKey(object.AttributeFileName) attrs[index].SetKey(objectSDK.AttributeFileName)
attrs[index].SetValue(filename) attrs[index].SetValue(filename)
} }
disableTime, _ := cmd.Flags().GetBool("disable-timestamp") disableTime, _ := cmd.Flags().GetBool("disable-timestamp")
if !disableTime { if !disableTime {
index := len(attrs) index := len(attrs)
attrs = append(attrs, object.Attribute{}) attrs = append(attrs, objectSDK.Attribute{})
attrs[index].SetKey(object.AttributeTimestamp) attrs[index].SetKey(objectSDK.AttributeTimestamp)
attrs[index].SetValue(strconv.FormatInt(time.Now().Unix(), 10)) attrs[index].SetValue(strconv.FormatInt(time.Now().Unix(), 10))
} }
return attrs, nil return attrs, nil
} }
func parseObjectNotifications(cmd *cobra.Command) (*object.NotificationInfo, error) { func parseObjectNotifications(cmd *cobra.Command) (*objectSDK.NotificationInfo, error) {
const ( const (
separator = ":" separator = ":"
useDefaultTopic = "-" useDefaultTopic = "-"
@ -241,7 +267,7 @@ func parseObjectNotifications(cmd *cobra.Command) (*object.NotificationInfo, err
return nil, fmt.Errorf("notification must be in the form of: *epoch*%s*topic*, got %s", separator, raw) return nil, fmt.Errorf("notification must be in the form of: *epoch*%s*topic*, got %s", separator, raw)
} }
ni := new(object.NotificationInfo) ni := new(objectSDK.NotificationInfo)
epoch, err := strconv.ParseUint(before, 10, 64) epoch, err := strconv.ParseUint(before, 10, 64)
if err != nil { if err != nil {

View file

@ -14,7 +14,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
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/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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -87,7 +87,7 @@ func getObjectRange(cmd *cobra.Command, _ []string) {
prm.SetRange(ranges[0]) prm.SetRange(ranges[0])
prm.SetPayloadWriter(out) prm.SetPayloadWriter(out)
_, err = internalclient.PayloadRange(prm) _, err = internalclient.PayloadRange(cmd.Context(), prm)
if err != nil { if err != nil {
if ok := printSplitInfoErr(cmd, err); ok { if ok := printSplitInfoErr(cmd, err); ok {
return return
@ -102,7 +102,7 @@ func getObjectRange(cmd *cobra.Command, _ []string) {
} }
func printSplitInfoErr(cmd *cobra.Command, err error) bool { func printSplitInfoErr(cmd *cobra.Command, err error) bool {
var errSplitInfo *object.SplitInfoError var errSplitInfo *objectSDK.SplitInfoError
ok := errors.As(err, &errSplitInfo) ok := errors.As(err, &errSplitInfo)
@ -114,14 +114,14 @@ func printSplitInfoErr(cmd *cobra.Command, err error) bool {
return ok return ok
} }
func printSplitInfo(cmd *cobra.Command, info *object.SplitInfo) { func printSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) {
bs, err := marshalSplitInfo(cmd, info) bs, err := marshalSplitInfo(cmd, info)
commonCmd.ExitOnErr(cmd, "can't marshal split info: %w", err) commonCmd.ExitOnErr(cmd, "can't marshal split info: %w", err)
cmd.Println(string(bs)) cmd.Println(string(bs))
} }
func marshalSplitInfo(cmd *cobra.Command, info *object.SplitInfo) ([]byte, error) { func marshalSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) ([]byte, error) {
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON) toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
toProto, _ := cmd.Flags().GetBool("proto") toProto, _ := cmd.Flags().GetBool("proto")
switch { switch {
@ -146,13 +146,13 @@ func marshalSplitInfo(cmd *cobra.Command, info *object.SplitInfo) ([]byte, error
} }
} }
func getRangeList(cmd *cobra.Command) ([]*object.Range, error) { func getRangeList(cmd *cobra.Command) ([]*objectSDK.Range, error) {
v := cmd.Flag("range").Value.String() v := cmd.Flag("range").Value.String()
if len(v) == 0 { if len(v) == 0 {
return nil, nil return nil, nil
} }
vs := strings.Split(v, ",") vs := strings.Split(v, ",")
rs := make([]*object.Range, len(vs)) rs := make([]*objectSDK.Range, len(vs))
for i := range vs { for i := range vs {
before, after, found := strings.Cut(vs[i], rangeSep) before, after, found := strings.Cut(vs[i], rangeSep)
if !found { if !found {
@ -176,7 +176,7 @@ func getRangeList(cmd *cobra.Command) ([]*object.Range, error) {
return nil, fmt.Errorf("invalid '%s' range: uint64 overflow", vs[i]) return nil, fmt.Errorf("invalid '%s' range: uint64 overflow", vs[i])
} }
rs[i] = object.NewRange() rs[i] = objectSDK.NewRange()
rs[i].SetOffset(offset) rs[i].SetOffset(offset)
rs[i].SetLength(length) rs[i].SetLength(length)
} }

View file

@ -1,6 +1,7 @@
package object package object
import ( import (
"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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,7 +16,9 @@ var Cmd = &cobra.Command{
// the viper before execution // the viper before execution
commonflags.Bind(cmd) commonflags.Bind(cmd)
commonflags.BindAPI(cmd) commonflags.BindAPI(cmd)
common.StartClientCommandSpan(cmd)
}, },
PersistentPostRun: common.StopClientCommandSpan,
} }
func init() { func init() {
@ -27,7 +30,8 @@ func init() {
objectHeadCmd, objectHeadCmd,
objectHashCmd, objectHashCmd,
objectRangeCmd, objectRangeCmd,
objectLockCmd} objectLockCmd,
objectNodesCmd}
Cmd.AddCommand(objectChildCommands...) Cmd.AddCommand(objectChildCommands...)
@ -44,4 +48,5 @@ func init() {
initObjectHashCmd() initObjectHashCmd()
initObjectRangeCmd() initObjectRangeCmd()
initCommandObjectLock() initCommandObjectLock()
initObjectNodesCmd()
} }

View file

@ -10,7 +10,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
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/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -61,7 +61,7 @@ func searchObject(cmd *cobra.Command, _ []string) {
prm.SetContainerID(cnr) prm.SetContainerID(cnr)
prm.SetFilters(sf) prm.SetFilters(sf)
res, err := internalclient.SearchObjects(prm) res, err := internalclient.SearchObjects(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "rpc error: %w", err) commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
ids := res.IDList() ids := res.IDList()
@ -72,18 +72,18 @@ func searchObject(cmd *cobra.Command, _ []string) {
} }
} }
var searchUnaryOpVocabulary = map[string]object.SearchMatchType{ var searchUnaryOpVocabulary = map[string]objectSDK.SearchMatchType{
"NOPRESENT": object.MatchNotPresent, "NOPRESENT": objectSDK.MatchNotPresent,
} }
var searchBinaryOpVocabulary = map[string]object.SearchMatchType{ var searchBinaryOpVocabulary = map[string]objectSDK.SearchMatchType{
"EQ": object.MatchStringEqual, "EQ": objectSDK.MatchStringEqual,
"NE": object.MatchStringNotEqual, "NE": objectSDK.MatchStringNotEqual,
"COMMON_PREFIX": object.MatchCommonPrefix, "COMMON_PREFIX": objectSDK.MatchCommonPrefix,
} }
func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) { func parseSearchFilters(cmd *cobra.Command) (objectSDK.SearchFilters, error) {
var fs object.SearchFilters var fs objectSDK.SearchFilters
for i := range searchFilters { for i := range searchFilters {
words := strings.Fields(searchFilters[i]) words := strings.Fields(searchFilters[i])
@ -97,7 +97,7 @@ func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) {
return nil, fmt.Errorf("could not read attributes filter from file: %w", err) return nil, fmt.Errorf("could not read attributes filter from file: %w", err)
} }
subFs := object.NewSearchFilters() subFs := objectSDK.NewSearchFilters()
if err := subFs.UnmarshalJSON(data); err != nil { if err := subFs.UnmarshalJSON(data); err != nil {
return nil, fmt.Errorf("could not unmarshal attributes filter from file: %w", err) return nil, fmt.Errorf("could not unmarshal attributes filter from file: %w", err)
@ -138,7 +138,7 @@ func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) {
return nil, fmt.Errorf("could not parse object ID: %w", err) return nil, fmt.Errorf("could not parse object ID: %w", err)
} }
fs.AddObjectIDFilter(object.MatchStringEqual, id) fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id)
} }
return fs, nil return fs, nil

View file

@ -16,7 +16,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
"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"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -87,7 +87,7 @@ func readObjectAddress(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID) oid.Address
func readObjectAddressBin(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID, filename string) oid.Address { func readObjectAddressBin(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID, filename string) oid.Address {
buf, err := os.ReadFile(filename) buf, err := os.ReadFile(filename)
commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err) commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err)
objTemp := object.New() objTemp := objectSDK.New()
commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf)) commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
var addr oid.Address var addr oid.Address
@ -278,7 +278,7 @@ func OpenSessionViaClient(cmd *cobra.Command, dst SessionPrm, cli *client.Client
common.PrintVerbose(cmd, "Opening remote session with the node...") common.PrintVerbose(cmd, "Opening remote session with the node...")
err := sessionCli.CreateSession(&tok, cli, sessionLifetime) err := sessionCli.CreateSession(cmd.Context(), &tok, cli, sessionLifetime)
commonCmd.ExitOnErr(cmd, "open remote session: %w", err) commonCmd.ExitOnErr(cmd, "open remote session: %w", err)
common.PrintVerbose(cmd, "Session successfully opened.") common.PrintVerbose(cmd, "Session successfully opened.")
@ -354,9 +354,9 @@ func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID,
Prepare(cmd, &prmHead) Prepare(cmd, &prmHead)
_, err := internal.HeadObject(prmHead) _, err := internal.HeadObject(cmd.Context(), prmHead)
var errSplit *object.SplitInfoError var errSplit *objectSDK.SplitInfoError
switch { switch {
default: default:
@ -381,7 +381,7 @@ func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID,
return tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnr, obj) return tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnr, obj)
} }
func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *object.SplitInfo, prmHead internal.HeadObjectPrm, cnr cid.ID) ([]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.
@ -396,7 +396,7 @@ func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *object.Spl
prmHead.SetRawFlag(false) prmHead.SetRawFlag(false)
// client is already set // client is already set
res, err := internal.HeadObject(prmHead) res, err := internal.HeadObject(cmd.Context(), prmHead)
if err == nil { if err == nil {
children := res.Header().Children() children := res.Header().Children()
@ -413,19 +413,19 @@ func tryGetSplitMembersByLinkingObject(cmd *cobra.Command, splitInfo *object.Spl
return nil, false return nil, false
} }
func tryGetSplitMembersBySplitID(cmd *cobra.Command, splitInfo *object.SplitInfo, cli *client.Client, cnr cid.ID) ([]oid.ID, bool) { func tryGetSplitMembersBySplitID(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, cli *client.Client, cnr cid.ID) ([]oid.ID, bool) {
if idSplit := splitInfo.SplitID(); idSplit != nil { if idSplit := splitInfo.SplitID(); idSplit != nil {
common.PrintVerbose(cmd, "Collecting split members by split ID...") common.PrintVerbose(cmd, "Collecting split members by split ID...")
var query object.SearchFilters var query objectSDK.SearchFilters
query.AddSplitIDFilter(object.MatchStringEqual, idSplit) query.AddSplitIDFilter(objectSDK.MatchStringEqual, idSplit)
var prm internal.SearchObjectsPrm var prm internal.SearchObjectsPrm
prm.SetContainerID(cnr) prm.SetContainerID(cnr)
prm.SetClient(cli) prm.SetClient(cli)
prm.SetFilters(query) prm.SetFilters(query)
res, err := internal.SearchObjects(prm) res, err := internal.SearchObjects(cmd.Context(), prm)
commonCmd.ExitOnErr(cmd, "failed to search objects by split ID: %w", err) commonCmd.ExitOnErr(cmd, "failed to search objects by split ID: %w", err)
parts := res.IDList() parts := res.IDList()
@ -437,7 +437,7 @@ func tryGetSplitMembersBySplitID(cmd *cobra.Command, splitInfo *object.SplitInfo
return nil, false return nil, false
} }
func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *object.SplitInfo, prmHead internal.HeadObjectPrm, cli *client.Client, cnr cid.ID, obj oid.ID) []oid.ID { func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *objectSDK.SplitInfo, prmHead internal.HeadObjectPrm, cli *client.Client, cnr cid.ID, obj oid.ID) []oid.ID {
var addrObj oid.Address var addrObj oid.Address
addrObj.SetContainer(cnr) addrObj.SetContainer(cnr)
@ -463,7 +463,7 @@ func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *object.SplitInfo, p
addrObj.SetObject(idMember) addrObj.SetObject(idMember)
prmHead.SetAddress(addrObj) prmHead.SetAddress(addrObj)
res, err = internal.HeadObject(prmHead) res, err = internal.HeadObject(cmd.Context(), prmHead)
commonCmd.ExitOnErr(cmd, "failed to read split chain member's header: %w", err) commonCmd.ExitOnErr(cmd, "failed to read split chain member's header: %w", err)
idMember, ok = res.Header().PreviousID() idMember, ok = res.Header().PreviousID()
@ -482,15 +482,15 @@ func tryRestoreChainInReverse(cmd *cobra.Command, splitInfo *object.SplitInfo, p
common.PrintVerbose(cmd, "Looking for a linking object...") common.PrintVerbose(cmd, "Looking for a linking object...")
var query object.SearchFilters var query objectSDK.SearchFilters
query.AddParentIDFilter(object.MatchStringEqual, obj) query.AddParentIDFilter(objectSDK.MatchStringEqual, obj)
var prmSearch internal.SearchObjectsPrm var prmSearch internal.SearchObjectsPrm
prmSearch.SetClient(cli) prmSearch.SetClient(cli)
prmSearch.SetContainerID(cnr) prmSearch.SetContainerID(cnr)
prmSearch.SetFilters(query) prmSearch.SetFilters(query)
resSearch, err := internal.SearchObjects(prmSearch) resSearch, err := internal.SearchObjects(cmd.Context(), prmSearch)
commonCmd.ExitOnErr(cmd, "failed to find object children: %w", err) commonCmd.ExitOnErr(cmd, "failed to find object children: %w", err)
list := resSearch.IDList() list := resSearch.IDList()

View file

@ -1,10 +1,12 @@
package session package session
import ( import (
"context"
"fmt" "fmt"
"os" "os"
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/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/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
@ -28,10 +30,12 @@ var createCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create session token", Short: "Create session token",
Run: createSession, Run: createSession,
PersistentPreRun: func(cmd *cobra.Command, _ []string) { PersistentPreRun: func(cmd *cobra.Command, args []string) {
_ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath)) _ = viper.BindPFlag(commonflags.WalletPath, cmd.Flags().Lookup(commonflags.WalletPath))
_ = viper.BindPFlag(commonflags.Account, cmd.Flags().Lookup(commonflags.Account)) _ = viper.BindPFlag(commonflags.Account, cmd.Flags().Lookup(commonflags.Account))
common.StartClientCommandSpan(cmd)
}, },
PersistentPostRun: common.StopClientCommandSpan,
} }
func init() { func init() {
@ -64,7 +68,7 @@ func createSession(cmd *cobra.Command, _ []string) {
var tok session.Object var tok session.Object
err = CreateSession(&tok, c, lifetime) err = CreateSession(cmd.Context(), &tok, c, lifetime)
commonCmd.ExitOnErr(cmd, "can't create session: %w", err) commonCmd.ExitOnErr(cmd, "can't create session: %w", err)
var data []byte var data []byte
@ -86,11 +90,11 @@ func createSession(cmd *cobra.Command, _ []string) {
// number of epochs. // number of epochs.
// //
// Fills ID, lifetime and session key. // Fills ID, lifetime and session key.
func CreateSession(dst *session.Object, c *client.Client, lifetime uint64) error { func CreateSession(ctx context.Context, dst *session.Object, c *client.Client, lifetime uint64) error {
var netInfoPrm internalclient.NetworkInfoPrm var netInfoPrm internalclient.NetworkInfoPrm
netInfoPrm.SetClient(c) netInfoPrm.SetClient(c)
ni, err := internalclient.NetworkInfo(netInfoPrm) ni, err := internalclient.NetworkInfo(ctx, netInfoPrm)
if err != nil { if err != nil {
return fmt.Errorf("can't fetch network info: %w", err) return fmt.Errorf("can't fetch network info: %w", err)
} }
@ -102,7 +106,7 @@ func CreateSession(dst *session.Object, c *client.Client, lifetime uint64) error
sessionPrm.SetClient(c) sessionPrm.SetClient(c)
sessionPrm.SetExp(exp) sessionPrm.SetExp(exp)
sessionRes, err := internalclient.CreateSession(sessionPrm) sessionRes, err := internalclient.CreateSession(ctx, sessionPrm)
if err != nil { if err != nil {
return fmt.Errorf("can't open session: %w", err) return fmt.Errorf("can't open session: %w", err)
} }

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
@ -49,24 +50,29 @@ func add(cmd *cobra.Command, _ []string) {
ctx := cmd.Context() ctx := cmd.Context()
cli, err := _client(ctx) cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "client: %w", err) commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size) rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID) cnr.Encode(rawCID)
var bt []byte
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
bt = t.Marshal()
}
req := new(tree.AddRequest) req := new(tree.AddRequest)
req.Body = &tree.AddRequest_Body{ req.Body = &tree.AddRequest_Body{
ContainerId: rawCID, ContainerId: rawCID,
TreeId: tid, TreeId: tid,
ParentId: pid, ParentId: pid,
Meta: meta, Meta: meta,
BearerToken: nil, // TODO: #1891 add token handling BearerToken: bt,
} }
commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
resp, err := cli.Add(ctx, req) resp, err := cli.Add(ctx, req)
commonCmd.ExitOnErr(cmd, "rpc call: %w", err) commonCmd.ExitOnErr(cmd, "failed to cal add: %w", err)
cmd.Println("Node ID: ", resp.Body.NodeId) cmd.Println("Node ID: ", resp.Body.NodeId)
} }

View file

@ -10,7 +10,7 @@ import (
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
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/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -53,7 +53,7 @@ func addByPath(cmd *cobra.Command, _ []string) {
ctx := cmd.Context() ctx := cmd.Context()
cli, err := _client(ctx) cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "client: %w", err) commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size) rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID) cnr.Encode(rawCID)
@ -62,23 +62,27 @@ func addByPath(cmd *cobra.Command, _ []string) {
commonCmd.ExitOnErr(cmd, "meta data parsing: %w", err) commonCmd.ExitOnErr(cmd, "meta data parsing: %w", err)
path, _ := cmd.Flags().GetString(pathFlagKey) path, _ := cmd.Flags().GetString(pathFlagKey)
// pAttr, _ := cmd.Flags().GetString(pathAttributeFlagKey)
var bt []byte
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
bt = t.Marshal()
}
req := new(tree.AddByPathRequest) req := new(tree.AddByPathRequest)
req.Body = &tree.AddByPathRequest_Body{ req.Body = &tree.AddByPathRequest_Body{
ContainerId: rawCID, ContainerId: rawCID,
TreeId: tid, TreeId: tid,
PathAttribute: object.AttributeFileName, PathAttribute: objectSDK.AttributeFileName,
// PathAttribute: pAttr, // PathAttribute: pAttr,
Path: strings.Split(path, "/"), Path: strings.Split(path, "/"),
Meta: meta, Meta: meta,
BearerToken: nil, // TODO: #1891 add token handling BearerToken: bt,
} }
commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
resp, err := cli.AddByPath(ctx, req) resp, err := cli.AddByPath(ctx, req)
commonCmd.ExitOnErr(cmd, "rpc call: %w", err) commonCmd.ExitOnErr(cmd, "failed to addByPath %w", err)
cmd.Printf("Parent ID: %d\n", resp.GetBody().GetParentId()) cmd.Printf("Parent ID: %d\n", resp.GetBody().GetParentId())

View file

@ -5,10 +5,11 @@ import (
"strings" "strings"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
metrics "git.frostfs.info/TrueCloudLab/frostfs-observability/metrics/grpc"
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
"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"
@ -26,10 +27,12 @@ func _client(ctx context.Context) (tree.TreeServiceClient, error) {
opts := []grpc.DialOption{ opts := []grpc.DialOption{
grpc.WithBlock(), grpc.WithBlock(),
grpc.WithChainUnaryInterceptor( grpc.WithChainUnaryInterceptor(
tracing.NewGRPCUnaryClientInteceptor(), metrics.NewUnaryClientInterceptor(),
tracing.NewUnaryClientInteceptor(),
), ),
grpc.WithChainStreamInterceptor( grpc.WithChainStreamInterceptor(
tracing.NewGRPCStreamClientInterceptor(), metrics.NewStreamClientInterceptor(),
tracing.NewStreamClientInterceptor(),
), ),
} }

View file

@ -10,7 +10,7 @@ import (
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
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/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -53,31 +53,35 @@ func getByPath(cmd *cobra.Command, _ []string) {
ctx := cmd.Context() ctx := cmd.Context()
cli, err := _client(ctx) cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "client: %w", err) commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size) rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID) cnr.Encode(rawCID)
latestOnly, _ := cmd.Flags().GetBool(latestOnlyFlagKey) latestOnly, _ := cmd.Flags().GetBool(latestOnlyFlagKey)
path, _ := cmd.Flags().GetString(pathFlagKey) path, _ := cmd.Flags().GetString(pathFlagKey)
// pAttr, _ := cmd.Flags().GetString(pathAttributeFlagKey)
var bt []byte
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
bt = t.Marshal()
}
req := new(tree.GetNodeByPathRequest) req := new(tree.GetNodeByPathRequest)
req.Body = &tree.GetNodeByPathRequest_Body{ req.Body = &tree.GetNodeByPathRequest_Body{
ContainerId: rawCID, ContainerId: rawCID,
TreeId: tid, TreeId: tid,
PathAttribute: object.AttributeFileName, PathAttribute: objectSDK.AttributeFileName,
// PathAttribute: pAttr, // PathAttribute: pAttr,
Path: strings.Split(path, "/"), Path: strings.Split(path, "/"),
LatestOnly: latestOnly, LatestOnly: latestOnly,
AllAttributes: true, AllAttributes: true,
BearerToken: nil, // TODO: #1891 add token handling BearerToken: bt,
} }
commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
resp, err := cli.GetNodeByPath(ctx, req) resp, err := cli.GetNodeByPath(ctx, req)
commonCmd.ExitOnErr(cmd, "rpc call: %w", err) commonCmd.ExitOnErr(cmd, "failed to call getNodeByPath: %w", err)
nn := resp.GetBody().GetNodes() nn := resp.GetBody().GetNodes()
if len(nn) == 0 { if len(nn) == 0 {

View file

@ -0,0 +1,83 @@
package tree
import (
"crypto/sha256"
"errors"
"io"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/spf13/cobra"
)
var getOpLogCmd = &cobra.Command{
Use: "get-op-log",
Short: "Get logged operations starting with some height",
Run: getOpLog,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
commonflags.Bind(cmd)
},
}
func initGetOpLogCmd() {
commonflags.Init(getOpLogCmd)
initCTID(getOpLogCmd)
ff := getOpLogCmd.Flags()
ff.Uint64(heightFlagKey, 0, "Height to start with")
ff.Uint64(countFlagKey, 10, "Logged operations count")
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
}
func getOpLog(cmd *cobra.Command, _ []string) {
pk := key.GetOrGenerate(cmd)
cidRaw, _ := cmd.Flags().GetString(commonflags.CIDFlag)
var cnr cid.ID
err := cnr.DecodeString(cidRaw)
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
ctx := cmd.Context()
cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
height, _ := cmd.Flags().GetUint64(heightFlagKey)
count, _ := cmd.Flags().GetUint64(countFlagKey)
req := &tree.GetOpLogRequest{
Body: &tree.GetOpLogRequest_Body{
ContainerId: rawCID,
TreeId: tid,
Height: height,
Count: count,
},
}
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
resp, err := cli.GetOpLog(ctx, req)
commonCmd.ExitOnErr(cmd, "get op log: %w", err)
opLogResp, err := resp.Recv()
for ; err == nil; opLogResp, err = resp.Recv() {
o := opLogResp.GetBody().GetOperation()
cmd.Println("Parent ID: ", o.GetParentId())
cmd.Println("\tChild ID: ", o.GetChildId())
cmd.Printf("\tMeta: %s\n", o.GetMeta())
}
if !errors.Is(err, io.EOF) {
commonCmd.ExitOnErr(cmd, "get op log response stream: %w", err)
}
}

View file

@ -0,0 +1,43 @@
package tree
import (
"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-node/pkg/services/tree"
"github.com/spf13/cobra"
)
var healthcheckCmd = &cobra.Command{
Use: "healthcheck",
Short: "Check tree service availability",
Run: healthcheck,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
commonflags.Bind(cmd)
},
}
func initHealthcheckCmd() {
commonflags.Init(healthcheckCmd)
ff := healthcheckCmd.Flags()
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
}
func healthcheck(cmd *cobra.Command, _ []string) {
pk := key.GetOrGenerate(cmd)
ctx := cmd.Context()
cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
req := &tree.HealthcheckRequest{
Body: &tree.HealthcheckRequest_Body{},
}
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
_, err = cli.Healthcheck(ctx, req)
commonCmd.ExitOnErr(cmd, "failed to call healthcheck: %w", err)
common.PrintVerbose(cmd, "Successful healthcheck invocation.")
}

View file

@ -41,7 +41,7 @@ func list(cmd *cobra.Command, _ []string) {
ctx := cmd.Context() ctx := cmd.Context()
cli, err := _client(ctx) cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "client: %w", err) commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size) rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID) cnr.Encode(rawCID)
@ -52,10 +52,10 @@ func list(cmd *cobra.Command, _ []string) {
}, },
} }
commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
resp, err := cli.TreeList(ctx, req) resp, err := cli.TreeList(ctx, req)
commonCmd.ExitOnErr(cmd, "rpc call: %w", err) commonCmd.ExitOnErr(cmd, "failed to call treeList %w", err)
for _, treeID := range resp.GetBody().GetIds() { for _, treeID := range resp.GetBody().GetIds() {
cmd.Println(treeID) cmd.Println(treeID)

View file

@ -0,0 +1,107 @@
package tree
import (
"crypto/sha256"
"errors"
"io"
"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-node/pkg/services/tree"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/spf13/cobra"
)
var moveCmd = &cobra.Command{
Use: "move",
Short: "Move node",
Run: move,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
commonflags.Bind(cmd)
},
}
func initMoveCmd() {
commonflags.Init(moveCmd)
initCTID(moveCmd)
ff := moveCmd.Flags()
ff.Uint64(nodeIDFlagKey, 0, "Node ID.")
ff.Uint64(parentIDFlagKey, 0, "Parent ID.")
_ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey)
_ = getSubtreeCmd.MarkFlagRequired(parentIDFlagKey)
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
}
func move(cmd *cobra.Command, _ []string) {
pk := key.GetOrGenerate(cmd)
cidString, _ := cmd.Flags().GetString(commonflags.CIDFlag)
var cnr cid.ID
err := cnr.DecodeString(cidString)
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
ctx := cmd.Context()
cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
pid, _ := cmd.Flags().GetUint64(parentIDFlagKey)
nid, _ := cmd.Flags().GetUint64(nodeIDFlagKey)
var bt []byte
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
bt = t.Marshal()
}
subTreeReq := &tree.GetSubTreeRequest{
Body: &tree.GetSubTreeRequest_Body{
ContainerId: rawCID,
TreeId: tid,
RootId: nid,
Depth: 1,
BearerToken: bt,
},
}
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(subTreeReq, pk))
resp, err := cli.GetSubTree(ctx, subTreeReq)
commonCmd.ExitOnErr(cmd, "rpc call: %w", err)
var meta []*tree.KeyValue
subtreeResp, err := resp.Recv()
for ; err == nil; subtreeResp, err = resp.Recv() {
meta = subtreeResp.GetBody().GetMeta()
}
if !errors.Is(err, io.EOF) {
commonCmd.ExitOnErr(cmd, "failed to read getSubTree response stream: %w", err)
}
var metaErr error
if len(meta) == 0 {
metaErr = errors.New("no meta for given node ID")
}
commonCmd.ExitOnErr(cmd, "unexpected rpc call result: %w", metaErr)
req := &tree.MoveRequest{
Body: &tree.MoveRequest_Body{
ContainerId: rawCID,
TreeId: tid,
ParentId: pid,
NodeId: nid,
Meta: meta,
},
}
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
_, err = cli.Move(ctx, req)
commonCmd.ExitOnErr(cmd, "failed to call move: %w", err)
common.PrintVerbose(cmd, "Successful move invocation.")
}

View file

@ -0,0 +1,74 @@
package tree
import (
"crypto/sha256"
"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-node/pkg/services/tree"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/spf13/cobra"
)
var removeCmd = &cobra.Command{
Use: "remove",
Short: "Remove node",
Run: remove,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
commonflags.Bind(cmd)
},
}
func initRemoveCmd() {
commonflags.Init(removeCmd)
initCTID(removeCmd)
ff := removeCmd.Flags()
ff.Uint64(nodeIDFlagKey, 0, "Node ID.")
_ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey)
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
}
func remove(cmd *cobra.Command, _ []string) {
pk := key.GetOrGenerate(cmd)
cidString, _ := cmd.Flags().GetString(commonflags.CIDFlag)
var cnr cid.ID
err := cnr.DecodeString(cidString)
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
ctx := cmd.Context()
cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
nid, _ := cmd.Flags().GetUint64(nodeIDFlagKey)
var bt []byte
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
bt = t.Marshal()
}
req := &tree.RemoveRequest{
Body: &tree.RemoveRequest_Body{
ContainerId: rawCID,
TreeId: tid,
NodeId: nid,
BearerToken: bt,
},
}
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
_, err = cli.Remove(ctx, req)
commonCmd.ExitOnErr(cmd, "failed to call remove: %w", err)
common.PrintVerbose(cmd, "Successful remove invocation.")
}

View file

@ -15,16 +15,28 @@ func init() {
Cmd.AddCommand(getByPathCmd) Cmd.AddCommand(getByPathCmd)
Cmd.AddCommand(addByPathCmd) Cmd.AddCommand(addByPathCmd)
Cmd.AddCommand(listCmd) Cmd.AddCommand(listCmd)
Cmd.AddCommand(healthcheckCmd)
Cmd.AddCommand(moveCmd)
Cmd.AddCommand(removeCmd)
Cmd.AddCommand(getSubtreeCmd)
Cmd.AddCommand(getOpLogCmd)
initAddCmd() initAddCmd()
initGetByPathCmd() initGetByPathCmd()
initAddByPathCmd() initAddByPathCmd()
initListCmd() initListCmd()
initHealthcheckCmd()
initMoveCmd()
initRemoveCmd()
initGetSubtreeCmd()
initGetOpLogCmd()
} }
const ( const (
treeIDFlagKey = "tid" treeIDFlagKey = "tid"
parentIDFlagKey = "pid" parentIDFlagKey = "pid"
nodeIDFlagKey = "nid"
rootIDFlagKey = "root"
metaFlagKey = "meta" metaFlagKey = "meta"
@ -32,6 +44,12 @@ const (
pathAttributeFlagKey = "pattr" pathAttributeFlagKey = "pattr"
latestOnlyFlagKey = "latest" latestOnlyFlagKey = "latest"
bearerFlagKey = "bearer"
heightFlagKey = "height"
countFlagKey = "count"
depthFlagKey = "depth"
) )
func initCTID(cmd *cobra.Command) { func initCTID(cmd *cobra.Command) {
@ -42,4 +60,6 @@ func initCTID(cmd *cobra.Command) {
ff.String(treeIDFlagKey, "", "Tree ID") ff.String(treeIDFlagKey, "", "Tree ID")
_ = cmd.MarkFlagRequired(treeIDFlagKey) _ = cmd.MarkFlagRequired(treeIDFlagKey)
ff.String(bearerFlagKey, "", "Path to bearer token")
} }

View file

@ -0,0 +1,101 @@
package tree
import (
"crypto/sha256"
"errors"
"io"
"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-node/pkg/services/tree"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/spf13/cobra"
)
var getSubtreeCmd = &cobra.Command{
Use: "get-subtree",
Short: "Get subtree",
Run: getSubTree,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
commonflags.Bind(cmd)
},
}
func initGetSubtreeCmd() {
commonflags.Init(getSubtreeCmd)
initCTID(getSubtreeCmd)
ff := getSubtreeCmd.Flags()
ff.Uint64(rootIDFlagKey, 0, "Root ID to traverse from.")
ff.Uint32(depthFlagKey, 10, "Traversal depth.")
_ = getSubtreeCmd.MarkFlagRequired(commonflags.CIDFlag)
_ = getSubtreeCmd.MarkFlagRequired(treeIDFlagKey)
_ = cobra.MarkFlagRequired(ff, commonflags.RPC)
}
func getSubTree(cmd *cobra.Command, _ []string) {
pk := key.GetOrGenerate(cmd)
cidString, _ := cmd.Flags().GetString(commonflags.CIDFlag)
var cnr cid.ID
err := cnr.DecodeString(cidString)
commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err)
ctx := cmd.Context()
cli, err := _client(ctx)
commonCmd.ExitOnErr(cmd, "failed to create client: %w", err)
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
tid, _ := cmd.Flags().GetString(treeIDFlagKey)
rid, _ := cmd.Flags().GetUint64(rootIDFlagKey)
depth, _ := cmd.Flags().GetUint32(depthFlagKey)
var bt []byte
if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil {
bt = t.Marshal()
}
req := &tree.GetSubTreeRequest{
Body: &tree.GetSubTreeRequest_Body{
ContainerId: rawCID,
TreeId: tid,
RootId: rid,
Depth: depth,
BearerToken: bt,
},
}
commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk))
resp, err := cli.GetSubTree(ctx, req)
commonCmd.ExitOnErr(cmd, "failed to call getSubTree: %w", err)
subtreeResp, err := resp.Recv()
for ; err == nil; subtreeResp, err = resp.Recv() {
b := subtreeResp.GetBody()
cmd.Printf("Node ID: %d\n", b.GetNodeId())
cmd.Println("\tParent ID: ", b.GetParentId())
cmd.Println("\tTimestamp: ", b.GetTimestamp())
if meta := b.GetMeta(); len(meta) > 0 {
cmd.Println("\tMeta pairs: ")
for _, kv := range meta {
cmd.Printf("\t\t%s: %s\n", kv.GetKey(), string(kv.GetValue()))
}
}
}
if !errors.Is(err, io.EOF) {
commonCmd.ExitOnErr(cmd, "rpc call: %w", err)
}
}

View file

@ -60,6 +60,13 @@ func watchForSignal(cancel func()) {
if err != nil { if err != nil {
log.Error(logs.FrostFSNodeConfigurationReading, zap.Error(err)) log.Error(logs.FrostFSNodeConfigurationReading, zap.Error(err))
} }
pprofCmp.reload()
metricsCmp.reload()
log.Info(logs.FrostFSIRReloadExtraWallets)
err = innerRing.SetExtraWallets(cfg)
if err != nil {
log.Error(logs.FrostFSNodeConfigurationReading, zap.Error(err))
}
log.Info(logs.FrostFSNodeConfigurationHasBeenReloadedSuccessfully) log.Info(logs.FrostFSNodeConfigurationHasBeenReloadedSuccessfully)
case syscall.SIGTERM, syscall.SIGINT: case syscall.SIGTERM, syscall.SIGINT:
log.Info(logs.FrostFSNodeTerminationSignalHasBeenReceivedStopping) log.Info(logs.FrostFSNodeTerminationSignalHasBeenReceivedStopping)

View file

@ -51,9 +51,8 @@ func setControlDefaults(cfg *viper.Viper) {
func setFeeDefaults(cfg *viper.Viper) { func setFeeDefaults(cfg *viper.Viper) {
// extra fee values for working mode without notary contract // extra fee values for working mode without notary contract
cfg.SetDefault("fee.main_chain", 5000_0000) // 0.5 Fixed8 cfg.SetDefault("fee.main_chain", 5000_0000) // 0.5 Fixed8
cfg.SetDefault("fee.side_chain", 2_0000_0000) // 2.0 Fixed8 cfg.SetDefault("fee.side_chain", 2_0000_0000) // 2.0 Fixed8
cfg.SetDefault("fee.named_container_register", 25_0000_0000) // 25.0 Fixed8
} }
func setEmitDefaults(cfg *viper.Viper) { func setEmitDefaults(cfg *viper.Viper) {

View file

@ -0,0 +1,87 @@
package main
import (
"fmt"
"net/http"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
httputil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/http"
"go.uber.org/zap"
)
type httpComponent struct {
srv *httputil.Server
address string
name string
handler http.Handler
shutdownDur time.Duration
enabled bool
}
const (
enabledKeyPostfix = ".enabled"
addressKeyPostfix = ".address"
shutdownTimeoutKeyPostfix = ".shutdown_timeout"
)
func (c *httpComponent) init() {
log.Info(fmt.Sprintf("init %s", c.name))
c.enabled = cfg.GetBool(c.name + enabledKeyPostfix)
c.address = cfg.GetString(c.name + addressKeyPostfix)
c.shutdownDur = cfg.GetDuration(c.name + shutdownTimeoutKeyPostfix)
if c.enabled {
c.srv = httputil.New(
httputil.HTTPSrvPrm{
Address: c.address,
Handler: c.handler,
},
httputil.WithShutdownTimeout(c.shutdownDur),
)
} else {
log.Info(fmt.Sprintf("%s is disabled, skip", c.name))
c.srv = nil
}
}
func (c *httpComponent) start() {
if c.srv != nil {
log.Info(fmt.Sprintf("start %s", c.name))
wg.Add(1)
go func() {
defer wg.Done()
exitErr(c.srv.Serve())
}()
}
}
func (c *httpComponent) shutdown() error {
if c.srv != nil {
log.Info(fmt.Sprintf("shutdown %s", c.name))
return c.srv.Shutdown()
}
return nil
}
func (c *httpComponent) needReload() bool {
enabled := cfg.GetBool(c.name + enabledKeyPostfix)
address := cfg.GetString(c.name + addressKeyPostfix)
dur := cfg.GetDuration(c.name + shutdownTimeoutKeyPostfix)
return enabled != c.enabled || enabled && (address != c.address || dur != c.shutdownDur)
}
func (c *httpComponent) reload() {
log.Info(fmt.Sprintf("reload %s", c.name))
if c.needReload() {
log.Info(fmt.Sprintf("%s config updated", c.name))
if err := c.shutdown(); err != nil {
log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
zap.String("error", err.Error()),
)
} else {
c.init()
c.start()
}
}
}

View file

@ -4,16 +4,13 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"net/http"
"os" "os"
"sync" "sync"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"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"
httputil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/http"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -29,15 +26,16 @@ const (
) )
var ( var (
wg = new(sync.WaitGroup) wg = new(sync.WaitGroup)
intErr = make(chan error) // internal inner ring errors intErr = make(chan error) // internal inner ring errors
logPrm = new(logger.Prm) logPrm = new(logger.Prm)
innerRing *innerring.Server innerRing *innerring.Server
httpServers []*httputil.Server pprofCmp *pprofComponent
log *logger.Logger metricsCmp *httpComponent
cfg *viper.Viper log *logger.Logger
configFile *string cfg *viper.Viper
configDir *string configFile *string
configDir *string
) )
func exitErr(err error) { func exitErr(err error) {
@ -63,6 +61,7 @@ func main() {
cfg, err = newConfig() cfg, err = newConfig()
exitErr(err) exitErr(err)
logPrm.MetricsNamespace = "frostfs_ir"
err = logPrm.SetLevelString( err = logPrm.SetLevelString(
cfg.GetString("logger.level"), cfg.GetString("logger.level"),
) )
@ -73,19 +72,17 @@ func main() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
initHTTPServers(cfg) pprofCmp = newPprofComponent()
pprofCmp.init()
metricsCmp = newMetricsComponent()
metricsCmp.init()
innerRing, err = innerring.New(ctx, log, cfg, intErr) innerRing, err = innerring.New(ctx, log, cfg, intErr)
exitErr(err) exitErr(err)
// start HTTP servers pprofCmp.start()
for _, srv := range httpServers { metricsCmp.start()
wg.Add(1)
go func(srv *httputil.Server) {
exitErr(srv.Serve())
wg.Done()
}(srv)
}
// start inner ring // start inner ring
err = innerRing.Start(ctx, intErr) err = innerRing.Start(ctx, intErr)
@ -103,54 +100,16 @@ func main() {
log.Info(logs.FrostFSIRApplicationStopped) log.Info(logs.FrostFSIRApplicationStopped)
} }
func initHTTPServers(cfg *viper.Viper) { func shutdown() {
items := []struct { innerRing.Stop()
cfgPrefix string if err := metricsCmp.shutdown(); err != nil {
handler func() http.Handler log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
}{ zap.String("error", err.Error()),
{"pprof", httputil.Handler}, )
{"prometheus", promhttp.Handler},
} }
if err := pprofCmp.shutdown(); err != nil {
httpServers = make([]*httputil.Server, 0, len(items)) log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
zap.String("error", err.Error()),
for _, item := range items {
if !cfg.GetBool(item.cfgPrefix + ".enabled") {
log.Info(item.cfgPrefix + " is disabled, skip")
continue
}
addr := cfg.GetString(item.cfgPrefix + ".address")
var prm httputil.HTTPSrvPrm
prm.Address = addr
prm.Handler = item.handler()
httpServers = append(httpServers,
httputil.New(prm,
httputil.WithShutdownTimeout(
cfg.GetDuration(item.cfgPrefix+".shutdown_timeout"),
),
),
) )
} }
} }
func shutdown() {
innerRing.Stop()
// shut down HTTP servers
for _, srv := range httpServers {
wg.Add(1)
go func(srv *httputil.Server) {
err := srv.Shutdown()
if err != nil {
log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
zap.String("error", err.Error()),
)
}
wg.Done()
}(srv)
}
}

12
cmd/frostfs-ir/metrics.go Normal file
View file

@ -0,0 +1,12 @@
package main
import (
"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
)
func newMetricsComponent() *httpComponent {
return &httpComponent{
name: "prometheus",
handler: metrics.Handler(),
}
}

68
cmd/frostfs-ir/pprof.go Normal file
View file

@ -0,0 +1,68 @@
package main
import (
"fmt"
"runtime"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
httputil "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/http"
"go.uber.org/zap"
)
type pprofComponent struct {
httpComponent
blockRate int
mutexRate int
}
const (
pprofBlockRateKey = "pprof.block_rate"
pprofMutexRateKey = "pprof.mutex_rate"
)
func newPprofComponent() *pprofComponent {
return &pprofComponent{
httpComponent: httpComponent{
name: "pprof",
handler: httputil.Handler(),
},
}
}
func (c *pprofComponent) init() {
c.httpComponent.init()
if c.enabled {
c.blockRate = cfg.GetInt(pprofBlockRateKey)
c.mutexRate = cfg.GetInt(pprofMutexRateKey)
runtime.SetBlockProfileRate(c.blockRate)
runtime.SetMutexProfileFraction(c.mutexRate)
} else {
c.blockRate = 0
c.mutexRate = 0
runtime.SetBlockProfileRate(0)
runtime.SetMutexProfileFraction(0)
}
}
func (c *pprofComponent) needReload() bool {
blockRate := cfg.GetInt(pprofBlockRateKey)
mutexRate := cfg.GetInt(pprofMutexRateKey)
return c.httpComponent.needReload() ||
c.enabled && (c.blockRate != blockRate || c.mutexRate != mutexRate)
}
func (c *pprofComponent) reload() {
log.Info(fmt.Sprintf("reload %s", c.name))
if c.needReload() {
log.Info(fmt.Sprintf("%s config updated", c.name))
if err := c.shutdown(); err != nil {
log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
zap.String("error", err.Error()))
return
}
c.init()
c.start()
}
}

View file

@ -3,7 +3,7 @@ package blobovnicza
import ( import (
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal" common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza"
"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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -38,7 +38,7 @@ func inspectFunc(cmd *cobra.Command, _ []string) {
data := res.Object() data := res.Object()
var o object.Object var o objectSDK.Object
common.ExitOnErr(cmd, common.Errf("could not unmarshal object: %w", common.ExitOnErr(cmd, common.Errf("could not unmarshal object: %w",
o.Unmarshal(data)), o.Unmarshal(data)),
) )

View file

@ -1,6 +1,7 @@
package blobovnicza package blobovnicza
import ( import (
"context"
"fmt" "fmt"
"io" "io"
@ -33,6 +34,6 @@ func listFunc(cmd *cobra.Command, _ []string) {
blz := openBlobovnicza(cmd) blz := openBlobovnicza(cmd)
defer blz.Close() defer blz.Close()
err := blobovnicza.IterateAddresses(blz, wAddr) err := blobovnicza.IterateAddresses(context.Background(), blz, wAddr)
common.ExitOnErr(cmd, common.Errf("blobovnicza iterator failure: %w", err)) common.ExitOnErr(cmd, common.Errf("blobovnicza iterator failure: %w", err))
} }

View file

@ -7,7 +7,7 @@ import (
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal" common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza"
meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase" meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
"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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -49,7 +49,7 @@ func inspectFunc(cmd *cobra.Command, _ []string) {
prm.SetAddress(addr) prm.SetAddress(addr)
prm.SetRaw(true) prm.SetRaw(true)
siErr := new(object.SplitInfoError) siErr := new(objectSDK.SplitInfoError)
res, err := db.Get(cmd.Context(), prm) res, err := db.Get(cmd.Context(), prm)
if errors.As(err, &siErr) { if errors.As(err, &siErr) {

View file

@ -28,6 +28,6 @@ func listGarbageFunc(cmd *cobra.Command, _ []string) {
return nil return nil
}) })
err := db.IterateOverGarbage(garbPrm) err := db.IterateOverGarbage(cmd.Context(), garbPrm)
common.ExitOnErr(cmd, common.Errf("could not iterate over garbage bucket: %w", err)) common.ExitOnErr(cmd, common.Errf("could not iterate over garbage bucket: %w", err))
} }

View file

@ -33,6 +33,6 @@ func listGraveyardFunc(cmd *cobra.Command, _ []string) {
return nil return nil
}) })
err := db.IterateOverGraveyard(gravePrm) err := db.IterateOverGraveyard(cmd.Context(), gravePrm)
common.ExitOnErr(cmd, common.Errf("could not iterate over graveyard bucket: %w", err)) common.ExitOnErr(cmd, common.Errf("could not iterate over graveyard bucket: %w", err))
} }

View file

@ -4,14 +4,14 @@ import (
"os" "os"
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/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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// PrintObjectHeader prints passed object's header fields via // PrintObjectHeader prints passed object's header fields via
// the passed cobra command. Does nothing with the payload. // the passed cobra command. Does nothing with the payload.
func PrintObjectHeader(cmd *cobra.Command, h object.Object) { func PrintObjectHeader(cmd *cobra.Command, h objectSDK.Object) {
cmd.Println("Version:", h.Version()) cmd.Println("Version:", h.Version())
cmd.Println("Type:", h.Type()) cmd.Println("Type:", h.Type())
printContainerID(cmd, h.ContainerID) printContainerID(cmd, h.ContainerID)

View file

@ -3,7 +3,7 @@ package writecache
import ( import (
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal" common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -27,7 +27,7 @@ func inspectFunc(cmd *cobra.Command, _ []string) {
data, err := writecache.Get(db, []byte(vAddress)) data, err := writecache.Get(db, []byte(vAddress))
common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err)) common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err))
var o object.Object var o objectSDK.Object
common.ExitOnErr(cmd, common.Errf("could not unmarshal object: %w", o.Unmarshal(data))) common.ExitOnErr(cmd, common.Errf("could not unmarshal object: %w", o.Unmarshal(data)))
common.PrintObjectHeader(cmd, o) common.PrintObjectHeader(cmd, o)

View file

@ -21,10 +21,8 @@ func initAccountingService(ctx context.Context, c *cfg) {
server := accountingTransportGRPC.New( server := accountingTransportGRPC.New(
accountingService.NewSignService( accountingService.NewSignService(
&c.key.PrivateKey, &c.key.PrivateKey,
accountingService.NewResponseService( accountingService.NewExecutionService(
accountingService.NewExecutionService( accounting.NewExecutor(balanceMorphWrapper),
accounting.NewExecutor(balanceMorphWrapper),
),
c.respSvc, c.respSvc,
), ),
), ),

View file

@ -25,19 +25,18 @@ type valueWithTime[V any] struct {
} }
type locker struct { type locker struct {
mtx *sync.Mutex mtx sync.Mutex
waiters int // not protected by mtx, must used outer mutex to update concurrently waiters int // not protected by mtx, must used outer mutex to update concurrently
} }
type keyLocker[K comparable] struct { type keyLocker[K comparable] struct {
lockers map[K]*locker lockers map[K]*locker
lockersMtx *sync.Mutex lockersMtx sync.Mutex
} }
func newKeyLocker[K comparable]() *keyLocker[K] { func newKeyLocker[K comparable]() *keyLocker[K] {
return &keyLocker[K]{ return &keyLocker[K]{
lockers: make(map[K]*locker), lockers: make(map[K]*locker),
lockersMtx: &sync.Mutex{},
} }
} }
@ -53,7 +52,6 @@ func (l *keyLocker[K]) LockKey(key K) {
} }
locker := &locker{ locker := &locker{
mtx: &sync.Mutex{},
waiters: 1, waiters: 1,
} }
locker.mtx.Lock() locker.mtx.Lock()

View file

@ -11,12 +11,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
atomicstd "sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
netmapV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap" netmapV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
apiclientconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/apiclient" apiclientconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/apiclient"
contractsconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/contracts" contractsconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/contracts"
@ -37,6 +36,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase" meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
lsmetrics "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
shardmode "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode" shardmode "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
@ -60,6 +60,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
"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/state" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/state"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"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"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
@ -68,7 +69,6 @@ import (
neogoutil "github.com/nspcc-dev/neo-go/pkg/util" neogoutil "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
"go.etcd.io/bbolt" "go.etcd.io/bbolt"
"go.uber.org/atomic"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -98,6 +98,7 @@ type applicationConfiguration struct {
errorThreshold uint32 errorThreshold uint32
shardPoolSize uint32 shardPoolSize uint32
shards []shardCfg shards []shardCfg
lowMem bool
} }
} }
@ -200,6 +201,7 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
a.EngineCfg.errorThreshold = engineconfig.ShardErrorThreshold(c) a.EngineCfg.errorThreshold = engineconfig.ShardErrorThreshold(c)
a.EngineCfg.shardPoolSize = engineconfig.ShardPoolSize(c) a.EngineCfg.shardPoolSize = engineconfig.ShardPoolSize(c)
a.EngineCfg.lowMem = engineconfig.EngineLowMemoryConsumption(c)
return engineconfig.IterateShards(c, false, func(sc *shardconfig.Config) error { return a.updateShardConfig(c, sc) }) return engineconfig.IterateShards(c, false, func(sc *shardconfig.Config) error { return a.updateShardConfig(c, sc) })
} }
@ -321,20 +323,22 @@ func (a *applicationConfiguration) setGCConfig(newConfig *shardCfg, oldConfig *s
// helpers and fields. // helpers and fields.
type internals struct { type internals struct {
done chan struct{} done chan struct{}
ctxCancel func()
internalErr chan error // channel for internal application errors at runtime internalErr chan error // channel for internal application errors at runtime
appCfg *config.Config appCfg *config.Config
log *logger.Logger log *logger.Logger
wg *sync.WaitGroup wg sync.WaitGroup
workers []worker workers []worker
closers []closer closers []closer
apiVersion version.Version apiVersion version.Version
healthStatus *atomic.Int32 healthStatus *atomic.Int32
// is node under maintenance // is node under maintenance
isMaintenance atomic.Bool isMaintenance atomic.Bool
isOnlineCandidate bool
} }
// starts node's maintenance. // starts node's maintenance.
@ -373,7 +377,7 @@ type shared struct {
ownerIDFromKey user.ID // user ID calculated from key ownerIDFromKey user.ID // user ID calculated from key
// current network map // current network map
netMap atomicstd.Value // type netmap.NetMap netMap atomic.Value // type netmap.NetMap
netMapSource netmapCore.Source netMapSource netmapCore.Source
cnrClient *containerClient.Client cnrClient *containerClient.Client
@ -542,6 +546,8 @@ func initCfg(appCfg *config.Config) *cfg {
logPrm, err := c.loggerPrm() logPrm, err := c.loggerPrm()
fatalOnErr(err) fatalOnErr(err)
logPrm.MetricsNamespace = "frostfs_node"
log, err := logger.NewLogger(logPrm) log, err := logger.NewLogger(logPrm)
fatalOnErr(err) fatalOnErr(err)
@ -581,14 +587,16 @@ func initCfg(appCfg *config.Config) *cfg {
} }
func initInternals(appCfg *config.Config, log *logger.Logger) internals { func initInternals(appCfg *config.Config, log *logger.Logger) internals {
var healthStatus atomic.Int32
healthStatus.Store(int32(control.HealthStatus_HEALTH_STATUS_UNDEFINED))
return internals{ return internals{
done: make(chan struct{}), done: make(chan struct{}),
appCfg: appCfg, appCfg: appCfg,
internalErr: make(chan error), internalErr: make(chan error),
log: log, log: log,
wg: new(sync.WaitGroup),
apiVersion: version.Current(), apiVersion: version.Current(),
healthStatus: atomic.NewInt32(int32(control.HealthStatus_HEALTH_STATUS_UNDEFINED)), healthStatus: &healthStatus,
} }
} }
@ -614,7 +622,7 @@ func initShared(appCfg *config.Config, key *keys.PrivateKey, netState *networkSt
key: key, key: key,
binPublicKey: key.PublicKey().Bytes(), binPublicKey: key.PublicKey().Bytes(),
localAddr: netAddr, localAddr: netAddr,
respSvc: response.NewService(response.WithNetworkState(netState)), respSvc: response.NewService(netState),
clientCache: cache.NewSDKClientCache(cacheOpts), clientCache: cache.NewSDKClientCache(cacheOpts),
bgClientCache: cache.NewSDKClientCache(cacheOpts), bgClientCache: cache.NewSDKClientCache(cacheOpts),
putClientCache: cache.NewSDKClientCache(cacheOpts), putClientCache: cache.NewSDKClientCache(cacheOpts),
@ -626,12 +634,14 @@ func initNetmap(appCfg *config.Config, netState *networkState, relayOnly bool) c
netmapWorkerPool, err := ants.NewPool(notificationHandlerPoolSize) netmapWorkerPool, err := ants.NewPool(notificationHandlerPoolSize)
fatalOnErr(err) fatalOnErr(err)
var reBootstrapTurnedOff atomic.Bool
reBootstrapTurnedOff.Store(relayOnly)
return cfgNetmap{ return cfgNetmap{
scriptHash: contractsconfig.Netmap(appCfg), scriptHash: contractsconfig.Netmap(appCfg),
state: netState, state: netState,
workerPool: netmapWorkerPool, workerPool: netmapWorkerPool,
needBootstrap: !relayOnly, needBootstrap: !relayOnly,
reBoostrapTurnedOff: atomic.NewBool(relayOnly), reBoostrapTurnedOff: &reBootstrapTurnedOff,
} }
} }
@ -668,12 +678,12 @@ func (c *cfg) engineOpts() []engine.Option {
opts = append(opts, opts = append(opts,
engine.WithShardPoolSize(c.EngineCfg.shardPoolSize), engine.WithShardPoolSize(c.EngineCfg.shardPoolSize),
engine.WithErrorThreshold(c.EngineCfg.errorThreshold), engine.WithErrorThreshold(c.EngineCfg.errorThreshold),
engine.WithLogger(c.log), engine.WithLogger(c.log),
engine.WithLowMemoryConsumption(c.EngineCfg.lowMem),
) )
if c.metricsCollector != nil { if c.metricsCollector != nil {
opts = append(opts, engine.WithMetrics(c.metricsCollector)) opts = append(opts, engine.WithMetrics(c.metricsCollector.Engine()))
} }
return opts return opts
@ -722,6 +732,9 @@ func (c *cfg) getPiloramaOpts(shCfg shardCfg) []pilorama.Option {
pilorama.WithMaxBatchSize(prRead.maxBatchSize), pilorama.WithMaxBatchSize(prRead.maxBatchSize),
pilorama.WithMaxBatchDelay(prRead.maxBatchDelay), pilorama.WithMaxBatchDelay(prRead.maxBatchDelay),
) )
if c.metricsCollector != nil {
piloramaOpts = append(piloramaOpts, pilorama.WithMetrics(lsmetrics.NewPiloramaMetrics(c.metricsCollector.PiloramaMetrics())))
}
} }
return piloramaOpts return piloramaOpts
} }
@ -731,27 +744,46 @@ func (c *cfg) getSubstorageOpts(shCfg shardCfg) []blobstor.SubStorage {
for _, sRead := range shCfg.subStorages { for _, sRead := range shCfg.subStorages {
switch sRead.typ { switch sRead.typ {
case blobovniczatree.Type: case blobovniczatree.Type:
ss = append(ss, blobstor.SubStorage{ blobTreeOpts := []blobovniczatree.Option{
Storage: blobovniczatree.NewBlobovniczaTree( blobovniczatree.WithRootPath(sRead.path),
blobovniczatree.WithRootPath(sRead.path), blobovniczatree.WithPermissions(sRead.perm),
blobovniczatree.WithPermissions(sRead.perm), blobovniczatree.WithBlobovniczaSize(sRead.size),
blobovniczatree.WithBlobovniczaSize(sRead.size), blobovniczatree.WithBlobovniczaShallowDepth(sRead.depth),
blobovniczatree.WithBlobovniczaShallowDepth(sRead.depth), blobovniczatree.WithBlobovniczaShallowWidth(sRead.width),
blobovniczatree.WithBlobovniczaShallowWidth(sRead.width), blobovniczatree.WithOpenedCacheSize(sRead.openedCacheSize),
blobovniczatree.WithOpenedCacheSize(sRead.openedCacheSize), blobovniczatree.WithLogger(c.log),
}
blobovniczatree.WithLogger(c.log)), if c.metricsCollector != nil {
blobTreeOpts = append(blobTreeOpts,
blobovniczatree.WithMetrics(
lsmetrics.NewBlobovniczaTreeMetrics(sRead.path, c.metricsCollector.BlobobvnizcaTreeMetrics()),
),
)
}
ss = append(ss, blobstor.SubStorage{
Storage: blobovniczatree.NewBlobovniczaTree(blobTreeOpts...),
Policy: func(_ *objectSDK.Object, data []byte) bool { Policy: func(_ *objectSDK.Object, data []byte) bool {
return uint64(len(data)) < shCfg.smallSizeObjectLimit return uint64(len(data)) < shCfg.smallSizeObjectLimit
}, },
}) })
case fstree.Type: case fstree.Type:
fstreeOpts := []fstree.Option{
fstree.WithPath(sRead.path),
fstree.WithPerm(sRead.perm),
fstree.WithDepth(sRead.depth),
fstree.WithNoSync(sRead.noSync),
}
if c.metricsCollector != nil {
fstreeOpts = append(fstreeOpts,
fstree.WithMetrics(
lsmetrics.NewFSTreeMetricsWithoutShardID(sRead.path, c.metricsCollector.FSTree()),
),
)
}
ss = append(ss, blobstor.SubStorage{ ss = append(ss, blobstor.SubStorage{
Storage: fstree.New( Storage: fstree.New(fstreeOpts...),
fstree.WithPath(sRead.path),
fstree.WithPerm(sRead.perm),
fstree.WithDepth(sRead.depth),
fstree.WithNoSync(sRead.noSync)),
Policy: func(_ *objectSDK.Object, data []byte) bool { Policy: func(_ *objectSDK.Object, data []byte) bool {
return true return true
}, },
@ -769,31 +801,39 @@ func (c *cfg) getShardOpts(shCfg shardCfg) shardOptsWithID {
piloramaOpts := c.getPiloramaOpts(shCfg) piloramaOpts := c.getPiloramaOpts(shCfg)
ss := c.getSubstorageOpts(shCfg) ss := c.getSubstorageOpts(shCfg)
blobstoreOpts := []blobstor.Option{
blobstor.WithCompressObjects(shCfg.compress),
blobstor.WithUncompressableContentTypes(shCfg.uncompressableContentType),
blobstor.WithStorages(ss),
blobstor.WithLogger(c.log),
}
if c.metricsCollector != nil {
blobstoreOpts = append(blobstoreOpts, blobstor.WithMetrics(lsmetrics.NewBlobstoreMetrics(c.metricsCollector.Blobstore())))
}
mbOptions := []meta.Option{
meta.WithPath(shCfg.metaCfg.path),
meta.WithPermissions(shCfg.metaCfg.perm),
meta.WithMaxBatchSize(shCfg.metaCfg.maxBatchSize),
meta.WithMaxBatchDelay(shCfg.metaCfg.maxBatchDelay),
meta.WithBoltDBOptions(&bbolt.Options{
Timeout: 100 * time.Millisecond,
}),
meta.WithLogger(c.log),
meta.WithEpochState(c.cfgNetmap.state),
}
if c.metricsCollector != nil {
mbOptions = append(mbOptions, meta.WithMetrics(lsmetrics.NewMetabaseMetrics(shCfg.metaCfg.path, c.metricsCollector.MetabaseMetrics())))
}
var sh shardOptsWithID var sh shardOptsWithID
sh.configID = shCfg.id() sh.configID = shCfg.id()
sh.shOpts = []shard.Option{ sh.shOpts = []shard.Option{
shard.WithLogger(c.log), shard.WithLogger(c.log),
shard.WithRefillMetabase(shCfg.refillMetabase), shard.WithRefillMetabase(shCfg.refillMetabase),
shard.WithMode(shCfg.mode), shard.WithMode(shCfg.mode),
shard.WithBlobStorOptions( shard.WithBlobStorOptions(blobstoreOpts...),
blobstor.WithCompressObjects(shCfg.compress), shard.WithMetaBaseOptions(mbOptions...),
blobstor.WithUncompressableContentTypes(shCfg.uncompressableContentType),
blobstor.WithStorages(ss),
blobstor.WithLogger(c.log),
),
shard.WithMetaBaseOptions(
meta.WithPath(shCfg.metaCfg.path),
meta.WithPermissions(shCfg.metaCfg.perm),
meta.WithMaxBatchSize(shCfg.metaCfg.maxBatchSize),
meta.WithMaxBatchDelay(shCfg.metaCfg.maxBatchDelay),
meta.WithBoltDBOptions(&bbolt.Options{
Timeout: 100 * time.Millisecond,
}),
meta.WithLogger(c.log),
meta.WithEpochState(c.cfgNetmap.state),
),
shard.WithPiloramaOptions(piloramaOpts...), shard.WithPiloramaOptions(piloramaOpts...),
shard.WithWriteCache(shCfg.writecacheCfg.enabled), shard.WithWriteCache(shCfg.writecacheCfg.enabled),
shard.WithWriteCacheOptions(writeCacheOpts...), shard.WithWriteCacheOptions(writeCacheOpts...),
@ -842,18 +882,9 @@ func initLocalStorage(c *cfg) {
// service will be created later // service will be created later
c.cfgObject.getSvc = new(getsvc.Service) c.cfgObject.getSvc = new(getsvc.Service)
var tssPrm tsourse.TombstoneSourcePrm
tssPrm.SetGetService(c.cfgObject.getSvc)
tombstoneSrc := tsourse.NewSource(tssPrm)
tombstoneSource := tombstone.NewChecker(
tombstone.WithLogger(c.log),
tombstone.WithTombstoneSource(tombstoneSrc),
)
var shardsAttached int var shardsAttached int
for _, optsWithMeta := range c.shardOpts() { for _, optsWithMeta := range c.shardOpts() {
id, err := ls.AddShard(append(optsWithMeta.shOpts, shard.WithTombstoneSource(tombstoneSource))...) id, err := ls.AddShard(append(optsWithMeta.shOpts, shard.WithTombstoneSource(c.createTombstoneSource()))...)
if err != nil { if err != nil {
c.log.Error(logs.FrostFSNodeFailedToAttachShardToEngine, zap.Error(err)) c.log.Error(logs.FrostFSNodeFailedToAttachShardToEngine, zap.Error(err))
} else { } else {
@ -966,13 +997,6 @@ func (c *cfg) needBootstrap() bool {
return c.cfgNetmap.needBootstrap return c.cfgNetmap.needBootstrap
} }
// ObjectServiceLoad implements system loader interface for policer component.
// It is calculated as size/capacity ratio of "remote object put" worker.
// Returns float value between 0.0 and 1.0.
func (c *cfg) ObjectServiceLoad() float64 {
return float64(c.cfgObject.pool.putRemote.Running()) / float64(c.cfgObject.pool.putRemoteCapacity)
}
type dCmp struct { type dCmp struct {
name string name string
reloadFunc func() error reloadFunc func() error
@ -1031,6 +1055,10 @@ func (c *cfg) reloadConfig(ctx context.Context) {
} }
components = append(components, dCmp{"logger", logPrm.Reload}) components = append(components, dCmp{"logger", logPrm.Reload})
components = append(components, dCmp{"runtime", func() error {
setRuntimeParameters(c)
return nil
}})
components = append(components, dCmp{"tracing", func() error { components = append(components, dCmp{"tracing", func() error {
updated, err := tracing.Setup(ctx, *tracingconfig.ToTracingConfig(c.appCfg)) updated, err := tracing.Setup(ctx, *tracingconfig.ToTracingConfig(c.appCfg))
if updated { if updated {
@ -1054,7 +1082,7 @@ func (c *cfg) reloadConfig(ctx context.Context) {
var rcfg engine.ReConfiguration var rcfg engine.ReConfiguration
for _, optsWithID := range c.shardOpts() { for _, optsWithID := range c.shardOpts() {
rcfg.AddShard(optsWithID.configID, optsWithID.shOpts) rcfg.AddShard(optsWithID.configID, append(optsWithID.shOpts, shard.WithTombstoneSource(c.createTombstoneSource())))
} }
err = c.cfgObject.cfgLocalStorage.localStorage.Reload(ctx, rcfg) err = c.cfgObject.cfgLocalStorage.localStorage.Reload(ctx, rcfg)
@ -1075,9 +1103,22 @@ func (c *cfg) reloadConfig(ctx context.Context) {
c.log.Info(logs.FrostFSNodeConfigurationHasBeenReloadedSuccessfully) c.log.Info(logs.FrostFSNodeConfigurationHasBeenReloadedSuccessfully)
} }
func (c *cfg) createTombstoneSource() *tombstone.ExpirationChecker {
var tssPrm tsourse.TombstoneSourcePrm
tssPrm.SetGetService(c.cfgObject.getSvc)
tombstoneSrc := tsourse.NewSource(tssPrm)
tombstoneSource := tombstone.NewChecker(
tombstone.WithLogger(c.log),
tombstone.WithTombstoneSource(tombstoneSrc),
)
return tombstoneSource
}
func (c *cfg) shutdown() { func (c *cfg) shutdown() {
c.setHealthStatus(control.HealthStatus_SHUTTING_DOWN) c.setHealthStatus(control.HealthStatus_SHUTTING_DOWN)
c.ctxCancel()
c.done <- struct{}{} c.done <- struct{}{}
for i := range c.closers { for i := range c.closers {
c.closers[len(c.closers)-1-i].fn() c.closers[len(c.closers)-1-i].fn()

View file

@ -83,3 +83,8 @@ func ShardPoolSize(c *config.Config) uint32 {
func ShardErrorThreshold(c *config.Config) uint32 { func ShardErrorThreshold(c *config.Config) uint32 {
return config.Uint32Safe(c.Sub(subsection), "shard_ro_error_threshold") return config.Uint32Safe(c.Sub(subsection), "shard_ro_error_threshold")
} }
// EngineLowMemoryConsumption returns value of "lowmem" config parmeter from "storage" section.
func EngineLowMemoryConsumption(c *config.Config) bool {
return config.BoolSafe(c.Sub(subsection), "low_mem")
}

View file

@ -5,8 +5,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/storage" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/storage"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
) )
// Config is a wrapper over the config section // Config is a wrapper over the config section
@ -25,14 +23,11 @@ func (x *Config) Storages() []*storage.Config {
typ := config.String( typ := config.String(
(*config.Config)(x), (*config.Config)(x),
strconv.Itoa(i)+".type") strconv.Itoa(i)+".type")
switch typ { if typ == "" {
case "":
return ss return ss
case fstree.Type, blobovniczatree.Type:
sub := storage.From((*config.Config)(x).Sub(strconv.Itoa(i)))
ss = append(ss, sub)
default:
panic("invalid type")
} }
sub := storage.From((*config.Config)(x).Sub(strconv.Itoa(i)))
ss = append(ss, sub)
} }
} }

View file

@ -25,3 +25,9 @@ func HeadTimeout(c *config.Config) time.Duration {
return HeadTimeoutDefault return HeadTimeoutDefault
} }
// UnsafeDisable returns the value of "unsafe_disable" config parameter
// from "policer" section.
func UnsafeDisable(c *config.Config) bool {
return config.BoolSafe(c.Sub(subsection), "unsafe_disable")
}

View file

@ -51,3 +51,27 @@ func Address(c *config.Config) string {
return AddressDefault return AddressDefault
} }
// BlockRates returns the value of "block_rate" config parameter
// from "pprof" section.
func BlockRate(c *config.Config) int {
s := c.Sub(subsection)
v := int(config.IntSafe(s, "block_rate"))
if v <= 0 {
return 0
}
return v
}
// MutexRate returns the value of "mutex_rate" config parameter
// from "pprof" section.
func MutexRate(c *config.Config) int {
s := c.Sub(subsection)
v := int(config.IntSafe(s, "mutex_rate"))
if v <= 0 {
return 0
}
return v
}

View file

@ -18,6 +18,9 @@ func TestProfilerSection(t *testing.T) {
require.Equal(t, profilerconfig.ShutdownTimeoutDefault, to) require.Equal(t, profilerconfig.ShutdownTimeoutDefault, to)
require.Equal(t, profilerconfig.AddressDefault, addr) require.Equal(t, profilerconfig.AddressDefault, addr)
require.False(t, profilerconfig.Enabled(configtest.EmptyConfig())) require.False(t, profilerconfig.Enabled(configtest.EmptyConfig()))
require.Zero(t, profilerconfig.BlockRate(configtest.EmptyConfig()))
require.Zero(t, profilerconfig.MutexRate(configtest.EmptyConfig()))
}) })
const path = "../../../../config/example/node" const path = "../../../../config/example/node"
@ -29,6 +32,9 @@ func TestProfilerSection(t *testing.T) {
require.Equal(t, 15*time.Second, to) require.Equal(t, 15*time.Second, to)
require.Equal(t, "localhost:6060", addr) require.Equal(t, "localhost:6060", addr)
require.True(t, profilerconfig.Enabled(c)) require.True(t, profilerconfig.Enabled(c))
require.Equal(t, 10_000, profilerconfig.BlockRate(c))
require.Equal(t, 10_000, profilerconfig.MutexRate(c))
} }
configtest.ForEachFileType(path, fileConfigTest) configtest.ForEachFileType(path, fileConfigTest)

View file

@ -0,0 +1,23 @@
package runtime
import (
"math"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
)
const (
subsection = "runtime"
memoryLimitDefault = math.MaxInt64
)
// GCMemoryLimitBytes returns the value of "soft_memory_limit" config parameter from "runtime" section.
func GCMemoryLimitBytes(c *config.Config) int64 {
l := config.SizeInBytesSafe(c.Sub(subsection), "soft_memory_limit")
if l > 0 {
return int64(l)
}
return memoryLimitDefault
}

View file

@ -0,0 +1,30 @@
package runtime
import (
"math"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
configtest "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/test"
"github.com/stretchr/testify/require"
)
func TestGCMemoryLimit(t *testing.T) {
t.Run("defaults", func(t *testing.T) {
empty := configtest.EmptyConfig()
require.Equal(t, int64(math.MaxInt64), GCMemoryLimitBytes(empty))
})
const path = "../../../../config/example/node"
fileConfigTest := func(c *config.Config) {
require.Equal(t, int64(1073741824), GCMemoryLimitBytes(c))
}
configtest.ForEachFileType(path, fileConfigTest)
t.Run("ENV", func(t *testing.T) {
configtest.ForEnvFileType(t, path, fileConfigTest)
})
}

View file

@ -1,9 +1,9 @@
package tracing package tracing
import ( import (
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/misc" "git.frostfs.info/TrueCloudLab/frostfs-node/misc"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
) )
const ( const (

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