forked from TrueCloudLab/frostfs-node
Compare commits
63 commits
master
...
support/v0
Author | SHA1 | Date | |
---|---|---|---|
|
db981e9c99 | ||
|
28ad4c6ebc | ||
c180c405b5 | |||
|
02049ca5b2 | ||
b9a24e99dc | |||
|
584f465eee | ||
|
2614aa1582 | ||
|
15d2091f42 | ||
056cb0d50e | |||
|
402bbba15a | ||
|
7cc0986e0c | ||
|
02676f05c3 | ||
|
e8d401e28d | ||
|
479601ceb9 | ||
|
27ca754dc1 | ||
|
56442c0be3 | ||
b167700b6f | |||
|
92cac5bbdf | ||
|
19a6ca7896 | ||
|
114018a7bd | ||
|
84f545dacc | ||
|
049ab58336 | ||
|
4ed5dcd9c8 | ||
|
cdbfd05704 | ||
|
8212020165 | ||
|
e538291c59 | ||
|
aa12fc57c9 | ||
|
5747187884 | ||
|
de2934aeaa | ||
|
2ba3abde5c | ||
|
f6f911b50c | ||
|
af54295ac6 | ||
|
ddba9180ef | ||
|
6acb831248 | ||
|
2a88b49bca | ||
|
01a226b3ec | ||
|
110f6e7864 | ||
|
c38ad2d339 | ||
|
51a9306e41 | ||
|
871be9d63d | ||
|
3eb2ac985d | ||
|
76b87c6d94 | ||
|
59eebc5eeb | ||
|
f79386e538 | ||
|
d2cce62934 | ||
|
a4a6d547a8 | ||
|
681400eed8 | ||
|
b580846630 | ||
|
6e2f7e291d | ||
|
7a75b3aaaf | ||
|
9cd8441dd5 | ||
|
163d8d778d | ||
|
52d85ca463 | ||
|
ba3db7fed5 | ||
|
2e89176892 | ||
|
855de87b62 | ||
|
289a7827c4 | ||
|
abf4a63585 | ||
|
3ee4260647 | ||
|
245da1e50e | ||
|
a386448ab9 | ||
|
3c1f788642 | ||
|
0de9efa685 |
1284 changed files with 45005 additions and 50458 deletions
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-adm
|
||||
RUN make bin/neofs-adm
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-adm
|
||||
FROM alpine AS neofs-adm
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /src/bin/frostfs-adm /bin/frostfs-adm
|
||||
COPY --from=builder /src/bin/neofs-adm /bin/neofs-adm
|
||||
|
||||
CMD ["frostfs-adm"]
|
||||
CMD ["neofs-adm"]
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
FROM golang:1.21
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
# Install apt packages
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
pip \
|
||||
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Dash → Bash
|
||||
RUN echo "dash dash/sh boolean false" | debconf-set-selections
|
||||
RUN DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash
|
||||
|
||||
RUN useradd -u 1234 -d /home/ci -m ci
|
||||
USER ci
|
||||
|
||||
ENV PATH="$PATH:/home/ci/.local/bin"
|
||||
|
||||
COPY .pre-commit-config.yaml .
|
||||
|
||||
RUN pip install "pre-commit==3.1.1" \
|
||||
&& git init . \
|
||||
&& pre-commit install-hooks \
|
||||
&& rm -rf /tmp/*
|
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-cli
|
||||
RUN make bin/neofs-cli
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-cli
|
||||
FROM alpine AS neofs-cli
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /src/bin/frostfs-cli /bin/frostfs-cli
|
||||
COPY --from=builder /src/bin/neofs-cli /bin/neofs-cli
|
||||
|
||||
CMD ["frostfs-cli"]
|
||||
CMD ["neofs-cli"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-adm /bin/frostfs-adm
|
||||
COPY bin/neofs-adm /bin/neofs-adm
|
||||
|
||||
CMD ["frostfs-adm"]
|
||||
CMD ["neofs-adm"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-cli /bin/frostfs-cli
|
||||
COPY bin/neofs-cli /bin/neofs-cli
|
||||
|
||||
CMD ["frostfs-cli"]
|
||||
CMD ["neofs-cli"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-ir /bin/frostfs-ir
|
||||
COPY bin/neofs-ir /bin/neofs-ir
|
||||
|
||||
CMD ["frostfs-ir"]
|
||||
CMD ["neofs-ir"]
|
||||
|
|
|
@ -3,6 +3,6 @@ RUN apk add --no-cache bash ca-certificates
|
|||
|
||||
WORKDIR /
|
||||
|
||||
COPY bin/frostfs-node /bin/frostfs-node
|
||||
COPY bin/neofs-node /bin/neofs-node
|
||||
|
||||
CMD ["frostfs-node"]
|
||||
CMD ["neofs-node"]
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-ir
|
||||
RUN make bin/neofs-ir
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-ir
|
||||
FROM alpine AS neofs-ir
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/frostfs-ir /bin/frostfs-ir
|
||||
COPY --from=builder /src/bin/neofs-ir /bin/neofs-ir
|
||||
|
||||
CMD ["frostfs-ir"]
|
||||
CMD ["neofs-ir"]
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
FROM golang:1.21 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/frostfs-node
|
||||
RUN make bin/neofs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS frostfs-node
|
||||
FROM alpine AS neofs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/frostfs-node /bin/frostfs-node
|
||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
||||
|
||||
CMD ["frostfs-node"]
|
||||
CMD ["neofs-node"]
|
||||
|
|
19
.docker/Dockerfile.storage-testnet
Normal file
19
.docker/Dockerfile.storage-testnet
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM golang:1.17 as builder
|
||||
ARG BUILD=now
|
||||
ARG VERSION=dev
|
||||
ARG REPO=repository
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make bin/neofs-node
|
||||
|
||||
# Executable image
|
||||
FROM alpine AS neofs-node
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY --from=builder /src/bin/neofs-node /bin/neofs-node
|
||||
COPY --from=builder /src/config/testnet/config.yml /config.yml
|
||||
|
||||
CMD ["neofs-node", "--config", "/config.yml"]
|
|
@ -5,5 +5,4 @@ docker-compose.yml
|
|||
Dockerfile
|
||||
temp
|
||||
.dockerignore
|
||||
docker
|
||||
.cache
|
||||
docker
|
|
@ -1,41 +0,0 @@
|
|||
name: Build
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Components
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.20', '1.21' ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# Allows to fetch all history for all branches and tags.
|
||||
# Need this for proper versioning.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
|
||||
- name: Build CLI
|
||||
run: make bin/frostfs-cli
|
||||
- run: bin/frostfs-cli --version
|
||||
|
||||
- name: Build NODE
|
||||
run: make bin/frostfs-node
|
||||
|
||||
- name: Build IR
|
||||
run: make bin/frostfs-ir
|
||||
|
||||
- name: Build ADM
|
||||
run: make bin/frostfs-adm
|
||||
- run: bin/frostfs-adm --version
|
||||
|
||||
- name: Build LENS
|
||||
run: make bin/frostfs-lens
|
||||
- run: bin/frostfs-lens --version
|
|
@ -1,21 +0,0 @@
|
|||
name: DCO action
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
dco:
|
||||
name: DCO
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Run commit format checker
|
||||
uses: https://git.frostfs.info/TrueCloudLab/dco-go@v2
|
||||
with:
|
||||
from: 'origin/${{ github.event.pull_request.base.ref }}'
|
|
@ -1,73 +0,0 @@
|
|||
name: Tests and linters
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install linters
|
||||
run: make lint-install
|
||||
|
||||
- name: Run linters
|
||||
run: make lint
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.20', '1.21' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '${{ matrix.go_versions }}'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
|
||||
tests-race:
|
||||
name: Tests with -race
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./... -count=1 -race
|
||||
|
||||
staticcheck:
|
||||
name: Staticcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache: true
|
||||
|
||||
- name: Install staticcheck
|
||||
run: make staticcheck-install
|
||||
|
||||
- name: Run staticcheck
|
||||
run: make staticcheck-run
|
|
@ -1,22 +0,0 @@
|
|||
name: Vulncheck
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
vulncheck:
|
||||
name: Vulncheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
- name: Run govulncheck
|
||||
run: govulncheck ./...
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1,3 +1,2 @@
|
|||
/**/*.pb.go -diff -merge
|
||||
/**/*.pb.go linguist-generated=true
|
||||
/go.sum -diff
|
||||
|
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* @alexvanin @carpawell @fyrchik @cthulhu-rider
|
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -2,7 +2,7 @@
|
|||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: community, triage, bug
|
||||
labels: community, triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
@ -18,11 +18,8 @@ assignees: ''
|
|||
If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!-- Not obligatory
|
||||
If no reason/fix/additions for the bug can be suggested,
|
||||
uncomment the following phrase:
|
||||
|
||||
No fix can be suggested by a QA engineer. Further solutions shall be up to developers. -->
|
||||
<!-- Not obligatory, but suggest a fix/reason for the bug,
|
||||
or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!-- Provide a link to a live example, or an unambiguous set of steps
|
||||
|
@ -44,3 +41,10 @@ assignees: ''
|
|||
* Version used:
|
||||
* Server setup and configuration:
|
||||
* Operating System and version (`uname -a`):
|
||||
|
||||
## Don't forget to add labels!
|
||||
- component label (`neofs-adm`, `neofs-storage`, ...)
|
||||
- `goodfirstissue`, `helpwanted` if needed
|
||||
- does this issue belong to an epic?
|
||||
- priority (`P0`-`P4`) if already triaged
|
||||
- quarter label (`202XQY`) if possible
|
||||
|
|
197
.github/logo.svg
vendored
197
.github/logo.svg
vendored
|
@ -1,70 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 184.2 51.8" style="enable-background:new 0 0 184.2 51.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;}
|
||||
.st1{display:inline;}
|
||||
.st2{fill:#01E397;}
|
||||
.st3{display:inline;fill:#010032;}
|
||||
.st4{display:inline;fill:#00E599;}
|
||||
.st5{display:inline;fill:#00AF92;}
|
||||
.st6{fill:#00C3E5;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g id="Layer_1-2" class="st0">
|
||||
<g class="st1">
|
||||
<path class="st2" d="M146.6,18.3v7.2h10.9V29h-10.9v10.7h-4V14.8h18v3.5H146.6z"/>
|
||||
<path class="st2" d="M180,15.7c1.7,0.9,3,2.2,4,3.8l-3,2.7c-0.6-1.3-1.5-2.4-2.6-3.3c-1.3-0.7-2.8-1-4.3-1
|
||||
c-1.4-0.1-2.8,0.3-4,1.1c-0.9,0.5-1.5,1.5-1.4,2.6c0,1,0.5,1.9,1.4,2.4c1.5,0.8,3.2,1.3,4.9,1.5c1.9,0.3,3.7,0.8,5.4,1.6
|
||||
c1.2,0.5,2.2,1.3,2.9,2.3c0.6,1,1,2.2,0.9,3.4c0,1.4-0.5,2.7-1.3,3.8c-0.9,1.2-2.1,2.1-3.5,2.6c-1.7,0.6-3.4,0.9-5.2,0.8
|
||||
c-5,0-8.6-1.6-10.7-5l2.9-2.8c0.7,1.4,1.8,2.5,3.1,3.3c1.5,0.7,3.1,1.1,4.7,1c1.5,0.1,2.9-0.2,4.2-0.9c0.9-0.5,1.5-1.5,1.5-2.6
|
||||
c0-0.9-0.5-1.8-1.3-2.2c-1.5-0.7-3.1-1.2-4.8-1.5c-1.9-0.3-3.7-0.8-5.5-1.5c-1.2-0.5-2.2-1.4-3-2.4c-0.6-1-1-2.2-0.9-3.4
|
||||
c0-1.4,0.4-2.7,1.2-3.8c0.8-1.2,2-2.2,3.3-2.8c1.6-0.7,3.4-1.1,5.2-1C176.1,14.3,178.2,14.8,180,15.7z"/>
|
||||
</g>
|
||||
<path class="st3" d="M73.3,16.3c1.9,1.9,2.9,4.5,2.7,7.1v15.9h-4V24.8c0-2.6-0.5-4.5-1.6-5.7c-1.2-1.2-2.8-1.8-4.5-1.7
|
||||
c-1.3,0-2.5,0.3-3.7,0.8c-1.2,0.7-2.2,1.7-2.9,2.9c-0.8,1.5-1.1,3.2-1.1,4.9v13.3h-4V15.1l3.6,1.5v1.7c0.8-1.5,2.1-2.6,3.6-3.3
|
||||
c1.5-0.8,3.2-1.2,4.9-1.1C68.9,13.8,71.3,14.7,73.3,16.3z"/>
|
||||
<path class="st3" d="M104.4,28.3H85.6c0.1,2.2,1,4.3,2.5,5.9c1.5,1.4,3.5,2.2,5.6,2.1c1.6,0.1,3.2-0.2,4.6-0.9
|
||||
c1.1-0.6,2-1.6,2.5-2.8l3.3,1.8c-0.9,1.7-2.3,3.1-4,4c-2,1-4.2,1.5-6.4,1.4c-3.7,0-6.7-1.1-8.8-3.4s-3.2-5.5-3.2-9.6s1-7.2,3-9.5
|
||||
s5-3.4,8.7-3.4c2.1-0.1,4.2,0.5,6.1,1.5c1.6,1,3,2.5,3.8,4.2c0.9,1.8,1.3,3.9,1.3,5.9C104.6,26.4,104.6,27.4,104.4,28.3z
|
||||
M88.1,19.3c-1.4,1.5-2.2,3.4-2.4,5.5h15.1c-0.2-2-1-3.9-2.3-5.5c-1.4-1.3-3.2-2-5.1-1.9C91.5,17.3,89.6,18,88.1,19.3z"/>
|
||||
<path class="st3" d="M131,17.3c2.2,2.3,3.2,5.5,3.2,9.5s-1,7.3-3.2,9.6s-5.1,3.4-8.8,3.4s-6.7-1.1-8.9-3.4s-3.2-5.5-3.2-9.6
|
||||
s1.1-7.2,3.2-9.5s5.1-3.4,8.9-3.4S128.9,15,131,17.3z M116.2,19.9c-1.5,2-2.2,4.4-2.1,6.9c-0.2,2.5,0.6,5,2.1,7
|
||||
c1.5,1.7,3.7,2.7,6,2.6c2.3,0.1,4.4-0.9,5.9-2.6c1.5-2,2.3-4.5,2.1-7c0.1-2.5-0.6-4.9-2.1-6.9c-1.5-1.7-3.6-2.7-5.9-2.6
|
||||
C119.9,17.2,117.7,18.2,116.2,19.9z"/>
|
||||
<polygon class="st4" points="0,9.1 0,43.7 22.5,51.8 22.5,16.9 46.8,7.9 24.8,0 "/>
|
||||
<polygon class="st5" points="24.3,17.9 24.3,36.8 46.8,44.9 46.8,9.6 "/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st6" d="M41.6,17.5H28.2v6.9h10.4v3.3H28.2v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||
<path class="st6" d="M45.8,37.9v-18h3.3l0.4,3.2c0.5-1.2,1.2-2.1,2.1-2.7c0.9-0.6,2.1-0.9,3.5-0.9c0.4,0,0.7,0,1.1,0.1
|
||||
c0.4,0.1,0.7,0.2,0.9,0.3l-0.5,3.4c-0.3-0.1-0.6-0.2-0.9-0.2C55.4,23,54.9,23,54.4,23c-0.7,0-1.5,0.2-2.2,0.6
|
||||
c-0.7,0.4-1.3,1-1.8,1.8s-0.7,1.8-0.7,3v9.5H45.8z"/>
|
||||
<path class="st6" d="M68.6,19.6c1.8,0,3.3,0.4,4.6,1.1c1.3,0.7,2.4,1.8,3.1,3.2s1.1,3.1,1.1,5c0,1.9-0.4,3.6-1.1,5
|
||||
c-0.8,1.4-1.8,2.5-3.1,3.2c-1.3,0.7-2.9,1.1-4.6,1.1s-3.3-0.4-4.6-1.1c-1.3-0.7-2.4-1.8-3.2-3.2c-0.8-1.4-1.2-3.1-1.2-5
|
||||
c0-1.9,0.4-3.6,1.2-5s1.8-2.5,3.2-3.2C65.3,19.9,66.8,19.6,68.6,19.6z M68.6,22.6c-1.1,0-2,0.2-2.8,0.7c-0.8,0.5-1.3,1.2-1.7,2.1
|
||||
s-0.6,2.1-0.6,3.5c0,1.3,0.2,2.5,0.6,3.4s1,1.7,1.7,2.2s1.7,0.7,2.8,0.7c1.1,0,2-0.2,2.7-0.7c0.7-0.5,1.3-1.2,1.7-2.2
|
||||
s0.6-2.1,0.6-3.4c0-1.4-0.2-2.5-0.6-3.5s-1-1.6-1.7-2.1C70.6,22.8,69.6,22.6,68.6,22.6z"/>
|
||||
<path class="st6" d="M89.2,38.3c-1.8,0-3.4-0.3-4.9-1c-1.5-0.7-2.7-1.7-3.5-3l2.7-2.3c0.5,1,1.3,1.8,2.3,2.4
|
||||
c1,0.6,2.2,0.9,3.6,0.9c1.1,0,2-0.2,2.6-0.6c0.6-0.4,1-0.9,1-1.6c0-0.5-0.2-0.9-0.5-1.2s-0.9-0.6-1.7-0.8l-3.8-0.8
|
||||
c-1.9-0.4-3.3-1-4.1-1.9c-0.8-0.9-1.2-1.9-1.2-3.3c0-1,0.3-1.9,0.9-2.7c0.6-0.8,1.4-1.5,2.5-2s2.5-0.8,4-0.8c1.8,0,3.3,0.3,4.6,1
|
||||
c1.3,0.6,2.2,1.5,2.9,2.7l-2.7,2.2c-0.5-1-1.1-1.7-2-2.1c-0.9-0.5-1.8-0.7-2.8-0.7c-0.8,0-1.4,0.1-2,0.3c-0.6,0.2-1,0.5-1.3,0.8
|
||||
c-0.3,0.3-0.4,0.7-0.4,1.2c0,0.5,0.2,0.9,0.5,1.3s1,0.6,1.9,0.8l4.1,0.9c1.7,0.3,2.9,0.9,3.7,1.7c0.7,0.8,1.1,1.8,1.1,2.9
|
||||
c0,1.2-0.3,2.2-0.9,3c-0.6,0.9-1.5,1.6-2.6,2C92.1,38.1,90.7,38.3,89.2,38.3z"/>
|
||||
<path class="st6" d="M112.8,19.9v3H99.3v-3H112.8z M106.6,14.6v17.9c0,0.9,0.2,1.5,0.7,1.9c0.5,0.4,1.1,0.6,1.9,0.6
|
||||
c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,0.9-0.5,1.3-0.8l0.9,2.8c-0.6,0.5-1.2,0.9-2,1.1c-0.8,0.3-1.7,0.4-2.7,0.4c-1,0-2-0.2-2.8-0.5
|
||||
s-1.5-0.9-2-1.6c-0.5-0.8-0.7-1.7-0.8-3V15.7L106.6,14.6z"/>
|
||||
<path d="M137.9,17.5h-13.3v6.9h10.4v3.3h-10.4v10.2h-3.9V14.2h17.2V17.5z"/>
|
||||
<path d="M150.9,13.8c2.1,0,4,0.4,5.5,1.2c1.6,0.8,2.9,2,4,3.5l-2.6,2.5c-0.9-1.4-1.9-2.4-3.1-3c-1.1-0.6-2.5-0.9-4-0.9
|
||||
c-1.2,0-2.1,0.2-2.8,0.5c-0.7,0.3-1.3,0.7-1.6,1.2c-0.3,0.5-0.5,1.1-0.5,1.7c0,0.7,0.3,1.4,0.8,1.9c0.5,0.6,1.5,1,2.9,1.3
|
||||
l4.8,1.1c2.3,0.5,3.9,1.3,4.9,2.3c1,1,1.4,2.3,1.4,3.9c0,1.5-0.4,2.7-1.2,3.8c-0.8,1.1-1.9,1.9-3.3,2.5s-3.1,0.9-5,0.9
|
||||
c-1.7,0-3.2-0.2-4.5-0.6c-1.3-0.4-2.5-1-3.5-1.8c-1-0.7-1.8-1.6-2.5-2.6l2.7-2.7c0.5,0.8,1.1,1.6,1.9,2.2
|
||||
c0.8,0.7,1.7,1.2,2.7,1.5c1,0.4,2.2,0.5,3.4,0.5c1.1,0,2.1-0.1,2.9-0.4c0.8-0.3,1.4-0.7,1.8-1.2c0.4-0.5,0.6-1.1,0.6-1.9
|
||||
c0-0.7-0.2-1.3-0.7-1.8c-0.5-0.5-1.3-0.9-2.6-1.2l-5.2-1.2c-1.4-0.3-2.6-0.8-3.6-1.3c-0.9-0.6-1.6-1.3-2.1-2.1s-0.7-1.8-0.7-2.8
|
||||
c0-1.3,0.4-2.6,1.1-3.7c0.7-1.1,1.8-2,3.2-2.6C147.3,14.1,148.9,13.8,150.9,13.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
sodipodi:docname="logo_fs.svg"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
id="svg57"
|
||||
version="1.1"
|
||||
viewBox="0 0 105 25"
|
||||
height="25mm"
|
||||
width="105mm">
|
||||
<defs
|
||||
id="defs51">
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath434">
|
||||
<path
|
||||
d="M 0,0 H 1366 V 768 H 0 Z"
|
||||
id="path432" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-x="130"
|
||||
inkscape:window-height="1040"
|
||||
inkscape:window-width="1274"
|
||||
height="50mm"
|
||||
units="mm"
|
||||
showgrid="false"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:cy="344.49897"
|
||||
inkscape:cx="468.64708"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base" />
|
||||
<metadata
|
||||
id="metadata54">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
id="g424"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,63.946468,10.194047)">
|
||||
<path
|
||||
d="m 0,0 v -8.093 h 12.287 v -3.94 H 0 V -24.067 H -4.534 V 3.898 H 15.677 V 0 Z"
|
||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path426" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,-315.43002,107.34005)"
|
||||
id="g428">
|
||||
<g
|
||||
id="g430"
|
||||
clip-path="url(#clipPath434)">
|
||||
<g
|
||||
id="g436"
|
||||
transform="translate(1112.874,278.2981)">
|
||||
<path
|
||||
d="M 0,0 C 1.822,-0.932 3.354,-2.359 4.597,-4.28 L 1.165,-7.373 c -0.791,1.695 -1.779,2.924 -2.966,3.686 -1.186,0.763 -2.768,1.145 -4.745,1.145 -1.949,0 -3.461,-0.389 -4.534,-1.166 -1.074,-0.777 -1.61,-1.772 -1.61,-2.987 0,-1.13 0.523,-2.027 1.568,-2.69 1.045,-0.664 2.909,-1.236 5.593,-1.716 2.514,-0.452 4.512,-1.024 5.995,-1.716 1.483,-0.693 2.564,-1.554 3.242,-2.585 0.677,-1.031 1.016,-2.309 1.016,-3.834 0,-1.639 -0.466,-3.079 -1.398,-4.322 -0.932,-1.243 -2.239,-2.197 -3.919,-2.86 -1.681,-0.664 -3.623,-0.996 -5.826,-0.996 -5.678,0 -9.689,1.892 -12.033,5.678 l 3.178,3.178 c 0.903,-1.695 2.068,-2.939 3.495,-3.729 1.426,-0.791 3.199,-1.186 5.318,-1.186 2.005,0 3.58,0.345 4.724,1.038 1.144,0.692 1.716,1.674 1.716,2.945 0,1.017 -0.516,1.835 -1.547,2.457 -1.031,0.621 -2.832,1.172 -5.402,1.653 -2.571,0.479 -4.618,1.073 -6.143,1.779 -1.526,0.706 -2.635,1.582 -3.326,2.627 -0.693,1.045 -1.039,2.316 -1.039,3.813 0,1.582 0.438,3.023 1.314,4.322 0.875,1.299 2.14,2.33 3.792,3.093 1.653,0.763 3.58,1.144 5.783,1.144 C -4.018,1.398 -1.822,0.932 0,0"
|
||||
style="fill:#00e396;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path438" />
|
||||
</g>
|
||||
<g
|
||||
id="g440"
|
||||
transform="translate(993.0239,277.5454)">
|
||||
<path
|
||||
d="m 0,0 c 2.054,-1.831 3.083,-4.465 3.083,-7.902 v -17.935 h -4.484 v 16.366 c 0,2.914 -0.626,5.024 -1.877,6.332 -1.253,1.308 -2.924,1.962 -5.016,1.962 -1.495,0 -2.896,-0.327 -4.204,-0.981 -1.308,-0.654 -2.381,-1.719 -3.222,-3.194 -0.841,-1.477 -1.261,-3.335 -1.261,-5.576 v -14.909 h -4.484 V 1.328 l 4.086,-1.674 0.118,-1.84 c 0.933,1.681 2.222,2.923 3.867,3.727 1.643,0.803 3.493,1.205 5.548,1.205 C -4.671,2.746 -2.055,1.83 0,0"
|
||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path442" />
|
||||
</g>
|
||||
<g
|
||||
id="g444"
|
||||
transform="translate(1027.9968,264.0386)">
|
||||
<path
|
||||
d="m 0,0 h -21.128 c 0.261,-2.84 1.205,-5.044 2.83,-6.613 1.625,-1.57 3.727,-2.355 6.305,-2.355 2.054,0 3.763,0.356 5.128,1.065 1.363,0.71 2.288,1.738 2.774,3.083 l 3.755,-1.961 c -1.121,-1.981 -2.616,-3.495 -4.484,-4.54 -1.868,-1.046 -4.259,-1.569 -7.173,-1.569 -4.223,0 -7.538,1.289 -9.948,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.149,8.127 3.447,10.705 2.298,2.578 5.557,3.867 9.779,3.867 2.615,0 4.876,-0.58 6.782,-1.738 1.905,-1.158 3.343,-2.728 4.315,-4.707 C -0.262,7.827 0.224,5.605 0.224,3.139 0.224,2.092 0.149,1.046 0,0 m -18.298,10.144 c -1.513,-1.457 -2.438,-3.512 -2.775,-6.165 h 16.982 c -0.3,2.615 -1.159,4.661 -2.578,6.137 -1.42,1.476 -3.307,2.214 -5.661,2.214 -2.466,0 -4.455,-0.728 -5.968,-2.186"
|
||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path446" />
|
||||
</g>
|
||||
<g
|
||||
id="g448"
|
||||
transform="translate(1057.8818,276.4246)">
|
||||
<path
|
||||
d="m 0,0 c 2.41,-2.578 3.615,-6.147 3.615,-10.705 0,-4.558 -1.205,-8.126 -3.615,-10.704 -2.41,-2.578 -5.726,-3.867 -9.948,-3.867 -4.222,0 -7.537,1.289 -9.947,3.867 -2.41,2.578 -3.615,6.146 -3.615,10.704 0,4.558 1.205,8.127 3.615,10.705 2.41,2.578 5.725,3.867 9.947,3.867 C -5.726,3.867 -2.41,2.578 0,0 m -16.617,-2.858 c -1.607,-1.906 -2.41,-4.522 -2.41,-7.847 0,-3.326 0.803,-5.94 2.41,-7.846 1.607,-1.905 3.83,-2.858 6.669,-2.858 2.839,0 5.063,0.953 6.67,2.858 1.606,1.906 2.41,4.52 2.41,7.846 0,3.325 -0.804,5.941 -2.41,7.847 C -4.885,-0.953 -7.109,0 -9.948,0 c -2.839,0 -5.062,-0.953 -6.669,-2.858"
|
||||
style="fill:#000033;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path450" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g452"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,5.8329581,6.5590171)">
|
||||
<path
|
||||
d="m 0,0 0.001,-38.946 25.286,-9.076 V -8.753 L 52.626,1.321 27.815,10.207 Z"
|
||||
style="fill:#00e599;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path454" />
|
||||
</g>
|
||||
<g
|
||||
id="g456"
|
||||
transform="matrix(0.35277777,0,0,-0.35277777,15.479008,10.041927)">
|
||||
<path
|
||||
d="M 0,0 V -21.306 L 25.293,-30.364 25.282,9.347 Z"
|
||||
style="fill:#00b091;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path458" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.5 KiB |
29
.github/workflows/changelog.yml
vendored
Normal file
29
.github/workflows/changelog.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
name: CHANGELOG check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check for updates
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get changed CHANGELOG
|
||||
id: changelog-diff
|
||||
uses: tj-actions/changed-files@v29
|
||||
with:
|
||||
files: CHANGELOG.md
|
||||
|
||||
- name: Fail if changelog not updated
|
||||
if: steps.changelog-diff.outputs.any_changed == 'false'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('CHANGELOG.md has not been updated')
|
37
.github/workflows/config-update.yml
vendored
Normal file
37
.github/workflows/config-update.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: Configuration check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: config-check
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get changed config-related files
|
||||
id: config-diff
|
||||
uses: tj-actions/changed-files@v29
|
||||
with:
|
||||
files: |
|
||||
config/**
|
||||
cmd/neofs-node/config/**
|
||||
|
||||
- name: Get changed doc files
|
||||
id: docs-diff
|
||||
uses: tj-actions/changed-files@v29
|
||||
with:
|
||||
files: docs/**
|
||||
|
||||
- name: Fail if config files are changed but the documentation is not updated
|
||||
if: steps.config-diff.outputs.any_changed == 'true' && steps.docs-diff.outputs.any_changed == 'false'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Documentation has not been updated')
|
22
.github/workflows/dco.yml
vendored
Normal file
22
.github/workflows/dco.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: DCO check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
|
||||
jobs:
|
||||
commits_check_job:
|
||||
runs-on: ubuntu-latest
|
||||
name: Commits Check
|
||||
steps:
|
||||
- name: Get PR Commits
|
||||
id: 'get-pr-commits'
|
||||
uses: tim-actions/get-pr-commits@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: DCO Check
|
||||
uses: tim-actions/dco@master
|
||||
with:
|
||||
commits: ${{ steps.get-pr-commits.outputs.commits }}
|
60
.github/workflows/go.yml
vendored
Normal file
60
.github/workflows/go.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: neofs-node tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- support/**
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
go: [ '1.17.x', '1.18.x', '1.19.x' ]
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache go mod
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ matrix.go }}-
|
||||
|
||||
- name: Run go test
|
||||
run: go test -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
- name: Codecov
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.50.0
|
||||
args: --timeout=5m
|
||||
only-new-issues: true
|
20
.gitignore
vendored
20
.gitignore
vendored
|
@ -30,22 +30,4 @@ testfile
|
|||
.neofs-cli.yml
|
||||
|
||||
# debhelpers
|
||||
debian/*debhelper*
|
||||
|
||||
# logfiles
|
||||
debian/*.log
|
||||
|
||||
# .substvars
|
||||
debian/*.substvars
|
||||
|
||||
# .bash-completion
|
||||
debian/*.bash-completion
|
||||
|
||||
# Install folders and files
|
||||
debian/frostfs-cli/
|
||||
debian/frostfs-ir/
|
||||
debian/files
|
||||
debian/frostfs-storage/
|
||||
debian/changelog
|
||||
man/
|
||||
debs/
|
||||
**/.debhelper
|
||||
|
|
11
.gitlint
11
.gitlint
|
@ -1,11 +0,0 @@
|
|||
[general]
|
||||
fail-without-commits=True
|
||||
regex-style-search=True
|
||||
contrib=CC1
|
||||
|
||||
[title-match-regex]
|
||||
regex=^\[\#[0-9Xx]+\]\s
|
||||
|
||||
[ignore-by-title]
|
||||
regex=^Release(.*)
|
||||
ignore=title-match-regex
|
|
@ -4,7 +4,7 @@
|
|||
# options for analysis running
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 20m
|
||||
timeout: 5m
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: false
|
||||
|
@ -24,28 +24,6 @@ linters-settings:
|
|||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
staticcheck:
|
||||
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||
funlen:
|
||||
lines: 80 # default 60
|
||||
statements: 60 # default 40
|
||||
gocognit:
|
||||
min-complexity: 40 # default 30
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object
|
||||
alias: objectSDK
|
||||
custom:
|
||||
truecloudlab-linters:
|
||||
path: bin/linters/external_linters.so
|
||||
original-url: git.frostfs.info/TrueCloudLab/linters.git
|
||||
settings:
|
||||
noliteral:
|
||||
target-methods : ["reportFlushError", "reportError"]
|
||||
disable-packages: ["codes", "err", "res","exec"]
|
||||
constants-package: "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
|
@ -73,11 +51,6 @@ linters:
|
|||
- predeclared
|
||||
- reassign
|
||||
- whitespace
|
||||
- containedctx
|
||||
- funlen
|
||||
- gocognit
|
||||
- contextcheck
|
||||
- importas
|
||||
- truecloudlab-linters
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
ci:
|
||||
autofix_prs: false
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: v0.19.1
|
||||
hooks:
|
||||
- id: gitlint
|
||||
stages: [commit-msg]
|
||||
- id: gitlint-ci
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- id: end-of-file-fixer
|
||||
exclude: ".key$"
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.9.0.5
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: make-lint
|
||||
name: Run Make Lint
|
||||
entry: make lint
|
||||
language: system
|
||||
pass_filenames: false
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: go-unit-tests
|
||||
name: go unit tests
|
||||
entry: make test
|
||||
pass_filenames: false
|
||||
types: [go]
|
||||
language: system
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: gofumpt
|
||||
name: gofumpt
|
||||
entry: make fumpt
|
||||
pass_filenames: false
|
||||
types: [go]
|
||||
language: system
|
||||
|
||||
- repo: https://github.com/TekWizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-staticcheck-repo-mod
|
||||
- id: go-mod-tidy
|
|
@ -1,11 +0,0 @@
|
|||
pipeline:
|
||||
# Kludge for non-root containers under WoodPecker
|
||||
fix-ownership:
|
||||
image: alpine:latest
|
||||
commands: chown -R 1234:1234 .
|
||||
|
||||
pre-commit:
|
||||
image: git.frostfs.info/truecloudlab/frostfs-ci:v0.36
|
||||
commands:
|
||||
- export HOME="$(getent passwd $(id -u) | cut '-d:' -f6)"
|
||||
- pre-commit run --hook-stage manual
|
1590
CHANGELOG.md
1590
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -3,8 +3,8 @@
|
|||
First, thank you for contributing! We love and encourage pull requests from
|
||||
everyone. Please follow the guidelines:
|
||||
|
||||
- Check the open [issues](https://git.frostfs.info/TrueCloudLab/frostfs-node/issues) and
|
||||
[pull requests](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls) for existing
|
||||
- Check the open [issues](https://github.com/nspcc-dev/neofs-node/issues) and
|
||||
[pull requests](https://github.com/nspcc-dev/neofs-node/pulls) for existing
|
||||
discussions.
|
||||
|
||||
- Open an issue first, to discuss a new feature or enhancement.
|
||||
|
@ -23,23 +23,23 @@ everyone. Please follow the guidelines:
|
|||
|
||||
## Development Workflow
|
||||
|
||||
Start by forking the `frostfs-node` repository, make changes in a branch and then
|
||||
Start by forking the `neofs-node` repository, make changes in a branch and then
|
||||
send a pull request. We encourage pull requests to discuss code changes. Here
|
||||
are the steps in details:
|
||||
|
||||
### Set up your Forgejo repository
|
||||
Fork [FrostFS node upstream](https://git.frostfs.info/TrueCloudLab/frostfs-node) source
|
||||
### Set up your GitHub Repository
|
||||
Fork [NeoFS node upstream](https://github.com/nspcc-dev/neofs-node/fork) source
|
||||
repository to your own personal repository. Copy the URL of your fork (you will
|
||||
need it for the `git clone` command below).
|
||||
|
||||
```sh
|
||||
$ git clone https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||
$ git clone https://github.com/nspcc-dev/neofs-node
|
||||
```
|
||||
|
||||
### Set up git remote as ``upstream``
|
||||
```sh
|
||||
$ cd frostfs-node
|
||||
$ git remote add upstream https://git.frostfs.info/TrueCloudLab/frostfs-node
|
||||
$ cd neofs-node
|
||||
$ git remote add upstream https://github.com/nspcc-dev/neofs-node
|
||||
$ git fetch upstream
|
||||
$ git merge upstream/master
|
||||
...
|
||||
|
@ -58,7 +58,7 @@ $ git checkout -b feature/123-something_awesome
|
|||
After your code changes, make sure
|
||||
|
||||
- To add test cases for the new code.
|
||||
- To run `make lint` and `make staticcheck-run`
|
||||
- To run `make lint`
|
||||
- To squash your commits into a single commit or a series of logically separated
|
||||
commits run `git rebase -i`. It's okay to force update your pull request.
|
||||
- To run `make test` and `make all` completes.
|
||||
|
@ -79,7 +79,7 @@ Description
|
|||
```
|
||||
|
||||
```
|
||||
$ git commit -sam '[#123] Add some feature'
|
||||
$ git commit -am '[#123] Add some feature'
|
||||
```
|
||||
|
||||
### Push to the branch
|
||||
|
@ -89,8 +89,8 @@ $ git push origin feature/123-something_awesome
|
|||
```
|
||||
|
||||
### Create a Pull Request
|
||||
Pull requests can be created via Forgejo. Refer to [this
|
||||
document](https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/) for
|
||||
Pull requests can be created via GitHub. Refer to [this
|
||||
document](https://help.github.com/articles/creating-a-pull-request/) for
|
||||
detailed steps on how to create a pull request. After a Pull Request gets peer
|
||||
reviewed and approved, it will be merged.
|
||||
|
||||
|
@ -106,8 +106,7 @@ contributors".
|
|||
To sign your work, just add a line like this at the end of your commit message:
|
||||
|
||||
```
|
||||
Signed-off-by: Samii Sakisaka <samii@ivunojikan.co.jp>
|
||||
|
||||
Signed-off-by: Samii Sakisaka <samii@nspcc.ru>
|
||||
```
|
||||
|
||||
This can easily be done with the `--signoff` option to `git commit`.
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# Credits
|
||||
|
||||
FrostFS continues the development of NeoFS.
|
||||
|
||||
Initial NeoFS research and development (2018-2020) was done by
|
||||
[NeoSPCC](https://nspcc.ru) team.
|
||||
|
||||
|
@ -10,7 +8,7 @@ In alphabetical order:
|
|||
- Alexey Vanin
|
||||
- Anastasia Prasolova
|
||||
- Anatoly Bogatyrev
|
||||
- Evgeny Kulikov
|
||||
- Evgeny Kulikov
|
||||
- Evgeny Stratonikov
|
||||
- Leonard Liubich
|
||||
- Sergei Liubich
|
||||
|
|
137
Makefile
Executable file → Normal file
137
Makefile
Executable file → Normal file
|
@ -4,20 +4,11 @@ SHELL = bash
|
|||
REPO ?= $(shell go list -m)
|
||||
VERSION ?= $(shell git describe --tags --dirty --match "v*" --always --abbrev=8 2>/dev/null || cat VERSION 2>/dev/null || echo "develop")
|
||||
|
||||
HUB_IMAGE ?= truecloudlab/frostfs
|
||||
HUB_IMAGE ?= nspccdev/neofs
|
||||
HUB_TAG ?= "$(shell echo ${VERSION} | sed 's/^v//')"
|
||||
|
||||
GO_VERSION ?= 1.21
|
||||
LINT_VERSION ?= 1.54.0
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.2
|
||||
PROTOC_VERSION ?= 25.0
|
||||
PROTOC_GEN_GO_VERSION ?= $(shell go list -f '{{.Version}}' -m google.golang.org/protobuf)
|
||||
PROTOGEN_FROSTFS_VERSION ?= $(shell go list -f '{{.Version}}' -m git.frostfs.info/TrueCloudLab/frostfs-api-go/v2)
|
||||
PROTOC_OS_VERSION=osx-x86_64
|
||||
ifeq ($(shell uname), Linux)
|
||||
PROTOC_OS_VERSION=linux-x86_64
|
||||
endif
|
||||
STATICCHECK_VERSION ?= 2023.1.6
|
||||
GO_VERSION ?= 1.17
|
||||
LINT_VERSION ?= 1.50.0
|
||||
ARCH = amd64
|
||||
|
||||
BIN = bin
|
||||
|
@ -25,7 +16,7 @@ RELEASE = release
|
|||
DIRS = $(BIN) $(RELEASE)
|
||||
|
||||
# List of binaries to build.
|
||||
CMDS = $(notdir $(basename $(wildcard cmd/frostfs-*)))
|
||||
CMDS = $(notdir $(basename $(wildcard cmd/*)))
|
||||
BINS = $(addprefix $(BIN)/, $(CMDS))
|
||||
|
||||
# .deb package versioning
|
||||
|
@ -34,21 +25,11 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
|||
sed -E "s/(.*)-(g[a-fA-F0-9]{6,8})(.*)/\1\3~\2/" | \
|
||||
sed "s/-/~/")-${OS_RELEASE}
|
||||
|
||||
OUTPUT_LINT_DIR ?= $(abspath $(BIN))/linters
|
||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||
TMP_DIR := .cache
|
||||
PROTOBUF_DIR ?= $(abspath $(BIN))/protobuf
|
||||
PROTOC_DIR ?= $(PROTOBUF_DIR)/protoc-v$(PROTOC_VERSION)
|
||||
PROTOC_GEN_GO_DIR ?= $(PROTOBUF_DIR)/protoc-gen-go-$(PROTOC_GEN_GO_VERSION)
|
||||
PROTOGEN_FROSTFS_DIR ?= $(PROTOBUF_DIR)/protogen-$(PROTOGEN_FROSTFS_VERSION)
|
||||
STATICCHECK_DIR ?= $(abspath $(BIN))/staticcheck
|
||||
STATICCHECK_VERSION_DIR ?= $(STATICCHECK_DIR)/$(STATICCHECK_VERSION)
|
||||
|
||||
.PHONY: help all images dep clean fmts fumpt imports test lint docker/lint
|
||||
prepare-release debpackage pre-commit unpre-commit
|
||||
.PHONY: help all images dep clean fmts fmt imports test lint docker/lint
|
||||
prepare-release debpackage
|
||||
|
||||
# To build a specific binary, use it's name prefix with bin/ as a target
|
||||
# For example `make bin/frostfs-node` will build only storage node binary
|
||||
# For example `make bin/neofs-node` will build only storage node binary
|
||||
# Just `make` will build all possible binaries
|
||||
all: $(DIRS) $(BINS)
|
||||
|
||||
|
@ -69,7 +50,7 @@ $(DIRS):
|
|||
# Prepare binaries and archives for release
|
||||
.ONESHELL:
|
||||
prepare-release: docker/all
|
||||
@for file in `ls -1 $(BIN)/frostfs-*`; do
|
||||
@for file in `ls -1 $(BIN)/neofs-*`; do
|
||||
cp $$file $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
strip $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
tar -czf $(RELEASE)/`basename $$file`-$(ARCH).tar.gz $(RELEASE)/`basename $$file`-$(ARCH)
|
||||
|
@ -84,44 +65,28 @@ dep:
|
|||
CGO_ENABLED=0 \
|
||||
go mod tidy -v && echo OK
|
||||
|
||||
# Build export-metrics
|
||||
export-metrics: dep
|
||||
@printf "⇒ Build export-metrics\n"
|
||||
CGO_ENABLED=0 \
|
||||
go build -v -trimpath -o bin/export-metrics ./scripts/export-metrics
|
||||
|
||||
# Regenerate proto files:
|
||||
protoc:
|
||||
@if [ ! -d "$(PROTOC_DIR)" ] || [ ! -d "$(PROTOC_GEN_GO_DIR)" ] || [ ! -d "$(PROTOGEN_FROSTFS_DIR)" ]; then \
|
||||
make protoc-install; \
|
||||
fi
|
||||
@for f in `find . -type f -name '*.proto' -not -path './bin/*'`; do \
|
||||
@GOPRIVATE=github.com/nspcc-dev go mod vendor
|
||||
# Install specific version for protobuf lib
|
||||
@go list -f '{{.Path}}/...@{{.Version}}' -m github.com/golang/protobuf | xargs go install -v
|
||||
@GOBIN=$(abspath $(BIN)) go install -mod=mod -v github.com/nspcc-dev/neofs-api-go/v2/util/protogen
|
||||
# Protoc generate
|
||||
@for f in `find . -type f -name '*.proto' -not -path './vendor/*'`; do \
|
||||
echo "⇒ Processing $$f "; \
|
||||
$(PROTOC_DIR)/bin/protoc \
|
||||
--proto_path=.:$(PROTOC_DIR)/include:/usr/local/include \
|
||||
--plugin=protoc-gen-go=$(PROTOC_GEN_GO_DIR)/protoc-gen-go \
|
||||
--plugin=protoc-gen-go-frostfs=$(PROTOGEN_FROSTFS_DIR)/protogen \
|
||||
--go-frostfs_out=. --go-frostfs_opt=paths=source_relative \
|
||||
protoc \
|
||||
--proto_path=.:./vendor:/usr/local/include \
|
||||
--plugin=protoc-gen-go-neofs=$(BIN)/protogen \
|
||||
--go-neofs_out=. --go-neofs_opt=paths=source_relative \
|
||||
--go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_opt=require_unimplemented_servers=false \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative $$f; \
|
||||
done
|
||||
rm -rf vendor
|
||||
|
||||
protoc-install:
|
||||
@rm -rf $(PROTOBUF_DIR)
|
||||
@mkdir $(PROTOBUF_DIR)
|
||||
@echo "⇒ Installing protoc... "
|
||||
@wget -q -O $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip 'https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS_VERSION).zip'
|
||||
@unzip -q -o $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip -d $(PROTOC_DIR)
|
||||
@rm $(PROTOBUF_DIR)/protoc-$(PROTOC_VERSION).zip
|
||||
@echo "⇒ Installing protoc-gen-go..."
|
||||
@GOBIN=$(PROTOC_GEN_GO_DIR) go install -v google.golang.org/protobuf/...@$(PROTOC_GEN_GO_VERSION)
|
||||
@echo "⇒ Instaling protogen FrostFS plugin..."
|
||||
@GOBIN=$(PROTOGEN_FROSTFS_DIR) go install -mod=mod -v git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/protogen@$(PROTOGEN_FROSTFS_VERSION)
|
||||
|
||||
# Build FrostFS component's docker image
|
||||
# Build NeoFS component's docker image
|
||||
image-%:
|
||||
@echo "⇒ Build FrostFS $* docker image "
|
||||
@echo "⇒ Build NeoFS $* docker image "
|
||||
@docker build \
|
||||
--build-arg REPO=$(REPO) \
|
||||
--build-arg VERSION=$(VERSION) \
|
||||
|
@ -130,7 +95,7 @@ image-%:
|
|||
-t $(HUB_IMAGE)-$*:$(HUB_TAG) .
|
||||
|
||||
# Build all Docker images
|
||||
images: image-storage image-ir image-cli image-adm
|
||||
images: image-storage image-ir image-cli image-adm image-storage-testnet
|
||||
|
||||
# Build dirty local Docker images
|
||||
dirty-images: image-dirty-storage image-dirty-ir image-dirty-cli image-dirty-adm
|
||||
|
@ -146,56 +111,26 @@ docker/%:
|
|||
|
||||
|
||||
# Run all code formatters
|
||||
fmts: fumpt imports
|
||||
fmts: fmt imports
|
||||
|
||||
# Reformat code
|
||||
fmt:
|
||||
@echo "⇒ Processing gofmt check"
|
||||
@gofmt -s -w cmd/ pkg/ misc/
|
||||
|
||||
# Reformat imports
|
||||
imports:
|
||||
@echo "⇒ Processing goimports check"
|
||||
@goimports -w cmd/ pkg/ misc/
|
||||
|
||||
fumpt:
|
||||
@echo "⇒ Processing gofumpt check"
|
||||
@gofumpt -l -w cmd/ pkg/ misc/
|
||||
|
||||
# Run Unit Test with go test
|
||||
test:
|
||||
@echo "⇒ Running go test"
|
||||
@go test ./... -count=1
|
||||
|
||||
pre-commit-run:
|
||||
@pre-commit run -a --hook-stage manual
|
||||
|
||||
# Install linters
|
||||
lint-install:
|
||||
@rm -rf $(OUTPUT_LINT_DIR)
|
||||
@mkdir $(OUTPUT_LINT_DIR)
|
||||
@mkdir -p $(TMP_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@git -c advice.detachedHead=false clone --branch v$(TRUECLOUDLAB_LINT_VERSION) https://git.frostfs.info/TrueCloudLab/linters.git $(TMP_DIR)/linters
|
||||
@@make -C $(TMP_DIR)/linters lib CGO_ENABLED=1 OUT_DIR=$(OUTPUT_LINT_DIR)
|
||||
@rm -rf $(TMP_DIR)/linters
|
||||
@rmdir $(TMP_DIR) 2>/dev/null || true
|
||||
@CGO_ENABLED=1 GOBIN=$(LINT_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v$(LINT_VERSION)
|
||||
@go test ./...
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
@if [ ! -d "$(LINT_DIR)" ]; then \
|
||||
make lint-install; \
|
||||
fi
|
||||
$(LINT_DIR)/golangci-lint run
|
||||
|
||||
# Install staticcheck
|
||||
staticcheck-install:
|
||||
@rm -rf $(STATICCHECK_DIR)
|
||||
@mkdir $(STATICCHECK_DIR)
|
||||
@GOBIN=$(STATICCHECK_VERSION_DIR) go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)
|
||||
|
||||
# Run staticcheck
|
||||
staticcheck-run:
|
||||
@if [ ! -d "$(STATICCHECK_VERSION_DIR)" ]; then \
|
||||
make staticcheck-install; \
|
||||
fi
|
||||
@$(STATICCHECK_VERSION_DIR)/staticcheck ./...
|
||||
@golangci-lint --timeout=5m run
|
||||
|
||||
# Run linters in Docker
|
||||
docker/lint:
|
||||
|
@ -205,27 +140,19 @@ docker/lint:
|
|||
--env HOME=/src \
|
||||
golangci/golangci-lint:v$(LINT_VERSION) bash -c 'cd /src/ && make lint'
|
||||
|
||||
# Activate pre-commit hooks
|
||||
pre-commit:
|
||||
pre-commit install -t pre-commit -t commit-msg
|
||||
|
||||
# Deactivate pre-commit hooks
|
||||
unpre-commit:
|
||||
pre-commit uninstall -t pre-commit -t commit-msg
|
||||
|
||||
# Print version
|
||||
version:
|
||||
@echo $(VERSION)
|
||||
|
||||
# Delete built artifacts
|
||||
clean:
|
||||
rm -rf vendor
|
||||
rm -rf .cache
|
||||
rm -rf $(BIN)
|
||||
rm -rf $(RELEASE)
|
||||
|
||||
# Package for Debian
|
||||
debpackage:
|
||||
dch -b --package frostfs-node \
|
||||
dch --package neofs-node \
|
||||
--controlmaint \
|
||||
--newversion $(PKG_VERSION) \
|
||||
--distribution $(OS_RELEASE) \
|
||||
|
|
41
README.md
41
README.md
|
@ -1,40 +1,39 @@
|
|||
<p align="center">
|
||||
<img src="./.github/logo.svg" width="500px" alt="FrostFS">
|
||||
<img src="./.github/logo.svg" width="500px" alt="NeoFS">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://frostfs.info">FrostFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
|
||||
<a href="https://fs.neo.org">NeoFS</a> is a decentralized distributed object storage integrated with the <a href="https://neo.org">NEO Blockchain</a>.
|
||||
</p>
|
||||
|
||||
---
|
||||
[![Report](https://goreportcard.com/badge/github.com/TrueCloudLab/frostfs-node)](https://goreportcard.com/report/github.com/TrueCloudLab/frostfs-node)
|
||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/TrueCloudLab/frostfs-node?sort=semver)
|
||||
![License](https://img.shields.io/github/license/TrueCloudLab/frostfs-node.svg?style=popout)
|
||||
[![Report](https://goreportcard.com/badge/github.com/nspcc-dev/neofs-node)](https://goreportcard.com/report/github.com/nspcc-dev/neofs-node)
|
||||
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nspcc-dev/neofs-node?sort=semver)
|
||||
![License](https://img.shields.io/github/license/nspcc-dev/neofs-node.svg?style=popout)
|
||||
|
||||
# Overview
|
||||
|
||||
FrostFS Nodes are organized in a peer-to-peer network that takes care of storing
|
||||
NeoFS Nodes are organized in a peer-to-peer network that takes care of storing
|
||||
and distributing user's data. Any Neo user may participate in the network and
|
||||
get paid for providing storage resources to other users or store their data in
|
||||
FrostFS and pay a competitive price for it.
|
||||
NeoFS and pay a competitive price for it.
|
||||
|
||||
Users can reliably store object data in the FrostFS network and have a transparent
|
||||
Users can reliably store object data in the NeoFS network and have a transparent
|
||||
data placement process due to a decentralized architecture and flexible storage
|
||||
policies. Each node is responsible for executing the storage policies that the
|
||||
users select for geographical location, reliability level, number of nodes, type
|
||||
of disks, capacity, etc. Thus, FrostFS gives full control over data to users.
|
||||
of disks, capacity, etc. Thus, NeoFS gives full control over data to users.
|
||||
|
||||
Deep [Neo Blockchain](https://neo.org) integration allows FrostFS to be used by
|
||||
Deep [Neo Blockchain](https://neo.org) integration allows NeoFS to be used by
|
||||
dApps directly from
|
||||
[NeoVM](https://docs.neo.org/docs/en-us/basic/technology/neovm.html) on the
|
||||
[Smart Contract](https://docs.neo.org/docs/en-us/intro/glossary.html)
|
||||
code level. This way dApps are not limited to on-chain storage and can
|
||||
manipulate large amounts of data without paying a prohibitive price.
|
||||
|
||||
FrostFS has a native [gRPC API](https://git.frostfs.info/TrueCloudLab/frostfs-api) and has
|
||||
NeoFS has a native [gRPC API](https://github.com/nspcc-dev/neofs-api) and has
|
||||
protocol gateways for popular protocols such as [AWS
|
||||
S3](https://github.com/TrueCloudLab/frostfs-s3-gw),
|
||||
[HTTP](https://github.com/TrueCloudLab/frostfs-http-gw),
|
||||
S3](https://github.com/nspcc-dev/neofs-s3-gw),
|
||||
[HTTP](https://github.com/nspcc-dev/neofs-http-gw),
|
||||
[FUSE](https://wikipedia.org/wiki/Filesystem_in_Userspace) and
|
||||
[sFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) allowing
|
||||
developers to integrate applications without rewriting their code.
|
||||
|
@ -44,12 +43,12 @@ developers to integrate applications without rewriting their code.
|
|||
Now, we only support GNU/Linux on amd64 CPUs with AVX/AVX2 instructions. More
|
||||
platforms will be officially supported after release `1.0`.
|
||||
|
||||
The latest version of frostfs-node works with frostfs-contract
|
||||
[v0.16.0](https://github.com/TrueCloudLab/frostfs-contract/releases/tag/v0.16.0).
|
||||
The latest version of neofs-node works with neofs-contract
|
||||
[v0.16.0](https://github.com/nspcc-dev/neofs-contract/releases/tag/v0.16.0).
|
||||
|
||||
# Building
|
||||
|
||||
To make all binaries you need Go 1.20+ and `make`:
|
||||
To make all binaries you need Go 1.17+ and `make`:
|
||||
```
|
||||
make all
|
||||
```
|
||||
|
@ -57,7 +56,7 @@ The resulting binaries will appear in `bin/` folder.
|
|||
|
||||
To make a specific binary use:
|
||||
```
|
||||
make bin/frostfs-<name>
|
||||
make bin/neofs-<name>
|
||||
```
|
||||
See the list of all available commands in the `cmd` folder.
|
||||
|
||||
|
@ -66,12 +65,12 @@ See the list of all available commands in the `cmd` folder.
|
|||
Building can also be performed in a container:
|
||||
```
|
||||
make docker/all # build all binaries
|
||||
make docker/bin/frostfs-<name> # build a specific binary
|
||||
make docker/bin/neofs-<name> # build a specific binary
|
||||
```
|
||||
|
||||
## Docker images
|
||||
|
||||
To make docker images suitable for use in [frostfs-dev-env](https://github.com/TrueCloudLab/frostfs-dev-env/) use:
|
||||
To make docker images suitable for use in [neofs-dev-env](https://github.com/nspcc-dev/neofs-dev-env/) use:
|
||||
```
|
||||
make images
|
||||
```
|
||||
|
@ -86,7 +85,7 @@ the feature/topic you are going to implement.
|
|||
|
||||
# Credits
|
||||
|
||||
FrostFS is maintained by [True Cloud Lab](https://github.com/TrueCloudLab/) with the help and
|
||||
NeoFS is maintained by [NeoSPCC](https://nspcc.ru) with the help and
|
||||
contributions from community members.
|
||||
|
||||
Please see [CREDITS](CREDITS.md) for details.
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
v0.36.0
|
||||
v0.34.0
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package commonflags
|
||||
|
||||
const (
|
||||
ConfigFlag = "config"
|
||||
ConfigFlagShorthand = "c"
|
||||
ConfigFlagUsage = "Config file"
|
||||
|
||||
ConfigDirFlag = "config-dir"
|
||||
ConfigDirFlagUsage = "Config directory"
|
||||
|
||||
Verbose = "verbose"
|
||||
VerboseShorthand = "v"
|
||||
VerboseUsage = "Verbose output"
|
||||
)
|
|
@ -1,244 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type accBalancePair struct {
|
||||
scriptHash util.Uint160
|
||||
balance *big.Int
|
||||
}
|
||||
|
||||
const (
|
||||
dumpBalancesStorageFlag = "storage"
|
||||
dumpBalancesAlphabetFlag = "alphabet"
|
||||
dumpBalancesProxyFlag = "proxy"
|
||||
dumpBalancesUseScriptHashFlag = "script-hash"
|
||||
)
|
||||
|
||||
func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||
var (
|
||||
dumpStorage, _ = cmd.Flags().GetBool(dumpBalancesStorageFlag)
|
||||
dumpAlphabet, _ = cmd.Flags().GetBool(dumpBalancesAlphabetFlag)
|
||||
dumpProxy, _ = cmd.Flags().GetBool(dumpBalancesProxyFlag)
|
||||
nnsCs *state.Contract
|
||||
nmHash util.Uint160
|
||||
)
|
||||
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
if dumpStorage || dumpAlphabet || dumpProxy {
|
||||
r := management.NewReader(inv)
|
||||
nnsCs, err = r.GetContractByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err = nnsResolveHash(inv, nnsCs.Hash, netmapContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
irList, err := fetchIRNodes(c, rolemgmt.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fetchBalances(inv, gas.Hash, irList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "Inner ring nodes balances:", irList)
|
||||
|
||||
if dumpStorage {
|
||||
if err := printStorageNodeBalances(cmd, inv, nmHash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dumpProxy {
|
||||
if err := printProxyContractBalance(cmd, inv, nnsCs.Hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dumpAlphabet {
|
||||
if err := printAlphabetContractBalances(cmd, c, inv, len(irList), nnsCs.Hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printStorageNodeBalances(cmd *cobra.Command, inv *invoker.Invoker, nmHash util.Uint160) error {
|
||||
arr, err := unwrap.Array(inv.Call(nmHash, "netmap"))
|
||||
if err != nil {
|
||||
return errors.New("can't fetch the list of storage nodes")
|
||||
}
|
||||
|
||||
snList := make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
node, ok := arr[i].Value().([]stackitem.Item)
|
||||
if !ok || len(node) == 0 {
|
||||
return errors.New("can't parse the list of storage nodes")
|
||||
}
|
||||
bs, err := node[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("can't parse the list of storage nodes")
|
||||
}
|
||||
var ni netmap.NodeInfo
|
||||
if err := ni.Unmarshal(bs); err != nil {
|
||||
return fmt.Errorf("can't parse the list of storage nodes: %w", err)
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse storage node public key: %w", err)
|
||||
}
|
||||
snList[i].scriptHash = pub.GetScriptHash()
|
||||
}
|
||||
|
||||
if err := fetchBalances(inv, gas.Hash, snList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBalances(cmd, "\nStorage node balances:", snList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printProxyContractBalance(cmd *cobra.Command, inv *invoker.Invoker, nnsHash util.Uint160) error {
|
||||
h, err := nnsResolveHash(inv, nnsHash, proxyContract+".frostfs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
|
||||
}
|
||||
|
||||
proxyList := []accBalancePair{{scriptHash: h}}
|
||||
if err := fetchBalances(inv, gas.Hash, proxyList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBalances(cmd, "\nProxy contract balance:", proxyList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printAlphabetContractBalances(cmd *cobra.Command, c Client, inv *invoker.Invoker, count int, nnsHash util.Uint160) error {
|
||||
alphaList := make([]accBalancePair, count)
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range alphaList {
|
||||
emit.AppCall(w.BinWriter, nnsHash, "resolve", callflag.ReadOnly,
|
||||
getAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
|
||||
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
for i := range alphaList {
|
||||
h, err := parseNNSResolveResult(alphaRes.Stack[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
|
||||
}
|
||||
alphaList[i].scriptHash = h
|
||||
}
|
||||
|
||||
if err := fetchBalances(inv, gas.Hash, alphaList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchIRNodes(c Client, desigHash util.Uint160) ([]accBalancePair, error) {
|
||||
inv := invoker.New(c, nil)
|
||||
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
|
||||
arr, err := getDesignatedByRole(inv, desigHash, noderoles.NeoFSAlphabet, height)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
irList := make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||
}
|
||||
return irList, nil
|
||||
}
|
||||
|
||||
func printBalances(cmd *cobra.Command, prefix string, accounts []accBalancePair) {
|
||||
useScriptHash, _ := cmd.Flags().GetBool(dumpBalancesUseScriptHashFlag)
|
||||
|
||||
cmd.Println(prefix)
|
||||
for i := range accounts {
|
||||
var addr string
|
||||
if useScriptHash {
|
||||
addr = accounts[i].scriptHash.StringLE()
|
||||
} else {
|
||||
addr = address.Uint160ToString(accounts[i].scriptHash)
|
||||
}
|
||||
cmd.Printf("%s: %s\n", addr, fixedn.ToString(accounts[i].balance, 8))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchBalances(c *invoker.Invoker, gasHash util.Uint160, accounts []accBalancePair) error {
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range accounts {
|
||||
emit.AppCall(w.BinWriter, gasHash, "balanceOf", callflag.ReadStates, accounts[i].scriptHash)
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
|
||||
res, err := c.Run(w.Bytes())
|
||||
if err != nil || res.State != vmstate.Halt.String() || len(res.Stack) != len(accounts) {
|
||||
return errors.New("can't fetch account balances")
|
||||
}
|
||||
|
||||
for i := range accounts {
|
||||
bal, err := res.Stack[i].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse account balance: %w", err)
|
||||
}
|
||||
accounts[i].balance = bal
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// neo-go doesn't support []util.Uint160 type:
|
||||
// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/smartcontract/parameter.go#L262
|
||||
// Thus, return []smartcontract.Parameter.
|
||||
func getFrostfsIDAuthorizedKeys(v *viper.Viper, defaultOwner util.Uint160) ([]smartcontract.Parameter, error) {
|
||||
var res []smartcontract.Parameter
|
||||
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: defaultOwner})
|
||||
|
||||
ks := v.GetStringSlice(frostfsIDAuthorizedKeysConfigKey)
|
||||
for i := range ks {
|
||||
h, err := address.StringToUint160(ks[i])
|
||||
if err == nil {
|
||||
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: h})
|
||||
continue
|
||||
}
|
||||
|
||||
h, err = util.Uint160DecodeStringLE(ks[i])
|
||||
if err == nil {
|
||||
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: h})
|
||||
continue
|
||||
}
|
||||
|
||||
pk, err := keys.NewPublicKeyFromString(ks[i])
|
||||
if err == nil {
|
||||
res = append(res, smartcontract.Parameter{Type: smartcontract.Hash160Type, Value: pk.GetScriptHash()})
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("frostfsid: #%d item in authorized_keys is invalid: '%s'", i, ks[i])
|
||||
}
|
||||
return res, nil
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFrostfsIDConfig(t *testing.T) {
|
||||
pks := make([]*keys.PrivateKey, 4)
|
||||
for i := range pks {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pks[i] = pk
|
||||
}
|
||||
|
||||
v := viper.New()
|
||||
v.Set("frostfsid.authorized_keys", []string{
|
||||
pks[0].GetScriptHash().StringLE(),
|
||||
address.Uint160ToString(pks[1].GetScriptHash()),
|
||||
hex.EncodeToString(pks[2].PublicKey().UncompressedBytes()),
|
||||
hex.EncodeToString(pks[3].PublicKey().Bytes()),
|
||||
})
|
||||
|
||||
comm := util.Uint160{1, 2, 3}
|
||||
actual, err := getFrostfsIDAuthorizedKeys(v, comm)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(pks)+1, len(actual))
|
||||
require.Equal(t, smartcontract.Hash160Type, actual[0].Type)
|
||||
require.Equal(t, comm, actual[0].Value)
|
||||
for i := range pks {
|
||||
require.Equal(t, smartcontract.Hash160Type, actual[i+1].Type)
|
||||
require.Equal(t, pks[i].GetScriptHash(), actual[i+1].Value)
|
||||
}
|
||||
|
||||
t.Run("bad key", func(t *testing.T) {
|
||||
v := viper.New()
|
||||
v.Set("frostfsid.authorized_keys", []string{"abc"})
|
||||
|
||||
_, err := getFrostfsIDAuthorizedKeys(v, comm)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
contractsPath = "../../../../../../contract/frostfs-contract-v0.18.0.tar.gz"
|
||||
protoFileName = "proto.yml"
|
||||
)
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
// This test needs frostfs-contract tarball, so it is skipped by default.
|
||||
// It is here for performing local testing after the changes.
|
||||
t.Skip()
|
||||
|
||||
t.Run("1 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 1)
|
||||
})
|
||||
t.Run("4 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 4)
|
||||
})
|
||||
t.Run("7 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 7)
|
||||
})
|
||||
t.Run("16 nodes", func(t *testing.T) {
|
||||
testInitialize(t, 16)
|
||||
})
|
||||
t.Run("max nodes", func(t *testing.T) {
|
||||
testInitialize(t, maxAlphabetNodes)
|
||||
})
|
||||
t.Run("too many nodes", func(t *testing.T) {
|
||||
require.ErrorIs(t, generateTestData(t, t.TempDir(), maxAlphabetNodes+1), ErrTooManyAlphabetNodes)
|
||||
})
|
||||
}
|
||||
|
||||
func testInitialize(t *testing.T, committeeSize int) {
|
||||
testdataDir := t.TempDir()
|
||||
v := viper.GetViper()
|
||||
|
||||
require.NoError(t, generateTestData(t, testdataDir, committeeSize))
|
||||
v.Set(protoConfigPath, filepath.Join(testdataDir, protoFileName))
|
||||
|
||||
// Set to the path or remove the next statement to download from the network.
|
||||
require.NoError(t, initCmd.Flags().Set(contractsInitFlag, contractsPath))
|
||||
|
||||
dumpPath := filepath.Join(testdataDir, "out")
|
||||
require.NoError(t, initCmd.Flags().Set(localDumpFlag, dumpPath))
|
||||
v.Set(alphabetWalletsFlag, testdataDir)
|
||||
v.Set(epochDurationInitFlag, 1)
|
||||
v.Set(maxObjectSizeInitFlag, 1024)
|
||||
|
||||
setTestCredentials(v, committeeSize)
|
||||
require.NoError(t, initializeSideChainCmd(initCmd, nil))
|
||||
|
||||
t.Run("force-new-epoch", func(t *testing.T) {
|
||||
require.NoError(t, forceNewEpoch.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil))
|
||||
})
|
||||
t.Run("set-config", func(t *testing.T) {
|
||||
require.NoError(t, setConfig.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
|
||||
})
|
||||
t.Run("set-policy", func(t *testing.T) {
|
||||
require.NoError(t, setPolicy.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"}))
|
||||
})
|
||||
t.Run("remove-node", func(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pub := hex.EncodeToString(pk.PublicKey().Bytes())
|
||||
require.NoError(t, removeNodes.Flags().Set(localDumpFlag, dumpPath))
|
||||
require.NoError(t, removeNodesCmd(removeNodes, []string{pub}))
|
||||
})
|
||||
}
|
||||
|
||||
func generateTestData(t *testing.T, dir string, size int) error {
|
||||
v := viper.GetViper()
|
||||
v.Set(alphabetWalletsFlag, dir)
|
||||
|
||||
sizeStr := strconv.FormatUint(uint64(size), 10)
|
||||
if err := generateAlphabetCmd.Flags().Set(alphabetSizeFlag, sizeStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setTestCredentials(v, size)
|
||||
if err := generateAlphabetCreds(generateAlphabetCmd, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pubs []string
|
||||
for i := 0; i < size; i++ {
|
||||
p := filepath.Join(dir, innerring.GlagoliticLetter(i).String()+".json")
|
||||
w, err := wallet.NewWalletFromFile(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wallet doesn't exist: %w", err)
|
||||
}
|
||||
for _, acc := range w.Accounts {
|
||||
if acc.Label == singleAccountName {
|
||||
pub, ok := vm.ParseSignatureContract(acc.Contract.Script)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not parse signature script for %s", acc.Address)
|
||||
}
|
||||
pubs = append(pubs, hex.EncodeToString(pub))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg := config.Config{}
|
||||
cfg.ProtocolConfiguration.Magic = 12345
|
||||
cfg.ProtocolConfiguration.ValidatorsCount = uint32(size)
|
||||
cfg.ProtocolConfiguration.TimePerBlock = time.Second
|
||||
cfg.ProtocolConfiguration.StandbyCommittee = pubs // sorted by glagolic letters
|
||||
cfg.ProtocolConfiguration.P2PSigExtensions = true
|
||||
cfg.ProtocolConfiguration.VerifyTransactions = true
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protoPath := filepath.Join(dir, protoFileName)
|
||||
return os.WriteFile(protoPath, data, os.ModePerm)
|
||||
}
|
||||
|
||||
func setTestCredentials(v *viper.Viper, size int) {
|
||||
for i := 0; i < size; i++ {
|
||||
v.Set("credentials."+innerring.GlagoliticLetter(i).String(), strconv.FormatUint(uint64(i), 10))
|
||||
}
|
||||
v.Set("credentials.contract", testContractPassword)
|
||||
}
|
||||
|
||||
func TestNextPollInterval(t *testing.T) {
|
||||
var pollInterval time.Duration
|
||||
var iteration int
|
||||
|
||||
pollInterval, hasChanged := nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 4
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, time.Second, pollInterval)
|
||||
|
||||
iteration = 5
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 2*time.Second, pollInterval)
|
||||
|
||||
iteration = 10
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged)
|
||||
require.Equal(t, 4*time.Second, pollInterval)
|
||||
|
||||
iteration = 20
|
||||
pollInterval = 32 * time.Second
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.True(t, hasChanged) // from 32s to 16s
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
|
||||
pollInterval = 16 * time.Second
|
||||
pollInterval, hasChanged = nextPollInterval(iteration, pollInterval)
|
||||
require.False(t, hasChanged)
|
||||
require.Equal(t, 16*time.Second, pollInterval)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func listNetmapCandidatesNodes(cmd *cobra.Command, _ []string) {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "can't create N3 client: %w", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
r := management.NewReader(inv)
|
||||
|
||||
cs, err := r.GetContractByID(1)
|
||||
commonCmd.ExitOnErr(cmd, "can't get NNS contract info: %w", err)
|
||||
|
||||
nmHash, err := nnsResolveHash(inv, cs.Hash, netmapContract+".frostfs")
|
||||
commonCmd.ExitOnErr(cmd, "can't get netmap contract hash: %w", err)
|
||||
|
||||
res, err := inv.Call(nmHash, "netmapCandidates")
|
||||
commonCmd.ExitOnErr(cmd, "can't fetch list of network config keys from the netmap contract", err)
|
||||
nm, err := netmap.DecodeNetMap(res.Stack)
|
||||
commonCmd.ExitOnErr(cmd, "unable to decode netmap: %w", err)
|
||||
commonCmd.PrettyPrintNetMap(cmd, *nm, !viper.GetBool(commonflags.Verbose))
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getDefaultNetmapContractConfigMap() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag)
|
||||
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag)
|
||||
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
|
||||
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
|
||||
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)
|
||||
m[netmap.WithdrawFeeConfig] = viper.GetInt64(withdrawFeeInitFlag)
|
||||
m[netmap.HomomorphicHashingDisabledKey] = viper.GetBool(homomorphicHashDisabledInitFlag)
|
||||
m[netmap.MaintenanceModeAllowedConfig] = viper.GetBool(maintenanceModeAllowedInitFlag)
|
||||
return m
|
||||
}
|
||||
|
||||
func parseConfigFromNetmapContract(arr []stackitem.Item) (map[string][]byte, error) {
|
||||
m := make(map[string][]byte, len(arr))
|
||||
for _, param := range arr {
|
||||
tuple, ok := param.Value().([]stackitem.Item)
|
||||
if !ok || len(tuple) != 2 {
|
||||
return nil, errors.New("invalid ListConfig response from netmap contract")
|
||||
}
|
||||
|
||||
k, err := tuple[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid config key from netmap contract")
|
||||
}
|
||||
|
||||
v, err := tuple[1].TryBytes()
|
||||
if err != nil {
|
||||
return nil, invalidConfigValueErr(string(k))
|
||||
}
|
||||
m[string(k)] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
execFeeParam = "ExecFeeFactor"
|
||||
storagePriceParam = "StoragePrice"
|
||||
setFeeParam = "FeePerByte"
|
||||
)
|
||||
|
||||
func setPolicyCmd(cmd *cobra.Command, args []string) error {
|
||||
wCtx, err := newInitializeContext(cmd, viper.GetViper())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't to initialize context: %w", err)
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
for i := range args {
|
||||
k, v, found := strings.Cut(args[i], "=")
|
||||
if !found {
|
||||
return fmt.Errorf("invalid parameter format, must be Parameter=Value")
|
||||
}
|
||||
|
||||
switch k {
|
||||
case execFeeParam, storagePriceParam, setFeeParam:
|
||||
default:
|
||||
return fmt.Errorf("parameter must be one of %s, %s and %s", execFeeParam, storagePriceParam, setFeeParam)
|
||||
}
|
||||
|
||||
value, err := strconv.ParseUint(v, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse parameter value '%s': %w", args[1], err)
|
||||
}
|
||||
|
||||
emit.AppCall(bw.BinWriter, policy.Hash, "set"+k, callflag.All, int64(value))
|
||||
}
|
||||
|
||||
if err := wCtx.sendCommitteeTx(bw.Bytes(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wCtx.awaitTx()
|
||||
}
|
||||
|
||||
func dumpPolicyCmd(cmd *cobra.Command, _ []string) error {
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
commonCmd.ExitOnErr(cmd, "can't create N3 client:", err)
|
||||
|
||||
inv := invoker.New(c, nil)
|
||||
policyContract := policy.NewReader(inv)
|
||||
|
||||
execFee, err := policyContract.GetExecFeeFactor()
|
||||
commonCmd.ExitOnErr(cmd, "can't get execution fee factor:", err)
|
||||
|
||||
feePerByte, err := policyContract.GetFeePerByte()
|
||||
commonCmd.ExitOnErr(cmd, "can't get fee per byte:", err)
|
||||
|
||||
storagePrice, err := policyContract.GetStoragePrice()
|
||||
commonCmd.ExitOnErr(cmd, "can't get storage price:", err)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
|
||||
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Execution Fee Factor:\t%d (int)\n", execFee)))
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Fee Per Byte:\t%d (int)\n", feePerByte)))
|
||||
_, _ = tw.Write([]byte(fmt.Sprintf("Storage Price:\t%d (int)\n", storagePrice)))
|
||||
|
||||
_ = tw.Flush()
|
||||
cmd.Print(buf.String())
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/storagecfg"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||
utilConfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frostfs-adm",
|
||||
Short: "FrostFS Administrative Tool",
|
||||
Long: `FrostFS Administrative Tool provides functions to setup and
|
||||
manage FrostFS network deployment.`,
|
||||
RunE: entryPoint,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(func() { initConfig(rootCmd) })
|
||||
// we need to init viper config to bind viper and cobra configurations for
|
||||
// rpc endpoint, alphabet wallet dir, key credentials, etc.
|
||||
|
||||
// use stdout as default output for cmd.Print()
|
||||
rootCmd.SetOut(os.Stdout)
|
||||
|
||||
rootCmd.PersistentFlags().StringP(commonflags.ConfigFlag, commonflags.ConfigFlagShorthand, "", commonflags.ConfigFlagUsage)
|
||||
rootCmd.PersistentFlags().String(commonflags.ConfigDirFlag, "", commonflags.ConfigDirFlagUsage)
|
||||
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand, false, commonflags.VerboseUsage)
|
||||
_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
|
||||
rootCmd.Flags().Bool("version", false, "Application version")
|
||||
|
||||
rootCmd.AddCommand(config.RootCmd)
|
||||
rootCmd.AddCommand(morph.RootCmd)
|
||||
rootCmd.AddCommand(storagecfg.RootCmd)
|
||||
|
||||
rootCmd.AddCommand(autocomplete.Command("frostfs-adm"))
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||
}
|
||||
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, _ []string) error {
|
||||
printVersion, _ := cmd.Flags().GetBool("version")
|
||||
if printVersion {
|
||||
cmd.Print(misc.BuildInfo("FrostFS Adm"))
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Usage()
|
||||
}
|
||||
|
||||
func initConfig(cmd *cobra.Command) {
|
||||
configFile, err := cmd.Flags().GetString(commonflags.ConfigFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
viper.SetConfigType("yml")
|
||||
viper.SetConfigFile(configFile)
|
||||
_ = viper.ReadInConfig() // if config file is set but unavailable, ignore it
|
||||
}
|
||||
|
||||
configDir, err := cmd.Flags().GetString(commonflags.ConfigDirFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if configDir != "" {
|
||||
_ = utilConfig.ReadConfigDir(viper.GetViper(), configDir) // if config files cannot be read, ignore it
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
# Extended headers
|
||||
|
||||
## Overview
|
||||
|
||||
Extended headers are used for request/response. They may contain any
|
||||
user-defined headers to be interpreted on application level. Key name must be a
|
||||
unique valid UTF-8 string. Value can't be empty. Requests or Responses with
|
||||
duplicated header names or headers with empty values are considered invalid.
|
||||
|
||||
## Existing headers
|
||||
|
||||
There are some "well-known" headers starting with `__SYSTEM__` prefix that
|
||||
affect system behaviour. For backward compatibility, the same set of
|
||||
"well-known" headers may also use `__NEOFS__` prefix:
|
||||
|
||||
* `__SYSTEM__NETMAP_EPOCH` - netmap epoch to use for object placement calculation. The `value` is string
|
||||
encoded `uint64` in decimal presentation. If set to '0' or omitted, the
|
||||
current epoch only will be used.
|
||||
* `__SYSTEM__NETMAP_LOOKUP_DEPTH` - if object can't be found using current epoch's netmap, this header limits
|
||||
how many past epochs the node can look up through. Depth is applied to a current epoch or the value
|
||||
of `__SYSTEM__NETMAP_EPOCH` attribute. The `value` is string encoded `uint64` in decimal presentation.
|
||||
If set to '0' or not set, only the current epoch is used.
|
||||
|
||||
## `frostfs-cli` commands with `--xhdr`
|
||||
|
||||
List of commands with support of extended headers:
|
||||
* `container list-objects`
|
||||
* `object delete/get/hash/head/lock/put/range/search`
|
||||
|
||||
Example:
|
||||
```shell
|
||||
$ frostfs-cli object put -r s01.frostfs.devenv:8080 -w wallet.json --cid CID --file FILE --xhdr "__SYSTEM__NETMAP_EPOCH=777"
|
||||
```
|
|
@ -1,15 +0,0 @@
|
|||
// Package internal provides functionality for FrostFS CLI application
|
||||
// communication with FrostFS network.
|
||||
//
|
||||
// The base client for accessing remote nodes via FrostFS API is a FrostFS SDK
|
||||
// Go API client. However, although it encapsulates a useful piece of business
|
||||
// logic (e.g. the signature mechanism), the FrostFS CLI application does not
|
||||
// fully use the client's flexible interface.
|
||||
//
|
||||
// In this regard, this package provides functions over base API client
|
||||
// necessary for the application. This allows you to concentrate the entire
|
||||
// spectrum of the client's use in one place (this will be convenient both when
|
||||
// updating the base client and for evaluating the UX of SDK library). So it is
|
||||
// expected that all application packages will be limited to this package for
|
||||
// the development of functionality requiring FrostFS API communication.
|
||||
package internal
|
|
@ -1,102 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||
tracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
|
||||
|
||||
// GetSDKClientByFlag returns default frostfs-sdk-go client using the specified flag for the address.
|
||||
// On error, outputs to stderr of cmd and exits with non-zero code.
|
||||
func GetSDKClientByFlag(cmd *cobra.Command, key *ecdsa.PrivateKey, endpointFlag string) *client.Client {
|
||||
cli, err := getSDKClientByFlag(cmd, key, endpointFlag)
|
||||
if err != nil {
|
||||
commonCmd.ExitOnErr(cmd, "can't create API client: %w", err)
|
||||
}
|
||||
return cli
|
||||
}
|
||||
|
||||
func getSDKClientByFlag(cmd *cobra.Command, key *ecdsa.PrivateKey, endpointFlag string) (*client.Client, error) {
|
||||
var addr network.Address
|
||||
|
||||
err := addr.FromString(viper.GetString(endpointFlag))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", errInvalidEndpoint, err)
|
||||
}
|
||||
return GetSDKClient(cmd.Context(), cmd, key, addr)
|
||||
}
|
||||
|
||||
// GetSDKClient returns default frostfs-sdk-go client.
|
||||
func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) {
|
||||
var c client.Client
|
||||
|
||||
prmInit := client.PrmInit{
|
||||
Key: *key,
|
||||
}
|
||||
|
||||
prmDial := client.PrmDial{
|
||||
Endpoint: addr.URIAddr(),
|
||||
GRPCDialOptions: []grpc.DialOption{
|
||||
grpc.WithChainUnaryInterceptor(tracing.NewUnaryClientInteceptor()),
|
||||
grpc.WithChainStreamInterceptor(tracing.NewStreamClientInterceptor()),
|
||||
},
|
||||
}
|
||||
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
|
||||
// In CLI we can only set a timeout for the whole operation.
|
||||
// By also setting stream timeout we ensure that no operation hands
|
||||
// for too long.
|
||||
prmDial.DialTimeout = timeout
|
||||
prmDial.StreamTimeout = timeout
|
||||
|
||||
common.PrintVerbose(cmd, "Set request timeout to %s.", timeout)
|
||||
}
|
||||
|
||||
c.Init(prmInit)
|
||||
|
||||
if err := c.Dial(ctx, prmDial); err != nil {
|
||||
return nil, fmt.Errorf("can't init SDK client: %w", err)
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// GetCurrentEpoch returns current epoch.
|
||||
func GetCurrentEpoch(ctx context.Context, cmd *cobra.Command, endpoint string) (uint64, error) {
|
||||
var addr network.Address
|
||||
|
||||
if err := addr.FromString(endpoint); err != nil {
|
||||
return 0, fmt.Errorf("can't parse RPC endpoint: %w", err)
|
||||
}
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("can't generate key to sign query: %w", err)
|
||||
}
|
||||
|
||||
c, err := GetSDKClient(ctx, cmd, key, addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ni, err := c.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ni.Info().CurrentEpoch(), nil
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type spanKey struct{}
|
||||
|
||||
// StopClientCommandSpan stops tracing span for the command and prints trace ID on the standard output.
|
||||
func StopClientCommandSpan(cmd *cobra.Command, _ []string) {
|
||||
span, ok := cmd.Context().Value(spanKey{}).(trace.Span)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
span.End()
|
||||
|
||||
// Noop provider cannot fail on flush.
|
||||
_ = tracing.Shutdown(cmd.Context())
|
||||
|
||||
cmd.PrintErrf("Trace ID: %s\n", span.SpanContext().TraceID())
|
||||
}
|
||||
|
||||
// StartClientCommandSpan starts tracing span for the command.
|
||||
func StartClientCommandSpan(cmd *cobra.Command) {
|
||||
enableTracing, err := cmd.Flags().GetBool(commonflags.TracingFlag)
|
||||
if err != nil || !enableTracing {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = tracing.Setup(cmd.Context(), tracing.Config{
|
||||
Enabled: true,
|
||||
Exporter: tracing.NoOpExporter,
|
||||
Service: "frostfs-cli",
|
||||
Version: misc.Version,
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "init tracing: %w", err)
|
||||
|
||||
var components sort.StringSlice
|
||||
for c := cmd; c != nil; c = c.Parent() {
|
||||
components = append(components, c.Name())
|
||||
}
|
||||
for i, j := 0, len(components)-1; i < j; {
|
||||
components.Swap(i, j)
|
||||
i++
|
||||
j--
|
||||
}
|
||||
|
||||
operation := strings.Join(components, ".")
|
||||
ctx, span := tracing.StartSpanFromContext(cmd.Context(), operation)
|
||||
ctx = context.WithValue(ctx, spanKey{}, span)
|
||||
cmd.SetContext(ctx)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package main
|
||||
|
||||
import cmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var printACLCmd = &cobra.Command{
|
||||
Use: "print",
|
||||
Short: "Pretty print basic ACL from the HEX representation",
|
||||
Example: `frostfs-cli acl basic print 0x1C8C8CCC`,
|
||||
Long: `Pretty print basic ACL from the HEX representation.
|
||||
Few roles have exclusive default access to set of operation, even if particular bit deny it.
|
||||
Container have access to the operations of the data replication mechanism:
|
||||
Get, Head, Put, Search, Hash.
|
||||
InnerRing members are allowed to data audit ops only:
|
||||
Get, Head, Hash, Search.`,
|
||||
Run: printACL,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
|
||||
func printACL(cmd *cobra.Command, args []string) {
|
||||
var bacl acl.Basic
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse basic acl: %w", bacl.DecodeString(args[0]))
|
||||
util.PrettyPrintTableBACL(cmd, &bacl)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "basic",
|
||||
Short: "Operations with Basic Access Control Lists",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(printACLCmd)
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package extended
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create extended ACL from the text representation",
|
||||
Long: `Create extended ACL from the text representation.
|
||||
|
||||
Rule consist of these blocks: <action> <operation> [<filter1> ...] [<target1> ...]
|
||||
|
||||
Action is 'allow' or 'deny'.
|
||||
|
||||
Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'.
|
||||
|
||||
Filter consists of <typ>:<key><match><value>
|
||||
Typ is 'obj' for object applied filter or 'req' for request applied filter.
|
||||
Key is a valid unicode string corresponding to object or request header key.
|
||||
Well-known system object headers start with '$Object:' prefix.
|
||||
User defined headers start without prefix.
|
||||
Read more about filter keys at git.frostfs.info.com/TrueCloudLab/frostfs-api/src/branch/master/proto-docs/acl.md#message-eaclrecordfilter
|
||||
Match is '=' for matching and '!=' for non-matching filter.
|
||||
Value is a valid unicode string corresponding to object or request header value.
|
||||
|
||||
Target is
|
||||
'user' for container owner,
|
||||
'system' for Storage nodes in container and Inner Ring nodes,
|
||||
'others' for all other request senders,
|
||||
'pubkey:<key1>,<key2>,...' for exact request sender, where <key> is a hex-encoded 33-byte public key.
|
||||
|
||||
When both '--rule' and '--file' arguments are used, '--rule' records will be placed higher in resulting extended ACL table.
|
||||
`,
|
||||
Example: `frostfs-cli acl extended create --cid EutHBsdT1YCzHxjCfQHnLPL1vFrkSyLSio4vkphfnEk -f rules.txt --out table.json
|
||||
frostfs-cli acl extended create --cid EutHBsdT1YCzHxjCfQHnLPL1vFrkSyLSio4vkphfnEk -r 'allow get obj:Key=Value others' -r 'deny put others'`,
|
||||
Run: createEACL,
|
||||
}
|
||||
|
||||
func init() {
|
||||
createCmd.Flags().StringArrayP("rule", "r", nil, "Extended ACL table record to apply")
|
||||
createCmd.Flags().StringP("file", "f", "", "Read list of extended ACL table records from text file")
|
||||
createCmd.Flags().StringP("out", "o", "", "Save JSON formatted extended ACL table in file")
|
||||
createCmd.Flags().StringP(commonflags.CIDFlag, "", "", commonflags.CIDFlagUsage)
|
||||
|
||||
_ = cobra.MarkFlagFilename(createCmd.Flags(), "file")
|
||||
_ = cobra.MarkFlagFilename(createCmd.Flags(), "out")
|
||||
}
|
||||
|
||||
func createEACL(cmd *cobra.Command, _ []string) {
|
||||
rules, _ := cmd.Flags().GetStringArray("rule")
|
||||
fileArg, _ := cmd.Flags().GetString("file")
|
||||
outArg, _ := cmd.Flags().GetString("out")
|
||||
cidArg, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
|
||||
var containerID cid.ID
|
||||
if cidArg != "" {
|
||||
if err := containerID.DecodeString(cidArg); err != nil {
|
||||
cmd.PrintErrf("invalid container ID: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
rulesFile, err := getRulesFromFile(fileArg)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("can't read rules from file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
rules = append(rules, rulesFile...)
|
||||
if len(rules) == 0 {
|
||||
cmd.PrintErrln("no extended ACL rules has been provided")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tb := eacl.NewTable()
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse provided rules: %w", util.ParseEACLRules(tb, rules))
|
||||
|
||||
tb.SetCID(containerID)
|
||||
|
||||
data, err := tb.MarshalJSON()
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = json.Indent(buf, data, "", " ")
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(outArg) == 0 {
|
||||
cmd.Println(buf)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(outArg, buf.Bytes(), 0o644)
|
||||
if err != nil {
|
||||
cmd.PrintErrln(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getRulesFromFile(filename string) ([]string, error) {
|
||||
if len(filename) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package extended
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var printEACLCmd = &cobra.Command{
|
||||
Use: "print",
|
||||
Short: "Pretty print extended ACL from the file(in text or json format) or for given container.",
|
||||
Run: printEACL,
|
||||
}
|
||||
|
||||
func init() {
|
||||
flags := printEACLCmd.Flags()
|
||||
flags.StringP("file", "f", "",
|
||||
"Read list of extended ACL table records from text or json file")
|
||||
_ = printEACLCmd.MarkFlagRequired("file")
|
||||
}
|
||||
|
||||
func printEACL(cmd *cobra.Command, _ []string) {
|
||||
file, _ := cmd.Flags().GetString("file")
|
||||
eaclTable := new(eacl.Table)
|
||||
data, err := os.ReadFile(file)
|
||||
commonCmd.ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||
if strings.HasSuffix(file, ".json") {
|
||||
commonCmd.ExitOnErr(cmd, "unable to parse json: %w", eaclTable.UnmarshalJSON(data))
|
||||
} else {
|
||||
rules := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
commonCmd.ExitOnErr(cmd, "can't parse file with EACL: %w", util.ParseEACLRules(eaclTable, rules))
|
||||
}
|
||||
util.PrettyPrintTableEACL(cmd, eaclTable)
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/basic"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl/extended"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "acl",
|
||||
Short: "Operations with Access Control Lists",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(extended.Cmd)
|
||||
Cmd.AddCommand(basic.Cmd)
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/autocomplete"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(autocomplete.Command("frostfs-cli"))
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
containerApi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
containerACL string
|
||||
containerPolicy string
|
||||
containerAttributes []string
|
||||
containerAwait bool
|
||||
containerName string
|
||||
containerNnsName string
|
||||
containerNnsZone string
|
||||
containerNoTimestamp bool
|
||||
force bool
|
||||
)
|
||||
|
||||
var createContainerCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create new container",
|
||||
Long: `Create new container and register it in the FrostFS.
|
||||
It will be stored in sidechain when inner ring will accepts it.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
placementPolicy, err := parseContainerPolicy(cmd, containerPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
|
||||
key := key.Get(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC)
|
||||
|
||||
if !force {
|
||||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
resmap, err := internalclient.NetMapSnapshot(cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(cmd, "unable to get netmap snapshot to validate container placement, "+
|
||||
"use --force option to skip this check: %w", err)
|
||||
|
||||
nodesByRep, err := resmap.NetMap().ContainerNodes(*placementPolicy, nil)
|
||||
commonCmd.ExitOnErr(cmd, "could not build container nodes based on given placement policy, "+
|
||||
"use --force option to skip this check: %w", err)
|
||||
|
||||
for i, nodes := range nodesByRep {
|
||||
if placementPolicy.ReplicaNumberByIndex(i) > uint32(len(nodes)) {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf(
|
||||
"the number of nodes '%d' in selector is not enough for the number of replicas '%d', "+
|
||||
"use --force option to skip this check",
|
||||
len(nodes),
|
||||
placementPolicy.ReplicaNumberByIndex(i),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cnr container.Container
|
||||
cnr.Init()
|
||||
|
||||
err = parseAttributes(&cnr, containerAttributes)
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
|
||||
var basicACL acl.Basic
|
||||
commonCmd.ExitOnErr(cmd, "decode basic ACL string: %w", basicACL.DecodeString(containerACL))
|
||||
|
||||
tok := getSession(cmd)
|
||||
|
||||
if tok != nil {
|
||||
issuer := tok.Issuer()
|
||||
cnr.SetOwner(issuer)
|
||||
} else {
|
||||
var idOwner user.ID
|
||||
user.IDFromKey(&idOwner, key.PublicKey)
|
||||
|
||||
cnr.SetOwner(idOwner)
|
||||
}
|
||||
|
||||
cnr.SetPlacementPolicy(*placementPolicy)
|
||||
cnr.SetBasicACL(basicACL)
|
||||
|
||||
var syncContainerPrm internalclient.SyncContainerPrm
|
||||
syncContainerPrm.SetClient(cli)
|
||||
syncContainerPrm.SetContainer(&cnr)
|
||||
|
||||
_, err = internalclient.SyncContainerSettings(cmd.Context(), syncContainerPrm)
|
||||
commonCmd.ExitOnErr(cmd, "syncing container's settings rpc error: %w", err)
|
||||
|
||||
putPrm := internalclient.PutContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerPut{
|
||||
Container: &cnr,
|
||||
Session: tok,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := internalclient.PutContainer(cmd.Context(), putPrm)
|
||||
commonCmd.ExitOnErr(cmd, "put container rpc error: %w", err)
|
||||
|
||||
id := res.ID()
|
||||
|
||||
cmd.Println("container ID:", id)
|
||||
|
||||
if containerAwait {
|
||||
cmd.Println("awaiting...")
|
||||
|
||||
getPrm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < awaitTimeout; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||
if err == nil {
|
||||
cmd.Println("container has been persisted on sidechain")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "", errCreateTimeout)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func initContainerCreateCmd() {
|
||||
flags := createContainerCmd.Flags()
|
||||
|
||||
// Init common flags
|
||||
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
|
||||
flags.Bool(commonflags.TracingFlag, false, commonflags.TracingFlagUsage)
|
||||
flags.DurationP(commonflags.Timeout, commonflags.TimeoutShorthand, commonflags.TimeoutDefault, commonflags.TimeoutUsage)
|
||||
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
|
||||
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
|
||||
|
||||
flags.StringVar(&containerACL, "basic-acl", acl.NamePrivate, fmt.Sprintf("HEX encoded basic ACL value or keywords like '%s', '%s', '%s'",
|
||||
acl.NamePublicRW, acl.NamePrivate, acl.NamePublicROExtended,
|
||||
))
|
||||
flags.StringVarP(&containerPolicy, "policy", "p", "", "QL-encoded or JSON-encoded placement policy or path to file with it")
|
||||
flags.StringSliceVarP(&containerAttributes, "attributes", "a", nil, "Comma separated pairs of container attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.BoolVar(&containerAwait, "await", false, "Block execution until container is persisted")
|
||||
flags.StringVar(&containerName, "name", "", "Container name attribute")
|
||||
flags.StringVar(&containerNnsName, "nns-name", "", "Container nns name attribute")
|
||||
flags.StringVar(&containerNnsZone, "nns-zone", "", "Container nns zone attribute")
|
||||
flags.BoolVar(&containerNoTimestamp, "disable-timestamp", false, "Disable timestamp container attribute")
|
||||
flags.BoolVarP(&force, commonflags.ForceFlag, commonflags.ForceFlagShorthand, false,
|
||||
"Skip placement validity check")
|
||||
}
|
||||
|
||||
func parseContainerPolicy(cmd *cobra.Command, policyString string) (*netmap.PlacementPolicy, error) {
|
||||
_, err := os.Stat(policyString) // check if `policyString` is a path to file with placement policy
|
||||
if err == nil {
|
||||
common.PrintVerbose(cmd, "Reading placement policy from file: %s", policyString)
|
||||
|
||||
data, err := os.ReadFile(policyString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read file with placement policy: %w", err)
|
||||
}
|
||||
|
||||
policyString = string(data)
|
||||
}
|
||||
|
||||
var result netmap.PlacementPolicy
|
||||
|
||||
err = result.DecodeString(policyString)
|
||||
if err == nil {
|
||||
common.PrintVerbose(cmd, "Parsed QL encoded policy")
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
if err := result.UnmarshalJSON([]byte(policyString)); err == nil {
|
||||
common.PrintVerbose(cmd, "Parsed JSON encoded policy")
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't parse placement policy: %w", err)
|
||||
}
|
||||
|
||||
func parseAttributes(dst *container.Container, attributes []string) error {
|
||||
for i := range attributes {
|
||||
k, v, found := strings.Cut(attributes[i], attributeDelimiter)
|
||||
if !found {
|
||||
return errors.New("invalid container attribute")
|
||||
}
|
||||
|
||||
dst.SetAttribute(k, v)
|
||||
}
|
||||
|
||||
if !containerNoTimestamp {
|
||||
container.SetCreationTime(dst, time.Now())
|
||||
}
|
||||
|
||||
if containerName != "" {
|
||||
container.SetName(dst, containerName)
|
||||
}
|
||||
|
||||
if containerNnsName != "" {
|
||||
dst.SetAttribute(containerApi.SysAttributeName, containerNnsName)
|
||||
}
|
||||
if containerNnsZone != "" {
|
||||
dst.SetAttribute(containerApi.SysAttributeZone, containerNnsZone)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var deleteContainerCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete existing container",
|
||||
Long: `Delete existing container.
|
||||
Only owner of the container has a permission to remove container.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
id := parseContainerID(cmd)
|
||||
|
||||
tok := getSession(cmd)
|
||||
|
||||
pk := key.Get(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
if force, _ := cmd.Flags().GetBool(commonflags.ForceFlag); !force {
|
||||
common.PrintVerbose(cmd, "Reading the container to check ownership...")
|
||||
|
||||
getPrm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
|
||||
resGet, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||
commonCmd.ExitOnErr(cmd, "can't get the container: %w", err)
|
||||
|
||||
owner := resGet.Container().Owner()
|
||||
|
||||
if tok != nil {
|
||||
common.PrintVerbose(cmd, "Checking session issuer...")
|
||||
|
||||
if !tok.Issuer().Equals(owner) {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("session issuer differs with the container owner: expected %s, has %s", owner, tok.Issuer()))
|
||||
}
|
||||
} else {
|
||||
common.PrintVerbose(cmd, "Checking provided account...")
|
||||
|
||||
var acc user.ID
|
||||
user.IDFromKey(&acc, pk.PublicKey)
|
||||
|
||||
if !acc.Equals(owner) {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("provided account differs with the container owner: expected %s, has %s", owner, acc))
|
||||
}
|
||||
}
|
||||
|
||||
common.PrintVerbose(cmd, "Account matches the container owner.")
|
||||
|
||||
if tok != nil {
|
||||
common.PrintVerbose(cmd, "Skip searching for LOCK objects - session provided.")
|
||||
} else {
|
||||
fs := objectSDK.NewSearchFilters()
|
||||
fs.AddTypeFilter(objectSDK.MatchStringEqual, objectSDK.TypeLock)
|
||||
|
||||
var searchPrm internalclient.SearchObjectsPrm
|
||||
searchPrm.SetClient(cli)
|
||||
searchPrm.SetContainerID(id)
|
||||
searchPrm.SetFilters(fs)
|
||||
searchPrm.SetTTL(2)
|
||||
|
||||
common.PrintVerbose(cmd, "Searching for LOCK objects...")
|
||||
|
||||
res, err := internalclient.SearchObjects(cmd.Context(), searchPrm)
|
||||
commonCmd.ExitOnErr(cmd, "can't search for LOCK objects: %w", err)
|
||||
|
||||
if len(res.IDList()) != 0 {
|
||||
commonCmd.ExitOnErr(cmd, "",
|
||||
fmt.Errorf("container wasn't removed because LOCK objects were found, "+
|
||||
"use --%s flag to remove anyway", commonflags.ForceFlag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delPrm := internalclient.DeleteContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerDelete{
|
||||
ContainerID: &id,
|
||||
Session: tok,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := internalclient.DeleteContainer(cmd.Context(), delPrm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
cmd.Println("container delete method invoked")
|
||||
|
||||
if containerAwait {
|
||||
cmd.Println("awaiting...")
|
||||
|
||||
getPrm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &id,
|
||||
},
|
||||
}
|
||||
|
||||
for i := 0; i < awaitTimeout; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_, err := internalclient.GetContainer(cmd.Context(), getPrm)
|
||||
if err != nil {
|
||||
cmd.Println("container has been removed:", containerID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "", errDeleteTimeout)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func initContainerDeleteCmd() {
|
||||
flags := deleteContainerCmd.Flags()
|
||||
|
||||
flags.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
|
||||
flags.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
|
||||
flags.StringP(commonflags.RPC, commonflags.RPCShorthand, commonflags.RPCDefault, commonflags.RPCUsage)
|
||||
flags.Bool(commonflags.TracingFlag, false, commonflags.TracingFlagUsage)
|
||||
|
||||
flags.StringVar(&containerID, commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
flags.BoolVar(&containerAwait, "await", false, "Block execution until container is removed")
|
||||
flags.BoolP(commonflags.ForceFlag, commonflags.ForceFlagShorthand, false, "Skip validation checks (ownership, presence of LOCK objects)")
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
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"
|
||||
containerSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// flags of list command.
|
||||
const (
|
||||
flagListPrintAttr = "with-attr"
|
||||
flagListContainerOwner = "owner"
|
||||
flagListName = "name"
|
||||
)
|
||||
|
||||
// flag vars of list command.
|
||||
var (
|
||||
flagVarListPrintAttr bool
|
||||
flagVarListContainerOwner string
|
||||
flagVarListName string
|
||||
)
|
||||
|
||||
var listContainersCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all created containers",
|
||||
Long: "List all created containers",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var idUser user.ID
|
||||
|
||||
key := key.GetOrGenerate(cmd)
|
||||
|
||||
if flagVarListContainerOwner == "" {
|
||||
user.IDFromKey(&idUser, key.PublicKey)
|
||||
} else {
|
||||
err := idUser.DecodeString(flagVarListContainerOwner)
|
||||
commonCmd.ExitOnErr(cmd, "invalid user ID: %w", err)
|
||||
}
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC)
|
||||
|
||||
var prm internalclient.ListContainersPrm
|
||||
prm.SetClient(cli)
|
||||
prm.Account = idUser
|
||||
|
||||
res, err := internalclient.ListContainers(cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
prmGet := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
}
|
||||
|
||||
containerIDs := res.SortedIDList()
|
||||
for _, cnrID := range containerIDs {
|
||||
if flagVarListName == "" && !flagVarListPrintAttr {
|
||||
cmd.Println(cnrID.String())
|
||||
continue
|
||||
}
|
||||
|
||||
cnrID := cnrID
|
||||
prmGet.ClientParams.ContainerID = &cnrID
|
||||
res, err := internalclient.GetContainer(cmd.Context(), prmGet)
|
||||
if err != nil {
|
||||
cmd.Printf(" failed to read attributes: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
cnr := res.Container()
|
||||
if cnrName := containerSDK.Name(cnr); flagVarListName != "" && cnrName != flagVarListName {
|
||||
continue
|
||||
}
|
||||
cmd.Println(cnrID.String())
|
||||
|
||||
if flagVarListPrintAttr {
|
||||
cnr.IterateAttributes(func(key, val string) {
|
||||
if !strings.HasPrefix(key, container.SysAttributePrefix) && !strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
|
||||
// FIXME(@cthulhu-rider): https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/97
|
||||
// Use dedicated method to skip system attributes.
|
||||
cmd.Printf(" %s: %s\n", key, val)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func initContainerListContainersCmd() {
|
||||
commonflags.Init(listContainersCmd)
|
||||
|
||||
flags := listContainersCmd.Flags()
|
||||
|
||||
flags.StringVar(&flagVarListName, flagListName, "",
|
||||
"List containers by the attribute name",
|
||||
)
|
||||
flags.StringVar(&flagVarListContainerOwner, flagListContainerOwner, "",
|
||||
"Owner of containers (omit to use owner from private key)",
|
||||
)
|
||||
flags.BoolVar(&flagVarListPrintAttr, flagListPrintAttr, false,
|
||||
"Request and print attributes of each container",
|
||||
)
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type policyPlaygroundREPL struct {
|
||||
cmd *cobra.Command
|
||||
args []string
|
||||
nodes map[string]netmap.NodeInfo
|
||||
}
|
||||
|
||||
func newPolicyPlaygroundREPL(cmd *cobra.Command, args []string) (*policyPlaygroundREPL, error) {
|
||||
return &policyPlaygroundREPL{
|
||||
cmd: cmd,
|
||||
args: args,
|
||||
nodes: map[string]netmap.NodeInfo{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleLs(args []string) error {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("too many arguments for command 'ls': got %d, want 0", len(args))
|
||||
}
|
||||
i := 1
|
||||
for id, node := range repl.nodes {
|
||||
var attrs []string
|
||||
node.IterateAttributes(func(k, v string) {
|
||||
attrs = append(attrs, fmt.Sprintf("%s:%q", k, v))
|
||||
})
|
||||
fmt.Printf("\t%2d: id=%s attrs={%v}\n", i, id, strings.Join(attrs, " "))
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleAdd(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("too few arguments for command 'add': got %d, want >0", len(args))
|
||||
}
|
||||
id := args[0]
|
||||
key, err := hex.DecodeString(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("node id must be a hex string: got %q: %v", id, err)
|
||||
}
|
||||
node := repl.nodes[id]
|
||||
node.SetPublicKey(key)
|
||||
for _, attr := range args[1:] {
|
||||
kv := strings.Split(attr, ":")
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("node attributes must be in the format 'KEY:VALUE': got %q", attr)
|
||||
}
|
||||
node.SetAttribute(kv[0], kv[1])
|
||||
}
|
||||
repl.nodes[id] = node
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleLoad(args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("too few arguments for command 'add': got %d, want 1", len(args))
|
||||
}
|
||||
|
||||
jsonNetmap := map[string]map[string]string{}
|
||||
|
||||
b, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading netmap file %q: %v", args[0], err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &jsonNetmap); err != nil {
|
||||
return fmt.Errorf("decoding json netmap: %v", err)
|
||||
}
|
||||
|
||||
repl.nodes = make(map[string]netmap.NodeInfo)
|
||||
for id, attrs := range jsonNetmap {
|
||||
key, err := hex.DecodeString(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("node id must be a hex string: got %q: %v", id, err)
|
||||
}
|
||||
|
||||
node := repl.nodes[id]
|
||||
node.SetPublicKey(key)
|
||||
for k, v := range attrs {
|
||||
node.SetAttribute(k, v)
|
||||
}
|
||||
repl.nodes[id] = node
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleRemove(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("too few arguments for command 'remove': got %d, want >0", len(args))
|
||||
}
|
||||
id := args[0]
|
||||
if _, exists := repl.nodes[id]; exists {
|
||||
delete(repl.nodes, id)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("node not found: id=%q", id)
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) handleEval(args []string) error {
|
||||
policyStr := strings.TrimSpace(strings.Join(args, " "))
|
||||
var nodes [][]netmap.NodeInfo
|
||||
nm := repl.netMap()
|
||||
|
||||
if strings.HasPrefix(policyStr, "CBF") || strings.HasPrefix(policyStr, "SELECT") || strings.HasPrefix(policyStr, "FILTER") {
|
||||
// Assume that the input is a partial SELECT-FILTER expression.
|
||||
// Full inline policies always start with UNIQUE or REP keywords,
|
||||
// or different prefixes when it's the case of an external file.
|
||||
sfExpr, err := netmap.DecodeSelectFilterString(policyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing select-filter expression: %v", err)
|
||||
}
|
||||
nodes, err = nm.SelectFilterNodes(sfExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building select-filter nodes: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Assume that the input is a full policy or input file otherwise.
|
||||
placementPolicy, err := parseContainerPolicy(repl.cmd, policyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing placement policy: %v", err)
|
||||
}
|
||||
nodes, err = nm.ContainerNodes(*placementPolicy, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("building container nodes: %v", err)
|
||||
}
|
||||
}
|
||||
for i, ns := range nodes {
|
||||
var ids []string
|
||||
for _, node := range ns {
|
||||
ids = append(ids, hex.EncodeToString(node.PublicKey()))
|
||||
}
|
||||
fmt.Printf("\t%2d: %v\n", i+1, ids)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) netMap() netmap.NetMap {
|
||||
var nm netmap.NetMap
|
||||
var nodes []netmap.NodeInfo
|
||||
for _, node := range repl.nodes {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
nm.SetNodes(nodes)
|
||||
return nm
|
||||
}
|
||||
|
||||
func (repl *policyPlaygroundREPL) run() error {
|
||||
if len(viper.GetString(commonflags.RPC)) > 0 {
|
||||
key := key.GetOrGenerate(repl.cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(repl.cmd, key, commonflags.RPC)
|
||||
|
||||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
resp, err := internalclient.NetMapSnapshot(repl.cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(repl.cmd, "unable to get netmap snapshot to populate initial netmap: %w", err)
|
||||
|
||||
for _, node := range resp.NetMap().Nodes() {
|
||||
id := hex.EncodeToString(node.PublicKey())
|
||||
repl.nodes[id] = node
|
||||
}
|
||||
}
|
||||
|
||||
cmdHandlers := map[string]func([]string) error{
|
||||
"list": repl.handleLs,
|
||||
"ls": repl.handleLs,
|
||||
"add": repl.handleAdd,
|
||||
"load": repl.handleLoad,
|
||||
"remove": repl.handleRemove,
|
||||
"rm": repl.handleRemove,
|
||||
"eval": repl.handleEval,
|
||||
}
|
||||
for reader := bufio.NewReader(os.Stdin); ; {
|
||||
fmt.Print("> ")
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("reading line: %v", err)
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
cmd := parts[0]
|
||||
handler, exists := cmdHandlers[cmd]
|
||||
if exists {
|
||||
if err := handler(parts[1:]); err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("error: unknown command %q\n", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var policyPlaygroundCmd = &cobra.Command{
|
||||
Use: "policy-playground",
|
||||
Short: "A REPL for testing placement policies",
|
||||
Long: `A REPL for testing placement policies.
|
||||
If a wallet and endpoint is provided, the initial netmap data will be loaded from the snapshot of the node. Otherwise, an empty playground is created.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
repl, err := newPolicyPlaygroundREPL(cmd, args)
|
||||
commonCmd.ExitOnErr(cmd, "could not create policy playground: %w", err)
|
||||
commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run())
|
||||
},
|
||||
}
|
||||
|
||||
func initContainerPolicyPlaygroundCmd() {
|
||||
commonflags.Init(policyPlaygroundCmd)
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"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/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
ruleFlag = "rule"
|
||||
)
|
||||
|
||||
var addRuleCmd = &cobra.Command{
|
||||
Use: "add-rule",
|
||||
Short: "Add local override",
|
||||
Long: "Add local APE rule to a node with following format:\n<action>[:action_detail] <operation> [<condition1> ...] <resource>",
|
||||
Example: `allow Object.Get *
|
||||
deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*
|
||||
deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *
|
||||
`,
|
||||
Run: addRule,
|
||||
}
|
||||
|
||||
func prettyJSONFormat(cmd *cobra.Command, serializedChain []byte) string {
|
||||
wr := bytes.NewBufferString("")
|
||||
err := json.Indent(wr, serializedChain, "", " ")
|
||||
commonCmd.ExitOnErr(cmd, "%w", err)
|
||||
return wr.String()
|
||||
}
|
||||
|
||||
func addRule(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
chainID, _ := cmd.Flags().GetString(chainIDFlag)
|
||||
|
||||
var cnr cid.ID
|
||||
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
|
||||
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
cnr.Encode(rawCID)
|
||||
|
||||
rule, _ := cmd.Flags().GetString(ruleFlag)
|
||||
|
||||
chain := new(apechain.Chain)
|
||||
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, []string{rule}))
|
||||
chain.ID = apechain.ID(chainID)
|
||||
serializedChain := chain.Bytes()
|
||||
|
||||
cmd.Println("Container ID: " + cidStr)
|
||||
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, serializedChain))
|
||||
|
||||
req := &control.AddChainLocalOverrideRequest{
|
||||
Body: &control.AddChainLocalOverrideRequest_Body{
|
||||
ContainerId: rawCID,
|
||||
Chain: serializedChain,
|
||||
},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.AddChainLocalOverrideResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.AddChainLocalOverride(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Rule has been added. Chain id: ", resp.GetBody().GetChainId())
|
||||
}
|
||||
|
||||
func initControlAddRuleCmd() {
|
||||
initControlFlags(addRuleCmd)
|
||||
|
||||
ff := addRuleCmd.Flags()
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
ff.String(ruleFlag, "", "Rule statement")
|
||||
ff.String(chainIDFlag, "", "Assign ID to the parsed chain")
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
concurrencyFlag = "concurrency"
|
||||
removeDuplicatesFlag = "remove-duplicates"
|
||||
)
|
||||
|
||||
var doctorCmd = &cobra.Command{
|
||||
Use: "doctor",
|
||||
Short: "Restructure node's storage",
|
||||
Long: "Restructure node's storage",
|
||||
Run: doctor,
|
||||
}
|
||||
|
||||
func doctor(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
req := &control.DoctorRequest{Body: new(control.DoctorRequest_Body)}
|
||||
req.Body.Concurrency, _ = cmd.Flags().GetUint32(concurrencyFlag)
|
||||
req.Body.RemoveDuplicates, _ = cmd.Flags().GetBool(removeDuplicatesFlag)
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.DoctorResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.Doctor(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Operation has finished.")
|
||||
}
|
||||
|
||||
func initControlDoctorCmd() {
|
||||
initControlFlags(doctorCmd)
|
||||
|
||||
ff := doctorCmd.Flags()
|
||||
ff.Uint32(concurrencyFlag, 0, "Number of parallel threads to use")
|
||||
ff.Bool(removeDuplicatesFlag, false, "Remove duplicate objects")
|
||||
}
|
|
@ -1,316 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
clientSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
awaitFlag = "await"
|
||||
noProgressFlag = "no-progress"
|
||||
)
|
||||
|
||||
var evacuationShardCmd = &cobra.Command{
|
||||
Use: "evacuation",
|
||||
Short: "Objects evacuation from shard",
|
||||
Long: "Objects evacuation from shard to other shards",
|
||||
}
|
||||
|
||||
var startEvacuationShardCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start evacuate objects from shard",
|
||||
Long: "Start evacuate objects from shard to other shards",
|
||||
Run: startEvacuateShard,
|
||||
}
|
||||
|
||||
var getEvacuationShardStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Get evacuate objects from shard status",
|
||||
Long: "Get evacuate objects from shard to other shards status",
|
||||
Run: getEvacuateShardStatus,
|
||||
}
|
||||
|
||||
var stopEvacuationShardCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "Stop running evacuate process",
|
||||
Long: "Stop running evacuate process from shard to other shards",
|
||||
Run: stopEvacuateShardStatus,
|
||||
}
|
||||
|
||||
func startEvacuateShard(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag)
|
||||
|
||||
req := &control.StartShardEvacuationRequest{
|
||||
Body: &control.StartShardEvacuationRequest_Body{
|
||||
Shard_ID: getShardIDList(cmd),
|
||||
IgnoreErrors: ignoreErrors,
|
||||
},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.StartShardEvacuationResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.StartShardEvacuation(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "Start evacuate shards failed, rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Shard evacuation has been successfully started.")
|
||||
|
||||
if awaitCompletion, _ := cmd.Flags().GetBool(awaitFlag); awaitCompletion {
|
||||
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
|
||||
waitEvacuateCompletion(cmd, pk, cli, !noProgress, true)
|
||||
}
|
||||
}
|
||||
|
||||
func getEvacuateShardStatus(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
req := &control.GetShardEvacuationStatusRequest{
|
||||
Body: &control.GetShardEvacuationStatusRequest_Body{},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.GetShardEvacuationStatusResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.GetShardEvacuationStatus(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "Get evacuate shards status failed, rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
printStatus(cmd, resp)
|
||||
}
|
||||
|
||||
func stopEvacuateShardStatus(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
req := &control.StopShardEvacuationRequest{
|
||||
Body: &control.StopShardEvacuationRequest_Body{},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.StopShardEvacuationResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.StopShardEvacuation(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "Stop evacuate shards failed, rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
waitEvacuateCompletion(cmd, pk, cli, false, false)
|
||||
|
||||
cmd.Println("Evacuation stopped.")
|
||||
}
|
||||
|
||||
func waitEvacuateCompletion(cmd *cobra.Command, pk *ecdsa.PrivateKey, cli *clientSDK.Client, printProgress, printCompleted bool) {
|
||||
const statusPollingInterval = 1 * time.Second
|
||||
const reportIntervalSeconds = 5
|
||||
var resp *control.GetShardEvacuationStatusResponse
|
||||
reportResponse := new(atomic.Pointer[control.GetShardEvacuationStatusResponse])
|
||||
pollingCompleted := make(chan struct{})
|
||||
progressReportCompleted := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(progressReportCompleted)
|
||||
if !printProgress {
|
||||
return
|
||||
}
|
||||
cmd.Printf("Progress will be reported every %d seconds.\n", reportIntervalSeconds)
|
||||
for {
|
||||
select {
|
||||
case <-pollingCompleted:
|
||||
return
|
||||
case <-time.After(reportIntervalSeconds * time.Second):
|
||||
r := reportResponse.Load()
|
||||
if r == nil || r.GetBody().GetStatus() == control.GetShardEvacuationStatusResponse_Body_COMPLETED {
|
||||
continue
|
||||
}
|
||||
printStatus(cmd, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
req := &control.GetShardEvacuationStatusRequest{
|
||||
Body: &control.GetShardEvacuationStatusRequest_Body{},
|
||||
}
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.GetShardEvacuationStatus(client, req)
|
||||
return err
|
||||
})
|
||||
|
||||
reportResponse.Store(resp)
|
||||
|
||||
if err != nil {
|
||||
commonCmd.ExitOnErr(cmd, "Failed to get evacuate status, rpc error: %w", err)
|
||||
return
|
||||
}
|
||||
if resp.GetBody().GetStatus() != control.GetShardEvacuationStatusResponse_Body_RUNNING {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(statusPollingInterval)
|
||||
}
|
||||
close(pollingCompleted)
|
||||
<-progressReportCompleted
|
||||
if printCompleted {
|
||||
printCompletedStatusMessage(cmd, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func printCompletedStatusMessage(cmd *cobra.Command, resp *control.GetShardEvacuationStatusResponse) {
|
||||
cmd.Println("Shard evacuation has been completed.")
|
||||
sb := &strings.Builder{}
|
||||
appendShardIDs(sb, resp)
|
||||
appendCounts(sb, resp)
|
||||
appendError(sb, resp)
|
||||
appendStartedAt(sb, resp)
|
||||
appendDuration(sb, resp)
|
||||
cmd.Println(sb.String())
|
||||
}
|
||||
|
||||
func printStatus(cmd *cobra.Command, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetStatus() == control.GetShardEvacuationStatusResponse_Body_EVACUATE_SHARD_STATUS_UNDEFINED {
|
||||
cmd.Println("There is no running or completed evacuation.")
|
||||
return
|
||||
}
|
||||
sb := &strings.Builder{}
|
||||
appendShardIDs(sb, resp)
|
||||
appendStatus(sb, resp)
|
||||
appendCounts(sb, resp)
|
||||
appendError(sb, resp)
|
||||
appendStartedAt(sb, resp)
|
||||
appendDuration(sb, resp)
|
||||
appendEstimation(sb, resp)
|
||||
cmd.Println(sb.String())
|
||||
}
|
||||
|
||||
func appendEstimation(sb *strings.Builder, resp *control.GetShardEvacuationStatusResponse) {
|
||||
if resp.GetBody().GetStatus() != control.GetShardEvacuationStatusResponse_Body_RUNNING ||
|
||||
resp.GetBody().GetDuration() == nil ||
|
||||
resp.GetBody().GetTotal() == 0 ||
|
||||
resp.GetBody().GetEvacuated()+resp.GetBody().GetFailed()+resp.Body.GetSkipped() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
durationSeconds := float64(resp.GetBody().GetDuration().GetSeconds())
|
||||
evacuated := float64(resp.GetBody().GetEvacuated() + resp.GetBody().GetFailed() + resp.GetBody().GetSkipped())
|
||||
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 objects out of %d, failed to evacuate: %d, skipped: %d.",
|
||||
resp.GetBody().GetEvacuated(),
|
||||
resp.GetBody().GetTotal(),
|
||||
resp.GetBody().GetFailed(),
|
||||
resp.GetBody().GetSkipped()))
|
||||
}
|
||||
|
||||
func initControlEvacuationShardCmd() {
|
||||
evacuationShardCmd.AddCommand(startEvacuationShardCmd)
|
||||
evacuationShardCmd.AddCommand(getEvacuationShardStatusCmd)
|
||||
evacuationShardCmd.AddCommand(stopEvacuationShardCmd)
|
||||
|
||||
initControlStartEvacuationShardCmd()
|
||||
initControlFlags(getEvacuationShardStatusCmd)
|
||||
initControlFlags(stopEvacuationShardCmd)
|
||||
}
|
||||
|
||||
func initControlStartEvacuationShardCmd() {
|
||||
initControlFlags(startEvacuationShardCmd)
|
||||
|
||||
flags := startEvacuationShardCmd.Flags()
|
||||
flags.StringSlice(shardIDFlag, nil, "List of shard IDs in base58 encoding")
|
||||
flags.Bool(shardAllFlag, false, "Process all shards")
|
||||
flags.Bool(ignoreErrorsFlag, true, "Skip invalid/unreadable objects")
|
||||
flags.Bool(awaitFlag, false, "Block execution until evacuation is completed")
|
||||
flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag))
|
||||
|
||||
startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/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/services/control"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getRuleCmd = &cobra.Command{
|
||||
Use: "get-rule",
|
||||
Short: "Get local override",
|
||||
Long: "Get local APE override of the node",
|
||||
Run: getRule,
|
||||
}
|
||||
|
||||
func getRule(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
var cnr cid.ID
|
||||
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
|
||||
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
cnr.Encode(rawCID)
|
||||
|
||||
chainID, _ := cmd.Flags().GetString(chainIDFlag)
|
||||
|
||||
req := &control.GetChainLocalOverrideRequest{
|
||||
Body: &control.GetChainLocalOverrideRequest_Body{
|
||||
ContainerId: rawCID,
|
||||
ChainId: chainID,
|
||||
},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.GetChainLocalOverrideResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.GetChainLocalOverride(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
var chain apechain.Chain
|
||||
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(resp.GetBody().GetChain()))
|
||||
|
||||
// TODO (aarifullin): make pretty-formatted output for chains.
|
||||
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, chain.Bytes()))
|
||||
}
|
||||
|
||||
func initControGetRuleCmd() {
|
||||
initControlFlags(getRuleCmd)
|
||||
|
||||
ff := getRuleCmd.Flags()
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
ff.String(chainIDFlag, "", "Chain id")
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
healthcheckIRFlag = "ir"
|
||||
)
|
||||
|
||||
var healthCheckCmd = &cobra.Command{
|
||||
Use: "healthcheck",
|
||||
Short: "Health check for FrostFS storage nodes",
|
||||
Long: "Health check for FrostFS storage nodes.",
|
||||
Run: healthCheck,
|
||||
}
|
||||
|
||||
func initControlHealthCheckCmd() {
|
||||
initControlFlags(healthCheckCmd)
|
||||
|
||||
flags := healthCheckCmd.Flags()
|
||||
flags.Bool(healthcheckIRFlag, false, "Communicate with IR node")
|
||||
_ = flags.MarkDeprecated(healthcheckIRFlag, "for health check of inner ring nodes, use the 'control ir healthcheck' command instead.")
|
||||
}
|
||||
|
||||
func healthCheck(cmd *cobra.Command, args []string) {
|
||||
if isIR, _ := cmd.Flags().GetBool(healthcheckIRFlag); isIR {
|
||||
irHealthCheck(cmd, args)
|
||||
return
|
||||
}
|
||||
|
||||
pk := key.Get(cmd)
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
req := new(control.HealthCheckRequest)
|
||||
req.SetBody(new(control.HealthCheckRequest_Body))
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
var resp *control.HealthCheckResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = control.HealthCheck(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Printf("Network status: %s\n", resp.GetBody().GetNetmapStatus())
|
||||
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var irCmd = &cobra.Command{
|
||||
Use: "ir",
|
||||
Short: "Operations with inner ring nodes",
|
||||
Long: "Operations with inner ring nodes",
|
||||
}
|
||||
|
||||
func initControlIRCmd() {
|
||||
irCmd.AddCommand(tickEpochCmd)
|
||||
irCmd.AddCommand(removeNodeCmd)
|
||||
irCmd.AddCommand(irHealthCheckCmd)
|
||||
irCmd.AddCommand(removeContainerCmd)
|
||||
|
||||
initControlIRTickEpochCmd()
|
||||
initControlIRRemoveNodeCmd()
|
||||
initControlIRHealthCheckCmd()
|
||||
initControlIRRemoveContainerCmd()
|
||||
}
|
||||
|
||||
func printVUB(cmd *cobra.Command, vub uint32) {
|
||||
cmd.Printf("Transaction's valid until block is %d\n", vub)
|
||||
}
|
||||
|
||||
func parseVUB(cmd *cobra.Command) uint32 {
|
||||
vub, err := cmd.Flags().GetUint32(irFlagNameVUB)
|
||||
commonCmd.ExitOnErr(cmd, "invalid valid until block value: %w", err)
|
||||
return vub
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var irHealthCheckCmd = &cobra.Command{
|
||||
Use: "healthcheck",
|
||||
Short: "Health check for FrostFS inner ring nodes",
|
||||
Long: "Health check for FrostFS inner ring nodes.",
|
||||
Run: irHealthCheck,
|
||||
}
|
||||
|
||||
func initControlIRHealthCheckCmd() {
|
||||
initControlFlags(irHealthCheckCmd)
|
||||
}
|
||||
|
||||
func irHealthCheck(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
req := new(ircontrol.HealthCheckRequest)
|
||||
|
||||
req.SetBody(new(ircontrol.HealthCheckRequest_Body))
|
||||
|
||||
err := ircontrolsrv.SignMessage(pk, req)
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
|
||||
|
||||
var resp *ircontrol.HealthCheckResponse
|
||||
err = cli.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = ircontrol.HealthCheck(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus())
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/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"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
ownerFlag = "owner"
|
||||
)
|
||||
|
||||
var removeContainerCmd = &cobra.Command{
|
||||
Use: "remove-container",
|
||||
Short: "Schedules a container removal",
|
||||
Long: `Schedules a container removal via a notary request.
|
||||
Container data will be deleted asynchronously by policer.
|
||||
To check removal status "frostfs-cli container list" command can be used.`,
|
||||
Run: removeContainer,
|
||||
}
|
||||
|
||||
func initControlIRRemoveContainerCmd() {
|
||||
initControlIRFlags(removeContainerCmd)
|
||||
|
||||
flags := removeContainerCmd.Flags()
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
flags.String(ownerFlag, "", "Container owner's wallet address.")
|
||||
removeContainerCmd.MarkFlagsMutuallyExclusive(commonflags.CIDFlag, ownerFlag)
|
||||
removeContainerCmd.MarkFlagsOneRequired(commonflags.CIDFlag, ownerFlag)
|
||||
}
|
||||
|
||||
func removeContainer(cmd *cobra.Command, _ []string) {
|
||||
req := prepareRemoveContainerRequest(cmd)
|
||||
|
||||
pk := key.Get(cmd)
|
||||
c := getClient(cmd, pk)
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", ircontrolsrv.SignMessage(pk, req))
|
||||
|
||||
var resp *ircontrol.RemoveContainerResponse
|
||||
err := c.ExecRaw(func(client *rawclient.Client) error {
|
||||
var err error
|
||||
resp, err = ircontrol.RemoveContainer(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "failed to execute request: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
if len(req.GetBody().GetContainerId()) > 0 {
|
||||
cmd.Println("Container scheduled to removal")
|
||||
} else {
|
||||
cmd.Println("User containers sheduled to removal")
|
||||
}
|
||||
printVUB(cmd, resp.GetBody().GetVub())
|
||||
}
|
||||
|
||||
func prepareRemoveContainerRequest(cmd *cobra.Command) *ircontrol.RemoveContainerRequest {
|
||||
req := &ircontrol.RemoveContainerRequest{
|
||||
Body: &ircontrol.RemoveContainerRequest_Body{},
|
||||
}
|
||||
|
||||
cidStr, err := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get cid: ", err)
|
||||
|
||||
ownerStr, err := cmd.Flags().GetString(ownerFlag)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get owner: ", err)
|
||||
|
||||
if len(ownerStr) > 0 {
|
||||
var owner user.ID
|
||||
commonCmd.ExitOnErr(cmd, "invalid owner ID: %w", owner.DecodeString(ownerStr))
|
||||
var ownerID refs.OwnerID
|
||||
owner.WriteToV2(&ownerID)
|
||||
req.Body.Owner = ownerID.StableMarshal(nil)
|
||||
}
|
||||
|
||||
if len(cidStr) > 0 {
|
||||
var containerID cid.ID
|
||||
commonCmd.ExitOnErr(cmd, "invalid container ID: %w", containerID.DecodeString(cidStr))
|
||||
req.Body.ContainerId = containerID[:]
|
||||
}
|
||||
|
||||
req.Body.Vub = parseVUB(cmd)
|
||||
|
||||
return req
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var removeNodeCmd = &cobra.Command{
|
||||
Use: "remove-node",
|
||||
Short: "Forces a node removal from netmap",
|
||||
Long: "Forces a node removal from netmap via a notary request. It should be executed on other IR nodes as well.",
|
||||
Run: removeNode,
|
||||
}
|
||||
|
||||
func initControlIRRemoveNodeCmd() {
|
||||
initControlIRFlags(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,
|
||||
Vub: parseVUB(cmd),
|
||||
})
|
||||
|
||||
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")
|
||||
printVUB(cmd, resp.GetBody().GetVub())
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||
ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var tickEpochCmd = &cobra.Command{
|
||||
Use: "tick-epoch",
|
||||
Short: "Forces a new epoch",
|
||||
Long: "Forces a new epoch via a notary request. It should be executed on other IR nodes as well.",
|
||||
Run: tickEpoch,
|
||||
}
|
||||
|
||||
func initControlIRTickEpochCmd() {
|
||||
initControlIRFlags(tickEpochCmd)
|
||||
}
|
||||
|
||||
func tickEpoch(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
c := getClient(cmd, pk)
|
||||
|
||||
req := new(ircontrol.TickEpochRequest)
|
||||
req.SetBody(&ircontrol.TickEpochRequest_Body{
|
||||
Vub: parseVUB(cmd),
|
||||
})
|
||||
|
||||
err := ircontrolsrv.SignMessage(pk, req)
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
|
||||
|
||||
var resp *ircontrol.TickEpochResponse
|
||||
err = c.ExecRaw(func(client *rawclient.Client) error {
|
||||
resp, err = ircontrol.TickEpoch(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
cmd.Println("Epoch tick requested")
|
||||
printVUB(cmd, resp.GetBody().GetVub())
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/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/services/control"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var listRulesCmd = &cobra.Command{
|
||||
Use: "list-rules",
|
||||
Short: "List local overrides",
|
||||
Long: "List local APE overrides of the node",
|
||||
Run: listRules,
|
||||
}
|
||||
|
||||
func listRules(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
var cnr cid.ID
|
||||
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
|
||||
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
cnr.Encode(rawCID)
|
||||
|
||||
req := &control.ListChainLocalOverridesRequest{
|
||||
Body: &control.ListChainLocalOverridesRequest_Body{
|
||||
ContainerId: rawCID,
|
||||
},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.ListChainLocalOverridesResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.ListChainLocalOverrides(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
chains := resp.GetBody().GetChains()
|
||||
if len(chains) == 0 {
|
||||
cmd.Println("Local overrides are not defined for the container.")
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range chains {
|
||||
// TODO (aarifullin): make pretty-formatted output for chains.
|
||||
var chain apechain.Chain
|
||||
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(c))
|
||||
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, chain.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
func initControlListRulesCmd() {
|
||||
initControlFlags(listRulesCmd)
|
||||
|
||||
ff := listRulesCmd.Flags()
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/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/services/control"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
chainIDFlag = "chain-id"
|
||||
)
|
||||
|
||||
var removeRuleCmd = &cobra.Command{
|
||||
Use: "remove-rule",
|
||||
Short: "Remove local override",
|
||||
Long: "Remove local APE override of the node",
|
||||
Run: removeRule,
|
||||
}
|
||||
|
||||
func removeRule(cmd *cobra.Command, _ []string) {
|
||||
pk := key.Get(cmd)
|
||||
|
||||
var cnr cid.ID
|
||||
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
|
||||
|
||||
rawCID := make([]byte, sha256.Size)
|
||||
cnr.Encode(rawCID)
|
||||
|
||||
chainID, _ := cmd.Flags().GetString(chainIDFlag)
|
||||
|
||||
req := &control.RemoveChainLocalOverrideRequest{
|
||||
Body: &control.RemoveChainLocalOverrideRequest_Body{
|
||||
ContainerId: rawCID,
|
||||
ChainId: chainID,
|
||||
},
|
||||
}
|
||||
|
||||
signRequest(cmd, pk, req)
|
||||
|
||||
cli := getClient(cmd, pk)
|
||||
|
||||
var resp *control.RemoveChainLocalOverrideResponse
|
||||
var err error
|
||||
err = cli.ExecRaw(func(client *client.Client) error {
|
||||
resp, err = control.RemoveChainLocalOverride(client, req)
|
||||
return err
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||
|
||||
if resp.GetBody().Removed {
|
||||
cmd.Println("Rule has been removed.")
|
||||
} else {
|
||||
cmd.Println("Rule has not been removed.")
|
||||
}
|
||||
}
|
||||
|
||||
func initControlRemoveRuleCmd() {
|
||||
initControlFlags(removeRuleCmd)
|
||||
|
||||
ff := removeRuleCmd.Flags()
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
ff.String(chainIDFlag, "", "Chain id")
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package control
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
controlSvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/server"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
irFlagNameVUB = "vub"
|
||||
)
|
||||
|
||||
func initControlFlags(cmd *cobra.Command) {
|
||||
ff := cmd.Flags()
|
||||
ff.StringP(commonflags.WalletPath, commonflags.WalletPathShorthand, commonflags.WalletPathDefault, commonflags.WalletPathUsage)
|
||||
ff.StringP(commonflags.Account, commonflags.AccountShorthand, commonflags.AccountDefault, commonflags.AccountUsage)
|
||||
ff.String(controlRPC, controlRPCDefault, controlRPCUsage)
|
||||
ff.DurationP(commonflags.Timeout, commonflags.TimeoutShorthand, commonflags.TimeoutDefault, commonflags.TimeoutUsage)
|
||||
}
|
||||
|
||||
func initControlIRFlags(cmd *cobra.Command) {
|
||||
initControlFlags(cmd)
|
||||
|
||||
ff := cmd.Flags()
|
||||
ff.Uint32(irFlagNameVUB, 0, "Valid until block value for notary transaction")
|
||||
}
|
||||
|
||||
func signRequest(cmd *cobra.Command, pk *ecdsa.PrivateKey, req controlSvc.SignedMessage) {
|
||||
err := controlSvc.SignMessage(pk, req)
|
||||
commonCmd.ExitOnErr(cmd, "could not sign request: %w", err)
|
||||
}
|
||||
|
||||
func verifyResponse(cmd *cobra.Command,
|
||||
sigControl interface {
|
||||
GetKey() []byte
|
||||
GetSign() []byte
|
||||
},
|
||||
body interface {
|
||||
StableMarshal([]byte) []byte
|
||||
},
|
||||
) {
|
||||
if sigControl == nil {
|
||||
commonCmd.ExitOnErr(cmd, "", errors.New("missing response signature"))
|
||||
}
|
||||
|
||||
// TODO(@cthulhu-rider): #468 use Signature message from FrostFS API to avoid conversion
|
||||
var sigV2 refs.Signature
|
||||
sigV2.SetScheme(refs.ECDSA_SHA512)
|
||||
sigV2.SetKey(sigControl.GetKey())
|
||||
sigV2.SetSign(sigControl.GetSign())
|
||||
|
||||
var sig frostfscrypto.Signature
|
||||
commonCmd.ExitOnErr(cmd, "can't read signature: %w", sig.ReadFromV2(sigV2))
|
||||
|
||||
if !sig.Verify(body.StableMarshal(nil)) {
|
||||
commonCmd.ExitOnErr(cmd, "", errors.New("invalid response signature"))
|
||||
}
|
||||
}
|
||||
|
||||
func getClient(cmd *cobra.Command, pk *ecdsa.PrivateKey) *client.Client {
|
||||
return internalclient.GetSDKClientByFlag(cmd, pk, controlRPC)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getEpochCmd = &cobra.Command{
|
||||
Use: "epoch",
|
||||
Short: "Get current epoch number",
|
||||
Long: "Get current epoch number",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
p := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, p, commonflags.RPC)
|
||||
|
||||
prm := internalclient.NetworkInfoPrm{
|
||||
Client: cli,
|
||||
}
|
||||
|
||||
res, err := internalclient.NetworkInfo(cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
netInfo := res.NetworkInfo()
|
||||
|
||||
cmd.Println(netInfo.CurrentEpoch())
|
||||
},
|
||||
}
|
||||
|
||||
func initGetEpochCmd() {
|
||||
commonflags.Init(getEpochCmd)
|
||||
commonflags.InitAPI(getEpochCmd)
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var objectDelCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Aliases: []string{"del"},
|
||||
Short: "Delete object from FrostFS",
|
||||
Long: "Delete object from FrostFS",
|
||||
Run: deleteObject,
|
||||
}
|
||||
|
||||
func initObjectDeleteCmd() {
|
||||
commonflags.Init(objectDelCmd)
|
||||
initFlagSession(objectDelCmd, "DELETE")
|
||||
|
||||
flags := objectDelCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
flags.Bool(binaryFlag, false, "Deserialize object structure from given file.")
|
||||
flags.String(fileFlag, "", "File with object payload")
|
||||
}
|
||||
|
||||
func deleteObject(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
var objAddr oid.Address
|
||||
|
||||
binary, _ := cmd.Flags().GetBool(binaryFlag)
|
||||
if binary {
|
||||
filename, _ := cmd.Flags().GetString(fileFlag)
|
||||
if filename == "" {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", fileFlag))
|
||||
}
|
||||
objAddr = readObjectAddressBin(cmd, &cnr, &obj, filename)
|
||||
} else {
|
||||
cidVal, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
if cidVal == "" {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.CIDFlag))
|
||||
}
|
||||
|
||||
oidVal, _ := cmd.Flags().GetString(commonflags.OIDFlag)
|
||||
if oidVal == "" {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.OIDFlag))
|
||||
}
|
||||
|
||||
objAddr = readObjectAddress(cmd, &cnr, &obj)
|
||||
}
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
var prm internalclient.DeleteObjectPrm
|
||||
ReadOrOpenSession(cmd, &prm, pk, cnr, &obj)
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetAddress(objAddr)
|
||||
|
||||
res, err := internalclient.DeleteObject(cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
tomb := res.Tombstone()
|
||||
|
||||
cmd.Println("Object removed successfully.")
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", tomb, cnr)
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var objectGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get object from FrostFS",
|
||||
Long: "Get object from FrostFS",
|
||||
Run: getObject,
|
||||
}
|
||||
|
||||
func initObjectGetCmd() {
|
||||
commonflags.Init(objectGetCmd)
|
||||
initFlagSession(objectGetCmd, "GET")
|
||||
|
||||
flags := objectGetCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectGetCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectGetCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String(fileFlag, "", "File to write object payload to(with -b together with signature and header). Default: stdout.")
|
||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||
flags.Bool(noProgressFlag, false, "Do not show progress bar")
|
||||
flags.Bool(binaryFlag, false, "Serialize whole object structure into given file(id + signature + header + payload).")
|
||||
}
|
||||
|
||||
func getObject(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
filename := cmd.Flag(fileFlag).Value.String()
|
||||
out, closer := createOutWriter(cmd, filename)
|
||||
defer closer()
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.GetObjectPrm
|
||||
prm.SetClient(cli)
|
||||
Prepare(cmd, &prm)
|
||||
readSession(cmd, &prm, pk, cnr, obj)
|
||||
|
||||
raw, _ := cmd.Flags().GetBool(rawFlag)
|
||||
prm.SetRawFlag(raw)
|
||||
prm.SetAddress(objAddr)
|
||||
|
||||
var p *pb.ProgressBar
|
||||
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
|
||||
|
||||
var payloadWriter io.Writer
|
||||
var payloadBuffer *bytes.Buffer
|
||||
binary, _ := cmd.Flags().GetBool(binaryFlag)
|
||||
if binary {
|
||||
payloadBuffer = new(bytes.Buffer)
|
||||
payloadWriter = payloadBuffer
|
||||
} else {
|
||||
payloadWriter = out
|
||||
}
|
||||
|
||||
if filename == "" || noProgress {
|
||||
prm.SetPayloadWriter(payloadWriter)
|
||||
} else {
|
||||
p = pb.New64(0)
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadWriter(p.NewProxyWriter(payloadWriter))
|
||||
prm.SetHeaderCallback(func(o *objectSDK.Object) {
|
||||
p.SetTotal64(int64(o.PayloadSize()))
|
||||
p.Start()
|
||||
})
|
||||
}
|
||||
|
||||
res, err := internalclient.GetObject(cmd.Context(), prm)
|
||||
if p != nil {
|
||||
p.Finish()
|
||||
}
|
||||
if err != nil {
|
||||
if ok := printSplitInfoErr(cmd, err); ok {
|
||||
return
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
}
|
||||
|
||||
processResult(cmd, res, binary, payloadBuffer, out, filename)
|
||||
}
|
||||
|
||||
func processResult(cmd *cobra.Command, res *internalclient.GetObjectRes, binary bool, payloadBuffer *bytes.Buffer, out io.Writer, filename string) {
|
||||
if binary {
|
||||
objToStore := res.Header()
|
||||
// TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload
|
||||
objToStore.SetPayload(payloadBuffer.Bytes())
|
||||
objBytes, err := objToStore.Marshal()
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
_, err = out.Write(objBytes)
|
||||
commonCmd.ExitOnErr(cmd, "unable to write binary object in out: %w ", err)
|
||||
}
|
||||
|
||||
if filename != "" && !strictOutput(cmd) {
|
||||
cmd.Printf("[%s] Object successfully saved\n", filename)
|
||||
}
|
||||
|
||||
// Print header only if file is not streamed to stdout.
|
||||
if filename != "" {
|
||||
err := printHeader(cmd, res.Header())
|
||||
commonCmd.ExitOnErr(cmd, "", err)
|
||||
}
|
||||
}
|
||||
|
||||
func createOutWriter(cmd *cobra.Command, filename string) (out io.Writer, closer func()) {
|
||||
if filename == "" {
|
||||
out = os.Stdout
|
||||
closer = func() {}
|
||||
} else {
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
||||
}
|
||||
|
||||
out = f
|
||||
closer = func() {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func strictOutput(cmd *cobra.Command) bool {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
return toJSON || toProto
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// object lock command.
|
||||
var objectLockCmd = &cobra.Command{
|
||||
Use: "lock",
|
||||
Short: "Lock object in container",
|
||||
Long: "Lock object in container",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
cidRaw, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
|
||||
var cnr cid.ID
|
||||
err := cnr.DecodeString(cidRaw)
|
||||
commonCmd.ExitOnErr(cmd, "Incorrect container arg: %v", err)
|
||||
|
||||
key := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC)
|
||||
|
||||
oidsRaw, _ := cmd.Flags().GetStringSlice(commonflags.OIDFlag)
|
||||
|
||||
lockList := make([]oid.ID, 0, len(oidsRaw))
|
||||
oidM := make(map[oid.ID]struct{})
|
||||
|
||||
for i, oidRaw := range oidsRaw {
|
||||
var oID oid.ID
|
||||
err = oID.DecodeString(oidRaw)
|
||||
commonCmd.ExitOnErr(cmd, fmt.Sprintf("Incorrect object arg #%d: %%v", i+1), err)
|
||||
|
||||
if _, ok := oidM[oID]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
lockList = append(lockList, oID)
|
||||
oidM[oID] = struct{}{}
|
||||
|
||||
for _, relative := range collectObjectRelatives(cmd, cli, cnr, oID) {
|
||||
if _, ok := oidM[relative]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
lockList = append(lockList, relative)
|
||||
oidM[relative] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var idOwner user.ID
|
||||
user.IDFromKey(&idOwner, key.PublicKey)
|
||||
|
||||
var lock objectSDK.Lock
|
||||
lock.WriteMembers(lockList)
|
||||
|
||||
exp, _ := cmd.Flags().GetUint64(commonflags.ExpireAt)
|
||||
lifetime, _ := cmd.Flags().GetUint64(commonflags.Lifetime)
|
||||
if exp == 0 && lifetime == 0 { // mutual exclusion is ensured by cobra
|
||||
commonCmd.ExitOnErr(cmd, "", errors.New("either expiration epoch of a lifetime is required"))
|
||||
}
|
||||
|
||||
if lifetime != 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
|
||||
|
||||
currEpoch, err := internalclient.GetCurrentEpoch(ctx, cmd, endpoint)
|
||||
commonCmd.ExitOnErr(cmd, "Request current epoch: %w", err)
|
||||
|
||||
exp = currEpoch + lifetime
|
||||
}
|
||||
|
||||
common.PrintVerbose(cmd, "Lock object will expire after %d epoch", exp)
|
||||
|
||||
var expirationAttr objectSDK.Attribute
|
||||
expirationAttr.SetKey(objectV2.SysAttributeExpEpoch)
|
||||
expirationAttr.SetValue(strconv.FormatUint(exp, 10))
|
||||
|
||||
obj := objectSDK.New()
|
||||
obj.SetContainerID(cnr)
|
||||
obj.SetOwnerID(idOwner)
|
||||
obj.SetType(objectSDK.TypeLock)
|
||||
obj.SetAttributes(expirationAttr)
|
||||
obj.SetPayload(lock.Marshal())
|
||||
|
||||
var prm internalclient.PutObjectPrm
|
||||
ReadOrOpenSessionViaClient(cmd, &prm, cli, key, cnr, nil)
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
||||
res, err := internalclient.PutObject(cmd.Context(), prm)
|
||||
commonCmd.ExitOnErr(cmd, "Store lock object in FrostFS: %w", err)
|
||||
|
||||
cmd.Printf("Lock object ID: %s\n", res.ID())
|
||||
cmd.Println("Objects successfully locked.")
|
||||
},
|
||||
}
|
||||
|
||||
func initCommandObjectLock() {
|
||||
commonflags.Init(objectLockCmd)
|
||||
initFlagSession(objectLockCmd, "PUT")
|
||||
|
||||
ff := objectLockCmd.Flags()
|
||||
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectLockCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
ff.StringSlice(commonflags.OIDFlag, nil, commonflags.OIDFlagUsage)
|
||||
_ = objectLockCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
ff.Uint64P(commonflags.ExpireAt, "e", 0, "The last active epoch for the lock")
|
||||
|
||||
ff.Uint64(commonflags.Lifetime, 0, "Lock lifetime")
|
||||
objectLockCmd.MarkFlagsMutuallyExclusive(commonflags.ExpireAt, commonflags.Lifetime)
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object_manager/placement"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
verifyPresenceAllFlag = "verify-presence-all"
|
||||
)
|
||||
|
||||
type objectNodesInfo struct {
|
||||
containerID cid.ID
|
||||
objectID oid.ID
|
||||
relatedObjectIDs []oid.ID
|
||||
isLockOrTombstone 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,
|
||||
isLockOrTombstone: res.Header().Type() == objectSDK.TypeLock || res.Header().Type() == objectSDK.TypeTombstone,
|
||||
}
|
||||
}
|
||||
|
||||
var errSplitInfo *objectSDK.SplitInfoError
|
||||
|
||||
if !errors.As(err, &errSplitInfo) {
|
||||
commonCmd.ExitOnErr(cmd, "failed to get object info: %w", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
splitInfo := errSplitInfo.SplitInfo()
|
||||
|
||||
if members, ok := tryGetSplitMembersByLinkingObject(cmd, splitInfo, prmHead, cnrID); ok {
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
relatedObjectIDs: members,
|
||||
}
|
||||
}
|
||||
|
||||
if members, ok := tryGetSplitMembersBySplitID(cmd, splitInfo, cli, cnrID); ok {
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
relatedObjectIDs: members,
|
||||
}
|
||||
}
|
||||
|
||||
members := tryRestoreChainInReverse(cmd, splitInfo, prmHead, cli, cnrID, objID)
|
||||
return &objectNodesInfo{
|
||||
containerID: cnrID,
|
||||
objectID: objID,
|
||||
relatedObjectIDs: members,
|
||||
}
|
||||
}
|
||||
|
||||
func getPlacementPolicyAndNetmap(cmd *cobra.Command, cnrID cid.ID, cli *client.Client) (placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) {
|
||||
eg, egCtx := errgroup.WithContext(cmd.Context())
|
||||
eg.Go(func() (e error) {
|
||||
placementPolicy, e = getPlacementPolicy(egCtx, cnrID, cli)
|
||||
return
|
||||
})
|
||||
eg.Go(func() (e error) {
|
||||
netmap, e = getNetMap(egCtx, cli)
|
||||
return
|
||||
})
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", eg.Wait())
|
||||
return
|
||||
}
|
||||
|
||||
func getPlacementPolicy(ctx context.Context, cnrID cid.ID, cli *client.Client) (netmapSDK.PlacementPolicy, error) {
|
||||
prm := internalclient.GetContainerPrm{
|
||||
Client: cli,
|
||||
ClientParams: client.PrmContainerGet{
|
||||
ContainerID: &cnrID,
|
||||
},
|
||||
}
|
||||
|
||||
res, err := internalclient.GetContainer(ctx, prm)
|
||||
if err != nil {
|
||||
return netmapSDK.PlacementPolicy{}, err
|
||||
}
|
||||
|
||||
return res.Container().PlacementPolicy(), nil
|
||||
}
|
||||
|
||||
func getNetMap(ctx context.Context, cli *client.Client) (*netmapSDK.NetMap, error) {
|
||||
var prm internalclient.NetMapSnapshotPrm
|
||||
prm.SetClient(cli)
|
||||
|
||||
res, err := internalclient.NetMapSnapshot(ctx, prm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nm := res.NetMap()
|
||||
return &nm, nil
|
||||
}
|
||||
|
||||
func getRequiredPlacement(cmd *cobra.Command, objInfo *objectNodesInfo, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) map[uint64]netmapSDK.NodeInfo {
|
||||
nodes := make(map[uint64]netmapSDK.NodeInfo)
|
||||
placementBuilder := placement.NewNetworkMapBuilder(netmap)
|
||||
placement, err := placementBuilder.BuildPlacement(objInfo.containerID, &objInfo.objectID, placementPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get required placement: %w", err)
|
||||
for repIdx, rep := range placement {
|
||||
numOfReplicas := placementPolicy.ReplicaNumberByIndex(repIdx)
|
||||
var nodeIdx uint32
|
||||
for _, n := range rep {
|
||||
if !objInfo.isLockOrTombstone && nodeIdx == numOfReplicas { // lock and tombstone objects should be on all container nodes
|
||||
break
|
||||
}
|
||||
nodes[n.Hash()] = n
|
||||
nodeIdx++
|
||||
}
|
||||
}
|
||||
|
||||
for _, relatedObjID := range objInfo.relatedObjectIDs {
|
||||
placement, err = placementBuilder.BuildPlacement(objInfo.containerID, &relatedObjID, placementPolicy)
|
||||
commonCmd.ExitOnErr(cmd, "failed to get required placement for related object: %w", err)
|
||||
for _, rep := range placement {
|
||||
for _, n := range rep {
|
||||
nodes[n.Hash()] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo,
|
||||
pk *ecdsa.PrivateKey, objInfo *objectNodesInfo,
|
||||
) map[uint64]boolError {
|
||||
result := make(map[uint64]boolError)
|
||||
resultMtx := &sync.Mutex{}
|
||||
|
||||
var candidates []netmapSDK.NodeInfo
|
||||
checkAllNodes, _ := cmd.Flags().GetBool(verifyPresenceAllFlag)
|
||||
if checkAllNodes {
|
||||
candidates = netmap.Nodes()
|
||||
} else {
|
||||
for _, n := range requiredPlacement {
|
||||
candidates = append(candidates, n)
|
||||
}
|
||||
}
|
||||
|
||||
eg, egCtx := errgroup.WithContext(cmd.Context())
|
||||
for _, cand := range candidates {
|
||||
cand := cand
|
||||
|
||||
eg.Go(func() error {
|
||||
cli, err := createClient(egCtx, cmd, cand, pk)
|
||||
if err != nil {
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
result[cand.Hash()] = boolError{err: err}
|
||||
return nil
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
var v boolError
|
||||
v.value, v.err = isObjectStoredOnNode(egCtx, cmd, objInfo.containerID, objInfo.objectID, cli, pk)
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) {
|
||||
return nil
|
||||
}
|
||||
result[cand.Hash()] = v
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, rObjID := range objInfo.relatedObjectIDs {
|
||||
rObjID := rObjID
|
||||
eg.Go(func() error {
|
||||
var v boolError
|
||||
v.value, v.err = isObjectStoredOnNode(egCtx, cmd, objInfo.containerID, rObjID, cli, pk)
|
||||
resultMtx.Lock()
|
||||
defer resultMtx.Unlock()
|
||||
if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) {
|
||||
return nil
|
||||
}
|
||||
result[cand.Hash()] = v
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
commonCmd.ExitOnErr(cmd, "failed to get actual placement: %w", eg.Wait())
|
||||
return result
|
||||
}
|
||||
|
||||
func createClient(ctx context.Context, cmd *cobra.Command, candidate netmapSDK.NodeInfo, pk *ecdsa.PrivateKey) (*client.Client, error) {
|
||||
var cli *client.Client
|
||||
var addresses []string
|
||||
candidate.IterateNetworkEndpoints(func(s string) bool {
|
||||
addresses = append(addresses, s)
|
||||
return false
|
||||
})
|
||||
addresses = append(addresses, candidate.ExternalAddresses()...)
|
||||
var lastErr error
|
||||
for _, address := range addresses {
|
||||
var networkAddr network.Address
|
||||
lastErr = networkAddr.FromString(address)
|
||||
if lastErr != nil {
|
||||
continue
|
||||
}
|
||||
cli, lastErr = internalclient.GetSDKClient(ctx, cmd, pk, networkAddr)
|
||||
if lastErr == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
if cli == nil {
|
||||
return nil, fmt.Errorf("failed to create client: no available endpoint")
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) (bool, error) {
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnrID)
|
||||
addrObj.SetObject(objID)
|
||||
|
||||
var prmHead internalclient.HeadObjectPrm
|
||||
prmHead.SetClient(cli)
|
||||
prmHead.SetAddress(addrObj)
|
||||
|
||||
Prepare(cmd, &prmHead)
|
||||
prmHead.SetTTL(1)
|
||||
readSession(cmd, &prmHead, pk, cnrID, objID)
|
||||
|
||||
res, err := internalclient.HeadObject(ctx, prmHead)
|
||||
if err == nil && res != nil {
|
||||
return true, nil
|
||||
}
|
||||
var notFound *apistatus.ObjectNotFound
|
||||
var removed *apistatus.ObjectAlreadyRemoved
|
||||
if errors.As(err, ¬Found) || errors.As(err, &removed) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func printPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo, actualPlacement map[uint64]boolError) {
|
||||
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 1, ' ', tabwriter.AlignRight|tabwriter.Debug)
|
||||
defer func() {
|
||||
commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", w.Flush())
|
||||
}()
|
||||
fmt.Fprintln(w, "Node ID\tShould contain object\tActually contains object\t")
|
||||
for _, n := range netmap.Nodes() {
|
||||
nodeID := hex.EncodeToString(n.PublicKey())
|
||||
_, required := requiredPlacement[n.Hash()]
|
||||
actual, actualExists := actualPlacement[n.Hash()]
|
||||
actualStr := ""
|
||||
if actualExists {
|
||||
if actual.err != nil {
|
||||
actualStr = fmt.Sprintf("error: %v", actual.err)
|
||||
} else {
|
||||
actualStr = strconv.FormatBool(actual.value)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t\n", nodeID, strconv.FormatBool(required), actualStr)
|
||||
}
|
||||
}
|
|
@ -1,288 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
noProgressFlag = "no-progress"
|
||||
notificationFlag = "notify"
|
||||
copiesNumberFlag = "copies-number"
|
||||
prepareLocallyFlag = "prepare-locally"
|
||||
)
|
||||
|
||||
var putExpiredOn uint64
|
||||
|
||||
var objectPutCmd = &cobra.Command{
|
||||
Use: "put",
|
||||
Short: "Put object to FrostFS",
|
||||
Long: "Put object to FrostFS",
|
||||
Run: putObject,
|
||||
}
|
||||
|
||||
func initObjectPutCmd() {
|
||||
commonflags.Init(objectPutCmd)
|
||||
initFlagSession(objectPutCmd, "PUT")
|
||||
|
||||
flags := objectPutCmd.Flags()
|
||||
|
||||
flags.String(fileFlag, "", "File with object payload")
|
||||
_ = objectPutCmd.MarkFlagFilename(fileFlag)
|
||||
_ = objectPutCmd.MarkFlagRequired(fileFlag)
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
|
||||
flags.String("attributes", "", "User attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.Bool("disable-filename", false, "Do not set well-known filename attribute")
|
||||
flags.Bool("disable-timestamp", false, "Do not set well-known timestamp attribute")
|
||||
flags.Uint64VarP(&putExpiredOn, commonflags.ExpireAt, "e", 0, "The last active epoch in the life of the object")
|
||||
flags.Bool(noProgressFlag, false, "Do not show progress bar")
|
||||
flags.Bool(prepareLocallyFlag, false, "Generate object header on the client side (for big object - split locally too)")
|
||||
|
||||
flags.String(notificationFlag, "", "Object notification in the form of *epoch*:*topic*; '-' topic means using default")
|
||||
flags.Bool(binaryFlag, false, "Deserialize object structure from given file.")
|
||||
|
||||
flags.String(copiesNumberFlag, "", "Number of copies of the object to store within the RPC call")
|
||||
}
|
||||
|
||||
func putObject(cmd *cobra.Command, _ []string) {
|
||||
binary, _ := cmd.Flags().GetBool(binaryFlag)
|
||||
cidVal, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
|
||||
if !binary && cidVal == "" {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.CIDFlag))
|
||||
}
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
var ownerID user.ID
|
||||
var cnr cid.ID
|
||||
|
||||
filename, _ := cmd.Flags().GetString(fileFlag)
|
||||
f, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
||||
}
|
||||
var payloadReader io.Reader = f
|
||||
obj := objectSDK.New()
|
||||
|
||||
if binary {
|
||||
payloadReader, cnr, ownerID = readFilePayload(filename, cmd)
|
||||
} else {
|
||||
readCID(cmd, &cnr)
|
||||
user.IDFromKey(&ownerID, pk.PublicKey)
|
||||
}
|
||||
|
||||
attrs := getAllObjectAttributes(cmd)
|
||||
|
||||
obj.SetContainerID(cnr)
|
||||
obj.SetOwnerID(ownerID)
|
||||
obj.SetAttributes(attrs...)
|
||||
|
||||
notificationInfo, err := parseObjectNotifications(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse object notification information: %w", err)
|
||||
|
||||
if notificationInfo != nil {
|
||||
obj.SetNotification(*notificationInfo)
|
||||
}
|
||||
|
||||
var prm internalclient.PutObjectPrm
|
||||
if prepareLocally, _ := cmd.Flags().GetBool(prepareLocallyFlag); prepareLocally {
|
||||
prm.SetClient(internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC))
|
||||
prm.PrepareLocally()
|
||||
} else {
|
||||
ReadOrOpenSession(cmd, &prm, pk, cnr, nil)
|
||||
}
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
||||
var p *pb.ProgressBar
|
||||
|
||||
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
|
||||
if noProgress {
|
||||
prm.SetPayloadReader(payloadReader)
|
||||
} else {
|
||||
if binary {
|
||||
p = setBinaryPayloadReader(cmd, obj, &prm, payloadReader)
|
||||
} else {
|
||||
p = setFilePayloadReader(cmd, f, &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 {
|
||||
p.Finish()
|
||||
}
|
||||
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
cmd.Printf("[%s] Object successfully stored\n", filename)
|
||||
cmd.Printf(" OID: %s\n CID: %s\n", res.ID(), cnr)
|
||||
}
|
||||
|
||||
func parseCopyNumber(cmd *cobra.Command, copyNum string) []uint32 {
|
||||
var cn []uint32
|
||||
if len(copyNum) > 0 {
|
||||
for _, num := range strings.Split(copyNum, ",") {
|
||||
val, err := strconv.ParseUint(num, 10, 32)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse object copies numbers information: %w", err)
|
||||
cn = append(cn, uint32(val))
|
||||
}
|
||||
}
|
||||
return cn
|
||||
}
|
||||
|
||||
func readFilePayload(filename string, cmd *cobra.Command) (io.Reader, cid.ID, user.ID) {
|
||||
buf, err := os.ReadFile(filename)
|
||||
commonCmd.ExitOnErr(cmd, "unable to read given file: %w", err)
|
||||
objTemp := objectSDK.New()
|
||||
// TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload
|
||||
commonCmd.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
|
||||
payloadReader := bytes.NewReader(objTemp.Payload())
|
||||
cnr, _ := objTemp.ContainerID()
|
||||
ownerID := objTemp.OwnerID()
|
||||
return payloadReader, cnr, ownerID
|
||||
}
|
||||
|
||||
func setFilePayloadReader(cmd *cobra.Command, f *os.File, prm *internalclient.PutObjectPrm) *pb.ProgressBar {
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Failed to get file size, progress bar is disabled: %v\n", err)
|
||||
prm.SetPayloadReader(f)
|
||||
return nil
|
||||
}
|
||||
p := pb.New64(fi.Size())
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadReader(p.NewProxyReader(f))
|
||||
prm.SetHeaderCallback(func(o *objectSDK.Object) { p.Start() })
|
||||
return p
|
||||
}
|
||||
|
||||
func setBinaryPayloadReader(cmd *cobra.Command, obj *objectSDK.Object, prm *internalclient.PutObjectPrm, payloadReader io.Reader) *pb.ProgressBar {
|
||||
p := pb.New(len(obj.Payload()))
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadReader(p.NewProxyReader(payloadReader))
|
||||
prm.SetHeaderCallback(func(o *objectSDK.Object) { p.Start() })
|
||||
return p
|
||||
}
|
||||
|
||||
func getAllObjectAttributes(cmd *cobra.Command) []objectSDK.Attribute {
|
||||
attrs, err := parseObjectAttrs(cmd)
|
||||
commonCmd.ExitOnErr(cmd, "can't parse object attributes: %w", err)
|
||||
|
||||
expiresOn, _ := cmd.Flags().GetUint64(commonflags.ExpireAt)
|
||||
if expiresOn > 0 {
|
||||
var expAttrFound bool
|
||||
expAttrValue := strconv.FormatUint(expiresOn, 10)
|
||||
|
||||
for i := range attrs {
|
||||
if attrs[i].Key() == objectV2.SysAttributeExpEpoch {
|
||||
attrs[i].SetValue(expAttrValue)
|
||||
expAttrFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !expAttrFound {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, objectSDK.Attribute{})
|
||||
attrs[index].SetKey(objectV2.SysAttributeExpEpoch)
|
||||
attrs[index].SetValue(expAttrValue)
|
||||
}
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func parseObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) {
|
||||
var rawAttrs []string
|
||||
|
||||
raw := cmd.Flag("attributes").Value.String()
|
||||
if len(raw) != 0 {
|
||||
rawAttrs = strings.Split(raw, ",")
|
||||
}
|
||||
|
||||
attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||
for i := range rawAttrs {
|
||||
k, v, found := strings.Cut(rawAttrs[i], "=")
|
||||
if !found {
|
||||
return nil, fmt.Errorf("invalid attribute format: %s", rawAttrs[i])
|
||||
}
|
||||
attrs[i].SetKey(k)
|
||||
attrs[i].SetValue(v)
|
||||
}
|
||||
|
||||
disableFilename, _ := cmd.Flags().GetBool("disable-filename")
|
||||
if !disableFilename {
|
||||
filename := filepath.Base(cmd.Flag(fileFlag).Value.String())
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, objectSDK.Attribute{})
|
||||
attrs[index].SetKey(objectSDK.AttributeFileName)
|
||||
attrs[index].SetValue(filename)
|
||||
}
|
||||
|
||||
disableTime, _ := cmd.Flags().GetBool("disable-timestamp")
|
||||
if !disableTime {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, objectSDK.Attribute{})
|
||||
attrs[index].SetKey(objectSDK.AttributeTimestamp)
|
||||
attrs[index].SetValue(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func parseObjectNotifications(cmd *cobra.Command) (*objectSDK.NotificationInfo, error) {
|
||||
const (
|
||||
separator = ":"
|
||||
useDefaultTopic = "-"
|
||||
)
|
||||
|
||||
raw := cmd.Flag(notificationFlag).Value.String()
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
before, after, found := strings.Cut(raw, separator)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("notification must be in the form of: *epoch*%s*topic*, got %s", separator, raw)
|
||||
}
|
||||
|
||||
ni := new(objectSDK.NotificationInfo)
|
||||
|
||||
epoch, err := strconv.ParseUint(before, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse notification epoch %s: %w", before, err)
|
||||
}
|
||||
|
||||
ni.SetEpoch(epoch)
|
||||
|
||||
if after == "" {
|
||||
return nil, fmt.Errorf("incorrect empty topic: use %s to force using default topic", useDefaultTopic)
|
||||
}
|
||||
|
||||
if after != useDefaultTopic {
|
||||
ni.SetTopic(after)
|
||||
}
|
||||
|
||||
return ni, nil
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
accountingCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl"
|
||||
bearerCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/bearer"
|
||||
containerCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/container"
|
||||
controlCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/control"
|
||||
netmapCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/netmap"
|
||||
objectCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/object"
|
||||
sessionCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/tree"
|
||||
utilCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
|
||||
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
envPrefix = "FROSTFS_CLI"
|
||||
)
|
||||
|
||||
// Global scope flags.
|
||||
var (
|
||||
cfgFile string
|
||||
cfgDir string
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands.
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "frostfs-cli",
|
||||
Short: "Command Line Tool to work with FrostFS",
|
||||
Long: `FrostFS CLI provides all basic interactions with FrostFS and it's services.
|
||||
|
||||
It contains commands for interaction with FrostFS nodes using different versions
|
||||
of frostfs-api and some useful utilities for compiling ACL rules from JSON
|
||||
notation, managing container access through protocol gates, querying network map
|
||||
and much more!`,
|
||||
Run: entryPoint,
|
||||
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
|
||||
common.StartClientCommandSpan(cmd)
|
||||
},
|
||||
PersistentPostRun: common.StopClientCommandSpan,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
commonCmd.ExitOnErr(rootCmd, "", err)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
cobra.EnableTraverseRunHooks = true
|
||||
|
||||
// use stdout as default output for cmd.Print()
|
||||
rootCmd.SetOut(os.Stdout)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "Config file (default is $HOME/.config/frostfs-cli/config.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfgDir, "config-dir", "", "Config directory")
|
||||
rootCmd.PersistentFlags().BoolP(commonflags.Verbose, commonflags.VerboseShorthand,
|
||||
false, commonflags.VerboseUsage)
|
||||
|
||||
_ = viper.BindPFlag(commonflags.Verbose, rootCmd.PersistentFlags().Lookup(commonflags.Verbose))
|
||||
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
rootCmd.Flags().Bool("version", false, "Application version and FrostFS API compatibility")
|
||||
|
||||
rootCmd.AddCommand(acl.Cmd)
|
||||
rootCmd.AddCommand(bearerCli.Cmd)
|
||||
rootCmd.AddCommand(sessionCli.Cmd)
|
||||
rootCmd.AddCommand(accountingCli.Cmd)
|
||||
rootCmd.AddCommand(controlCli.Cmd)
|
||||
rootCmd.AddCommand(utilCli.Cmd)
|
||||
rootCmd.AddCommand(netmapCli.Cmd)
|
||||
rootCmd.AddCommand(objectCli.Cmd)
|
||||
rootCmd.AddCommand(containerCli.Cmd)
|
||||
rootCmd.AddCommand(tree.Cmd)
|
||||
rootCmd.AddCommand(gendoc.Command(rootCmd, gendoc.Options{}))
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, _ []string) {
|
||||
printVersion, _ := cmd.Flags().GetBool("version")
|
||||
if printVersion {
|
||||
cmd.Print(misc.BuildInfo("FrostFS CLI"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_ = cmd.Usage()
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
commonCmd.ExitOnErr(rootCmd, "", err)
|
||||
|
||||
// Search config in `$HOME/.config/frostfs-cli/` with name "config.yaml"
|
||||
viper.AddConfigPath(filepath.Join(home, ".config", "frostfs-cli"))
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix(envPrefix)
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
common.PrintVerbose(rootCmd, "Using config file: %s", viper.ConfigFileUsed())
|
||||
}
|
||||
|
||||
if cfgDir != "" {
|
||||
if err := config.ReadConfigDir(viper.GetViper(), cfgDir); err != nil {
|
||||
commonCmd.ExitOnErr(rootCmd, "failed to read config dir: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
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/local_object_storage/pilorama"
|
||||
"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())
|
||||
|
||||
m := &pilorama.Meta{}
|
||||
err = m.FromBytes(o.GetMeta())
|
||||
commonCmd.ExitOnErr(cmd, "could not unmarshal meta: %w", err)
|
||||
cmd.Printf("\tMeta:\n")
|
||||
cmd.Printf("\t\tTime: %d\n", m.Time)
|
||||
for _, item := range m.Items {
|
||||
cmd.Printf("\t\t%s: %s\n", item.Key, item.Value)
|
||||
}
|
||||
}
|
||||
if !errors.Is(err, io.EOF) {
|
||||
commonCmd.ExitOnErr(cmd, "get op log response stream: %w", err)
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
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.")
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
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.")
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
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.")
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package tree
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "tree",
|
||||
Short: "Operations with the Tree service",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(addCmd)
|
||||
Cmd.AddCommand(getByPathCmd)
|
||||
Cmd.AddCommand(addByPathCmd)
|
||||
Cmd.AddCommand(listCmd)
|
||||
Cmd.AddCommand(healthcheckCmd)
|
||||
Cmd.AddCommand(moveCmd)
|
||||
Cmd.AddCommand(removeCmd)
|
||||
Cmd.AddCommand(getSubtreeCmd)
|
||||
Cmd.AddCommand(getOpLogCmd)
|
||||
|
||||
initAddCmd()
|
||||
initGetByPathCmd()
|
||||
initAddByPathCmd()
|
||||
initListCmd()
|
||||
initHealthcheckCmd()
|
||||
initMoveCmd()
|
||||
initRemoveCmd()
|
||||
initGetSubtreeCmd()
|
||||
initGetOpLogCmd()
|
||||
}
|
||||
|
||||
const (
|
||||
treeIDFlagKey = "tid"
|
||||
parentIDFlagKey = "pid"
|
||||
nodeIDFlagKey = "nid"
|
||||
rootIDFlagKey = "root"
|
||||
|
||||
metaFlagKey = "meta"
|
||||
|
||||
pathFlagKey = "path"
|
||||
pathAttributeFlagKey = "pattr"
|
||||
|
||||
latestOnlyFlagKey = "latest"
|
||||
|
||||
bearerFlagKey = "bearer"
|
||||
|
||||
heightFlagKey = "height"
|
||||
countFlagKey = "count"
|
||||
depthFlagKey = "depth"
|
||||
)
|
||||
|
||||
func initCTID(cmd *cobra.Command) {
|
||||
ff := cmd.Flags()
|
||||
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = cmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
ff.String(treeIDFlagKey, "", "Tree ID")
|
||||
_ = cmd.MarkFlagRequired(treeIDFlagKey)
|
||||
|
||||
ff.String(bearerFlagKey, "", "Path to bearer token")
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/flynn-archive/go-shlex"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// PrettyPrintTableBACL print basic ACL in table format.
|
||||
func PrettyPrintTableBACL(cmd *cobra.Command, bacl *acl.Basic) {
|
||||
// Header
|
||||
w := tabwriter.NewWriter(cmd.OutOrStdout(), 1, 4, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "\tRangeHASH\tRange\tSearch\tDelete\tPut\tHead\tGet")
|
||||
// Bits
|
||||
bits := []string{
|
||||
boolToString(bacl.Sticky()) + " " + boolToString(!bacl.Extendable()),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectHash), getRoleBitsForOperation(bacl, acl.OpObjectRange),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectSearch), getRoleBitsForOperation(bacl, acl.OpObjectDelete),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectPut), getRoleBitsForOperation(bacl, acl.OpObjectHead),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectGet),
|
||||
}
|
||||
fmt.Fprintln(w, strings.Join(bits, "\t"))
|
||||
// Footer
|
||||
footer := []string{"X F"}
|
||||
for i := 0; i < 7; i++ {
|
||||
footer = append(footer, "U S O B")
|
||||
}
|
||||
fmt.Fprintln(w, strings.Join(footer, "\t"))
|
||||
|
||||
w.Flush()
|
||||
|
||||
cmd.Println(" X-Sticky F-Final U-User S-System O-Others B-Bearer")
|
||||
}
|
||||
|
||||
func getRoleBitsForOperation(bacl *acl.Basic, op acl.Op) string {
|
||||
return boolToString(bacl.IsOpAllowed(op, acl.RoleOwner)) + " " +
|
||||
boolToString(bacl.IsOpAllowed(op, acl.RoleContainer)) + " " +
|
||||
boolToString(bacl.IsOpAllowed(op, acl.RoleOthers)) + " " +
|
||||
boolToString(bacl.AllowedBearerRules(op))
|
||||
}
|
||||
|
||||
func boolToString(b bool) string {
|
||||
if b {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// PrettyPrintTableEACL print extended ACL in table format.
|
||||
func PrettyPrintTableEACL(cmd *cobra.Command, table *eacl.Table) {
|
||||
out := tablewriter.NewWriter(cmd.OutOrStdout())
|
||||
out.SetHeader([]string{"Operation", "Action", "Filters", "Targets"})
|
||||
out.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||
out.SetRowLine(true)
|
||||
|
||||
out.SetAutoWrapText(false)
|
||||
|
||||
for _, r := range table.Records() {
|
||||
out.Append([]string{
|
||||
r.Operation().String(),
|
||||
r.Action().String(),
|
||||
eaclFiltersToString(r.Filters()),
|
||||
eaclTargetsToString(r.Targets()),
|
||||
})
|
||||
}
|
||||
|
||||
out.Render()
|
||||
}
|
||||
|
||||
func eaclTargetsToString(ts []eacl.Target) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
for _, t := range ts {
|
||||
keysExists := len(t.BinaryKeys()) > 0
|
||||
switch t.Role() {
|
||||
case eacl.RoleUser:
|
||||
b.WriteString("User")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
case eacl.RoleSystem:
|
||||
b.WriteString("System")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
case eacl.RoleOthers:
|
||||
b.WriteString("Others")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
default:
|
||||
b.WriteString("Unknown")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
}
|
||||
|
||||
for i, pub := range t.BinaryKeys() {
|
||||
if i != 0 {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
b.WriteString(hex.EncodeToString(pub))
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func eaclFiltersToString(fs []eacl.Filter) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(b, 0, 0, 1, ' ', 0)
|
||||
|
||||
for _, f := range fs {
|
||||
switch f.From() {
|
||||
case eacl.HeaderFromObject:
|
||||
_, _ = tw.Write([]byte("O:\t"))
|
||||
case eacl.HeaderFromRequest:
|
||||
_, _ = tw.Write([]byte("R:\t"))
|
||||
case eacl.HeaderFromService:
|
||||
_, _ = tw.Write([]byte("S:\t"))
|
||||
default:
|
||||
_, _ = tw.Write([]byte(" \t"))
|
||||
}
|
||||
|
||||
_, _ = tw.Write([]byte(f.Key()))
|
||||
|
||||
switch f.Matcher() {
|
||||
case eacl.MatchStringEqual:
|
||||
_, _ = tw.Write([]byte("\t==\t"))
|
||||
case eacl.MatchStringNotEqual:
|
||||
_, _ = tw.Write([]byte("\t!=\t"))
|
||||
case eacl.MatchUnknown:
|
||||
}
|
||||
|
||||
_, _ = tw.Write([]byte(f.Value() + "\t"))
|
||||
_, _ = tw.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
_ = tw.Flush()
|
||||
|
||||
// To have nice output with tabwriter, we must append newline
|
||||
// after the last line. Here we strip it to delete empty line
|
||||
// in the final output.
|
||||
s := b.String()
|
||||
if len(s) > 0 {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ParseEACLRules parses eACL table.
|
||||
// Uses ParseEACLRule.
|
||||
func ParseEACLRules(table *eacl.Table, rules []string) error {
|
||||
if len(rules) == 0 {
|
||||
return errors.New("no extended ACL rules has been provided")
|
||||
}
|
||||
|
||||
for _, ruleStr := range rules {
|
||||
err := ParseEACLRule(table, ruleStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create extended acl record from rule '%s': %v", ruleStr, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseEACLRule parses eACL table from the following form:
|
||||
// <action> <operation> [<filter1> ...] [<target1> ...]
|
||||
//
|
||||
// Examples:
|
||||
// allow get req:X-Header=123 obj:Attr=value others:0xkey1,key2 system:key3 user:key4
|
||||
//
|
||||
//nolint:godot
|
||||
func ParseEACLRule(table *eacl.Table, rule string) error {
|
||||
r, err := shlex.Split(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse rule '%s': %v", rule, err)
|
||||
}
|
||||
return parseEACLTable(table, r)
|
||||
}
|
||||
|
||||
func parseEACLTable(tb *eacl.Table, args []string) error {
|
||||
if len(args) < 2 {
|
||||
return errors.New("at least 2 arguments must be provided")
|
||||
}
|
||||
|
||||
var action eacl.Action
|
||||
if !action.FromString(strings.ToUpper(args[0])) {
|
||||
return errors.New("invalid action (expected 'allow' or 'deny')")
|
||||
}
|
||||
|
||||
ops, err := eaclOperationsFromString(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := parseEACLRecord(args[2:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SetAction(action)
|
||||
|
||||
for _, op := range ops {
|
||||
r := *r
|
||||
r.SetOperation(op)
|
||||
tb.AddRecord(&r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEACLRecord(args []string) (*eacl.Record, error) {
|
||||
r := new(eacl.Record)
|
||||
for _, arg := range args {
|
||||
before, after, found := strings.Cut(arg, ":")
|
||||
|
||||
switch prefix := strings.ToLower(before); prefix {
|
||||
case "req", "obj": // filters
|
||||
if !found {
|
||||
return nil, fmt.Errorf("invalid filter or target: %s", arg)
|
||||
}
|
||||
|
||||
var key, value string
|
||||
var op eacl.Match
|
||||
var f bool
|
||||
|
||||
key, value, f = strings.Cut(after, "!=")
|
||||
if f {
|
||||
op = eacl.MatchStringNotEqual
|
||||
} else {
|
||||
key, value, f = strings.Cut(after, "=")
|
||||
if !f {
|
||||
return nil, fmt.Errorf("invalid filter key-value pair: %s", after)
|
||||
}
|
||||
op = eacl.MatchStringEqual
|
||||
}
|
||||
|
||||
typ := eacl.HeaderFromRequest
|
||||
if before == "obj" {
|
||||
typ = eacl.HeaderFromObject
|
||||
}
|
||||
|
||||
r.AddFilter(typ, op, key, value)
|
||||
case "others", "system", "user", "pubkey": // targets
|
||||
var err error
|
||||
|
||||
var pubs []ecdsa.PublicKey
|
||||
if found {
|
||||
pubs, err = parseKeyList(after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var role eacl.Role
|
||||
if prefix != "pubkey" {
|
||||
role, err = eaclRoleFromString(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
eacl.AddFormedTarget(r, role, pubs...)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid prefix: %s", before)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// eaclRoleFromString parses eacl.Role from string.
|
||||
func eaclRoleFromString(s string) (eacl.Role, error) {
|
||||
var r eacl.Role
|
||||
if !r.FromString(strings.ToUpper(s)) {
|
||||
return r, fmt.Errorf("unexpected role %s", s)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// parseKeyList parses list of hex-encoded public keys separated by comma.
|
||||
func parseKeyList(s string) ([]ecdsa.PublicKey, error) {
|
||||
ss := strings.Split(s, ",")
|
||||
pubs := make([]ecdsa.PublicKey, len(ss))
|
||||
for i := range ss {
|
||||
st := strings.TrimPrefix(ss[i], "0x")
|
||||
pub, err := keys.NewPublicKeyFromString(st)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key '%s': %w", ss[i], err)
|
||||
}
|
||||
|
||||
pubs[i] = ecdsa.PublicKey(*pub)
|
||||
}
|
||||
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
// eaclOperationsFromString parses list of eacl.Operation separated by comma.
|
||||
func eaclOperationsFromString(s string) ([]eacl.Operation, error) {
|
||||
ss := strings.Split(s, ",")
|
||||
ops := make([]eacl.Operation, len(ss))
|
||||
|
||||
for i := range ss {
|
||||
if !ops[i].FromString(strings.ToUpper(ss[i])) {
|
||||
return nil, fmt.Errorf("invalid operation: %s", ss[i])
|
||||
}
|
||||
}
|
||||
|
||||
return ops, nil
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/flynn-archive/go-shlex"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidStatementFormat = errors.New("invalid statement format")
|
||||
errInvalidConditionFormat = errors.New("invalid condition format")
|
||||
errUnknownAction = errors.New("action is not recognized")
|
||||
errUnknownOperation = errors.New("operation is not recognized")
|
||||
errUnknownActionDetail = errors.New("action detail is not recognized")
|
||||
errUnknownBinaryOperator = errors.New("binary operator is not recognized")
|
||||
errUnknownCondObjectType = errors.New("condition object type is not recognized")
|
||||
)
|
||||
|
||||
// ParseAPEChain parses APE chain rules.
|
||||
func ParseAPEChain(chain *apechain.Chain, rules []string) error {
|
||||
if len(rules) == 0 {
|
||||
return errors.New("no APE rules provided")
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
r := new(apechain.Rule)
|
||||
if err := ParseAPERule(r, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
chain.Rules = append(chain.Rules, *r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseAPERule parses access-policy-engine statement from the following form:
|
||||
// <action>[:action_detail] <operation> [<condition1> ...] <resource>
|
||||
//
|
||||
// Examples:
|
||||
// deny Object.Put *
|
||||
// deny:QuotaLimitReached Object.Put *
|
||||
// allow Object.Put *
|
||||
// allow Object.Get Object.Resource:Department=HR Object.Request:Actor=ownerA *
|
||||
//
|
||||
//nolint:godot
|
||||
func ParseAPERule(r *apechain.Rule, rule string) error {
|
||||
lexemes, err := shlex.Split(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse rule '%s': %v", rule, err)
|
||||
}
|
||||
return parseRuleLexemes(r, lexemes)
|
||||
}
|
||||
|
||||
func parseRuleLexemes(r *apechain.Rule, lexemes []string) error {
|
||||
if len(lexemes) < 2 {
|
||||
return errInvalidStatementFormat
|
||||
}
|
||||
|
||||
var err error
|
||||
r.Status, err = parseStatus(lexemes[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Actions, err = parseAction(lexemes[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Condition, err = parseConditions(lexemes[2 : len(lexemes)-1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Resources, err = parseResource(lexemes[len(lexemes)-1])
|
||||
return err
|
||||
}
|
||||
|
||||
func parseStatus(lexeme string) (apechain.Status, error) {
|
||||
action, expression, found := strings.Cut(lexeme, ":")
|
||||
switch action = strings.ToLower(action); action {
|
||||
case "deny":
|
||||
if !found {
|
||||
return apechain.AccessDenied, nil
|
||||
} else if strings.EqualFold(expression, "QuotaLimitReached") {
|
||||
return apechain.QuotaLimitReached, nil
|
||||
} else {
|
||||
return 0, fmt.Errorf("%w: %s", errUnknownActionDetail, expression)
|
||||
}
|
||||
case "allow":
|
||||
if found {
|
||||
return 0, errUnknownActionDetail
|
||||
}
|
||||
return apechain.Allow, nil
|
||||
default:
|
||||
return 0, errUnknownAction
|
||||
}
|
||||
}
|
||||
|
||||
func parseAction(lexeme string) (apechain.Actions, error) {
|
||||
switch strings.ToLower(lexeme) {
|
||||
case "object.put":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodPutObject}}, nil
|
||||
case "object.get":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodGetObject}}, nil
|
||||
case "object.head":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodHeadObject}}, nil
|
||||
case "object.delete":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodDeleteObject}}, nil
|
||||
case "object.search":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodSearchObject}}, nil
|
||||
case "object.range":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodRangeObject}}, nil
|
||||
case "object.hash":
|
||||
return apechain.Actions{Names: []string{nativeschema.MethodHashObject}}, nil
|
||||
default:
|
||||
}
|
||||
return apechain.Actions{}, fmt.Errorf("%w: %s", errUnknownOperation, lexeme)
|
||||
}
|
||||
|
||||
func parseResource(lexeme string) (apechain.Resources, error) {
|
||||
if lexeme == "*" {
|
||||
return apechain.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}}, nil
|
||||
}
|
||||
return apechain.Resources{Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, lexeme)}}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
ObjectResource = "object.resource"
|
||||
ObjectRequest = "object.request"
|
||||
)
|
||||
|
||||
var typeToCondObject = map[string]apechain.ObjectType{
|
||||
ObjectResource: apechain.ObjectResource,
|
||||
ObjectRequest: apechain.ObjectRequest,
|
||||
}
|
||||
|
||||
func parseConditions(lexemes []string) ([]apechain.Condition, error) {
|
||||
conds := make([]apechain.Condition, 0)
|
||||
|
||||
for _, lexeme := range lexemes {
|
||||
typ, expression, found := strings.Cut(lexeme, ":")
|
||||
typ = strings.ToLower(typ)
|
||||
|
||||
objType, ok := typeToCondObject[typ]
|
||||
if ok {
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%w: %s", errInvalidConditionFormat, lexeme)
|
||||
}
|
||||
|
||||
var lhs, rhs string
|
||||
var binExpFound bool
|
||||
|
||||
var cond apechain.Condition
|
||||
cond.Object = objType
|
||||
|
||||
lhs, rhs, binExpFound = strings.Cut(expression, "!=")
|
||||
if !binExpFound {
|
||||
lhs, rhs, binExpFound = strings.Cut(expression, "=")
|
||||
if !binExpFound {
|
||||
return nil, fmt.Errorf("%w: %s", errUnknownBinaryOperator, expression)
|
||||
}
|
||||
cond.Op = apechain.CondStringEquals
|
||||
} else {
|
||||
cond.Op = apechain.CondStringNotEquals
|
||||
}
|
||||
|
||||
cond.Key, cond.Value = lhs, rhs
|
||||
|
||||
conds = append(conds, cond)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%w: %s", errUnknownCondObjectType, typ)
|
||||
}
|
||||
}
|
||||
|
||||
return conds, nil
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseAPERule(t *testing.T) {
|
||||
tests := [...]struct {
|
||||
name string
|
||||
rule string
|
||||
expectErr error
|
||||
expectRule policyengine.Rule
|
||||
}{
|
||||
{
|
||||
name: "Valid allow rule",
|
||||
rule: "allow Object.Put *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
Condition: []policyengine.Condition{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid deny rule",
|
||||
rule: "deny Object.Put *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.AccessDenied,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
Condition: []policyengine.Condition{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid deny rule with action detail",
|
||||
rule: "deny:QuotaLimitReached Object.Put *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.QuotaLimitReached,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
Condition: []policyengine.Condition{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid allow rule with conditions",
|
||||
rule: "allow Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.Allow,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
Condition: []policyengine.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectResource,
|
||||
Key: "Department",
|
||||
Value: "HR",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Key: "Actor",
|
||||
Value: "ownerA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid rule with conditions with action detail",
|
||||
rule: "deny:QuotaLimitReached Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
|
||||
expectRule: policyengine.Rule{
|
||||
Status: policyengine.QuotaLimitReached,
|
||||
Actions: policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||
Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
|
||||
Condition: []policyengine.Condition{
|
||||
{
|
||||
Op: policyengine.CondStringEquals,
|
||||
Object: policyengine.ObjectResource,
|
||||
Key: "Department",
|
||||
Value: "HR",
|
||||
},
|
||||
{
|
||||
Op: policyengine.CondStringNotEquals,
|
||||
Object: policyengine.ObjectRequest,
|
||||
Key: "Actor",
|
||||
Value: "ownerA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown action",
|
||||
rule: "permit Object.Put *",
|
||||
expectErr: errUnknownAction,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown operation",
|
||||
rule: "allow Object.PutOut *",
|
||||
expectErr: errUnknownOperation,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown action detail",
|
||||
rule: "deny:UnknownActionDetail Object.Put *",
|
||||
expectErr: errUnknownActionDetail,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown condition binary operator",
|
||||
rule: "deny Object.Put Object.Resource:Department<HR *",
|
||||
expectErr: errUnknownBinaryOperator,
|
||||
},
|
||||
{
|
||||
name: "Invalid rule with unknown condition object type",
|
||||
rule: "deny Object.Put Object.ResourZe:Department=HR *",
|
||||
expectErr: errUnknownCondObjectType,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := new(policyengine.Rule)
|
||||
err := ParseAPERule(r, test.rule)
|
||||
require.ErrorIs(t, err, test.expectErr)
|
||||
if test.expectErr == nil {
|
||||
require.Equal(t, test.expectRule, *r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
configViper "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/config"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func newConfig() (*viper.Viper, error) {
|
||||
var err error
|
||||
dv := viper.New()
|
||||
|
||||
defaultConfiguration(dv)
|
||||
|
||||
_, err = configViper.CreateViper(configViper.WithConfigFile(*configFile),
|
||||
configViper.WithConfigDir(*configDir), configViper.WithEnvPrefix(EnvPrefix),
|
||||
configViper.WithViper(dv))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dv, err
|
||||
}
|
||||
|
||||
func reloadConfig() error {
|
||||
err := configViper.ReloadViper(configViper.WithConfigFile(*configFile),
|
||||
configViper.WithConfigDir(*configDir), configViper.WithEnvPrefix(EnvPrefix),
|
||||
configViper.WithViper(cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = logPrm.SetLevelString(cfg.GetString("logger.level"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return logPrm.Reload()
|
||||
}
|
||||
|
||||
func watchForSignal(cancel func()) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-intErr:
|
||||
log.Info(logs.FrostFSIRInternalError, zap.String("msg", err.Error()))
|
||||
cancel()
|
||||
shutdown()
|
||||
return
|
||||
case sig := <-ch:
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
log.Info(logs.FrostFSNodeSIGHUPHasBeenReceivedRereadingConfiguration)
|
||||
err := reloadConfig()
|
||||
if err != nil {
|
||||
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)
|
||||
case syscall.SIGTERM, syscall.SIGINT:
|
||||
log.Info(logs.FrostFSNodeTerminationSignalHasBeenReceivedStopping)
|
||||
cancel()
|
||||
shutdown()
|
||||
log.Info(logs.FrostFSNodeTerminationSignalProcessingIsComplete)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
||||
irMetrics "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrorReturnCode returns when application crashed at initialization stage.
|
||||
ErrorReturnCode = 1
|
||||
|
||||
// SuccessReturnCode returns when application closed without panic.
|
||||
SuccessReturnCode = 0
|
||||
|
||||
EnvPrefix = "FROSTFS_IR"
|
||||
)
|
||||
|
||||
var (
|
||||
wg = new(sync.WaitGroup)
|
||||
intErr = make(chan error) // internal inner ring errors
|
||||
logPrm = new(logger.Prm)
|
||||
innerRing *innerring.Server
|
||||
pprofCmp *pprofComponent
|
||||
metricsCmp *httpComponent
|
||||
log *logger.Logger
|
||||
cfg *viper.Viper
|
||||
configFile *string
|
||||
configDir *string
|
||||
)
|
||||
|
||||
func exitErr(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(ErrorReturnCode)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
configFile = flag.String("config", "", "path to config")
|
||||
configDir = flag.String("config-dir", "", "path to config directory")
|
||||
versionFlag := flag.Bool("version", false, "frostfs-ir node version")
|
||||
flag.Parse()
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Print(misc.BuildInfo("FrostFS Inner Ring node"))
|
||||
|
||||
os.Exit(SuccessReturnCode)
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = newConfig()
|
||||
exitErr(err)
|
||||
|
||||
metrics := irMetrics.NewInnerRingMetrics()
|
||||
|
||||
err = logPrm.SetLevelString(
|
||||
cfg.GetString("logger.level"),
|
||||
)
|
||||
exitErr(err)
|
||||
logPrm.SamplingHook = metrics.LogMetrics().GetSamplingHook()
|
||||
log, err = logger.NewLogger(logPrm)
|
||||
exitErr(err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
pprofCmp = newPprofComponent()
|
||||
pprofCmp.init()
|
||||
|
||||
metricsCmp = newMetricsComponent()
|
||||
metricsCmp.init()
|
||||
|
||||
innerRing, err = innerring.New(ctx, log, cfg, intErr, metrics)
|
||||
exitErr(err)
|
||||
|
||||
pprofCmp.start()
|
||||
metricsCmp.start()
|
||||
|
||||
// start inner ring
|
||||
err = innerRing.Start(ctx, intErr)
|
||||
exitErr(err)
|
||||
|
||||
log.Info(logs.CommonApplicationStarted,
|
||||
zap.String("version", misc.Version))
|
||||
|
||||
watchForSignal(cancel)
|
||||
|
||||
<-ctx.Done() // graceful shutdown
|
||||
log.Debug(logs.FrostFSNodeWaitingForAllProcessesToStop)
|
||||
wg.Wait()
|
||||
|
||||
log.Info(logs.FrostFSIRApplicationStopped)
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
innerRing.Stop()
|
||||
if err := metricsCmp.shutdown(); err != nil {
|
||||
log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
|
||||
zap.String("error", err.Error()),
|
||||
)
|
||||
}
|
||||
if err := pprofCmp.shutdown(); err != nil {
|
||||
log.Debug(logs.FrostFSIRCouldNotShutdownHTTPServer,
|
||||
zap.String("error", err.Error()),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
|
||||
)
|
||||
|
||||
func newMetricsComponent() *httpComponent {
|
||||
return &httpComponent{
|
||||
name: "prometheus",
|
||||
handler: metrics.Handler(),
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
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()
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package writecache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache/writecachebadger"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache/writecachebbolt"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var inspectCMD = &cobra.Command{
|
||||
Use: "inspect",
|
||||
Short: "Object inspection",
|
||||
Long: `Inspect specific object in a write-cache.`,
|
||||
Run: inspectFunc,
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.AddAddressFlag(inspectCMD, &vAddress)
|
||||
common.AddComponentPathFlag(inspectCMD, &vPath)
|
||||
common.AddOutputFileFlag(inspectCMD, &vOut)
|
||||
common.AddDBTypeFlag(inspectCMD, &vDBType)
|
||||
}
|
||||
|
||||
func inspectFunc(cmd *cobra.Command, _ []string) {
|
||||
var data []byte
|
||||
|
||||
switch vDBType {
|
||||
case "bbolt":
|
||||
db, err := writecachebbolt.OpenDB(vPath, true, os.OpenFile)
|
||||
common.ExitOnErr(cmd, common.Errf("could not open write-cache db: %w", err))
|
||||
defer db.Close()
|
||||
|
||||
data, err = writecachebbolt.Get(db, []byte(vAddress))
|
||||
common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err))
|
||||
|
||||
case "badger":
|
||||
log, err := logger.NewLogger(&logger.Prm{})
|
||||
common.ExitOnErr(cmd, common.Errf("could not create logger: %w", err))
|
||||
|
||||
db, err := writecachebadger.OpenDB(vPath, true, log)
|
||||
common.ExitOnErr(cmd, common.Errf("could not open write-cache db: %w", err))
|
||||
|
||||
data, err = writecachebadger.Get(db, []byte(vAddress))
|
||||
common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err))
|
||||
|
||||
default:
|
||||
common.ExitOnErr(cmd, fmt.Errorf("invalid dbtype: %q (possible values: bbolt, badger)", vDBType))
|
||||
}
|
||||
|
||||
var o objectSDK.Object
|
||||
common.ExitOnErr(cmd, common.Errf("could not unmarshal object: %w", o.Unmarshal(data)))
|
||||
|
||||
common.PrintObjectHeader(cmd, o)
|
||||
common.WriteObjectToFile(cmd, vOut, data)
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package writecache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache/writecachebadger"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache/writecachebbolt"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var listCMD = &cobra.Command{
|
||||
Use: "inspect",
|
||||
Short: "Object inspection",
|
||||
Long: `Inspect specific object in a write-cache.`,
|
||||
Run: listFunc,
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.AddComponentPathFlag(listCMD, &vPath)
|
||||
}
|
||||
|
||||
func listFunc(cmd *cobra.Command, _ []string) {
|
||||
// other targets can be supported
|
||||
w := cmd.OutOrStderr()
|
||||
|
||||
wAddr := func(addr oid.Address) error {
|
||||
_, err := io.WriteString(w, fmt.Sprintf("%s\n", addr))
|
||||
return err
|
||||
}
|
||||
|
||||
switch vDBType {
|
||||
case "bbolt":
|
||||
db, err := writecachebbolt.OpenDB(vPath, true, os.OpenFile)
|
||||
common.ExitOnErr(cmd, common.Errf("could not open write-cache db: %w", err))
|
||||
defer db.Close()
|
||||
|
||||
err = writecachebbolt.IterateDB(db, wAddr)
|
||||
common.ExitOnErr(cmd, common.Errf("write-cache iterator failure: %w", err))
|
||||
|
||||
case "badger":
|
||||
log, err := logger.NewLogger(&logger.Prm{})
|
||||
common.ExitOnErr(cmd, common.Errf("could not create logger: %w", err))
|
||||
|
||||
db, err := writecachebadger.OpenDB(vPath, true, log)
|
||||
common.ExitOnErr(cmd, common.Errf("could not open write-cache db: %w", err))
|
||||
|
||||
err = writecachebadger.IterateDB(db, wAddr)
|
||||
common.ExitOnErr(cmd, common.Errf("write-cache iterator failure: %w", err))
|
||||
|
||||
default:
|
||||
common.ExitOnErr(cmd, fmt.Errorf("invalid dbtype: %q (possible values: bbolt, badger)", vDBType))
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package writecache
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
vAddress string
|
||||
vPath string
|
||||
vOut string
|
||||
vDBType string
|
||||
)
|
||||
|
||||
// Root contains `write-cache` command definition.
|
||||
var Root = &cobra.Command{
|
||||
Use: "write-cache",
|
||||
Short: "Operations with write-cache",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Root.AddCommand(listCMD, inspectCMD)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/blobovnicza"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/meta"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/writecache"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/misc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/gendoc"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
Use: "frostfs-lens",
|
||||
Short: "FrostFS Storage Engine Lens",
|
||||
Long: `FrostFS Storage Engine Lens provides tools to browse the contents of the FrostFS storage engine.`,
|
||||
RunE: entryPoint,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func entryPoint(cmd *cobra.Command, _ []string) error {
|
||||
printVersion, _ := cmd.Flags().GetBool("version")
|
||||
if printVersion {
|
||||
cmd.Print(misc.BuildInfo("FrostFS Lens"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Usage()
|
||||
}
|
||||
|
||||
func init() {
|
||||
// use stdout as default output for cmd.Print()
|
||||
command.SetOut(os.Stdout)
|
||||
command.Flags().Bool("version", false, "Application version")
|
||||
command.AddCommand(
|
||||
blobovnicza.Root,
|
||||
meta.Root,
|
||||
writecache.Root,
|
||||
gendoc.Command(command, gendoc.Options{}),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := command.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
accountingGRPC "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting/grpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
|
||||
accountingTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/accounting/grpc"
|
||||
accountingService "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/accounting"
|
||||
accounting "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/accounting/morph"
|
||||
)
|
||||
|
||||
func initAccountingService(ctx context.Context, c *cfg) {
|
||||
if c.cfgMorph.client == nil {
|
||||
initMorphComponents(ctx, c)
|
||||
}
|
||||
|
||||
balanceMorphWrapper, err := balance.NewFromMorph(c.cfgMorph.client, c.cfgAccounting.scriptHash, 0)
|
||||
fatalOnErr(err)
|
||||
|
||||
server := accountingTransportGRPC.New(
|
||||
accountingService.NewSignService(
|
||||
&c.key.PrivateKey,
|
||||
accountingService.NewExecutionService(
|
||||
accounting.NewExecutor(balanceMorphWrapper),
|
||||
c.respSvc,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for _, srv := range c.cfgGRPC.servers {
|
||||
accountingGRPC.RegisterAccountingServiceServer(srv, server)
|
||||
}
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||
putsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/put"
|
||||
utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync"
|
||||
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"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
)
|
||||
|
||||
type netValueReader[K any, V any] func(K) (V, error)
|
||||
|
||||
type valueWithError[V any] struct {
|
||||
v V
|
||||
// cached error in order to not repeat failed request for some time
|
||||
e error
|
||||
}
|
||||
|
||||
// entity that provides TTL cache interface.
|
||||
type ttlNetCache[K comparable, V any] struct {
|
||||
cache *expirable.LRU[K, *valueWithError[V]]
|
||||
netRdr netValueReader[K, V]
|
||||
keyLocker *utilSync.KeyLocker[K]
|
||||
}
|
||||
|
||||
// complicates netValueReader with TTL caching mechanism.
|
||||
func newNetworkTTLCache[K comparable, V any](sz int, ttl time.Duration, netRdr netValueReader[K, V]) *ttlNetCache[K, V] {
|
||||
cache := expirable.NewLRU[K, *valueWithError[V]](sz, nil, ttl)
|
||||
|
||||
return &ttlNetCache[K, V]{
|
||||
cache: cache,
|
||||
netRdr: netRdr,
|
||||
keyLocker: utilSync.NewKeyLocker[K](),
|
||||
}
|
||||
}
|
||||
|
||||
// reads value by the key.
|
||||
//
|
||||
// updates the value from the network on cache miss or by TTL.
|
||||
//
|
||||
// returned value should not be modified.
|
||||
func (c *ttlNetCache[K, V]) get(key K) (V, error) {
|
||||
val, ok := c.cache.Peek(key)
|
||||
if ok {
|
||||
return val.v, val.e
|
||||
}
|
||||
|
||||
c.keyLocker.Lock(key)
|
||||
defer c.keyLocker.Unlock(key)
|
||||
|
||||
val, ok = c.cache.Peek(key)
|
||||
if ok {
|
||||
return val.v, val.e
|
||||
}
|
||||
|
||||
v, err := c.netRdr(key)
|
||||
|
||||
c.cache.Add(key, &valueWithError[V]{
|
||||
v: v,
|
||||
e: err,
|
||||
})
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *ttlNetCache[K, V]) set(k K, v V, e error) {
|
||||
c.keyLocker.Lock(k)
|
||||
defer c.keyLocker.Unlock(k)
|
||||
|
||||
c.cache.Add(k, &valueWithError[V]{
|
||||
v: v,
|
||||
e: e,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ttlNetCache[K, V]) remove(key K) {
|
||||
c.keyLocker.Lock(key)
|
||||
defer c.keyLocker.Unlock(key)
|
||||
|
||||
c.cache.Remove(key)
|
||||
}
|
||||
|
||||
// entity that provides LRU cache interface.
|
||||
type lruNetCache struct {
|
||||
cache *lru.Cache[uint64, *netmapSDK.NetMap]
|
||||
|
||||
netRdr netValueReader[uint64, *netmapSDK.NetMap]
|
||||
}
|
||||
|
||||
// newNetworkLRUCache returns wrapper over netValueReader with LRU cache.
|
||||
func newNetworkLRUCache(sz int, netRdr netValueReader[uint64, *netmapSDK.NetMap]) *lruNetCache {
|
||||
cache, err := lru.New[uint64, *netmapSDK.NetMap](sz)
|
||||
fatalOnErr(err)
|
||||
|
||||
return &lruNetCache{
|
||||
cache: cache,
|
||||
netRdr: netRdr,
|
||||
}
|
||||
}
|
||||
|
||||
// reads value by the key.
|
||||
//
|
||||
// updates the value from the network on cache miss.
|
||||
//
|
||||
// returned value should not be modified.
|
||||
func (c *lruNetCache) get(key uint64) (*netmapSDK.NetMap, error) {
|
||||
val, ok := c.cache.Get(key)
|
||||
if ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
val, err := c.netRdr(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.cache.Add(key, val)
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// wrapper over TTL cache of values read from the network
|
||||
// that implements container storage.
|
||||
type ttlContainerStorage struct {
|
||||
containerCache *ttlNetCache[cid.ID, *container.Container]
|
||||
delInfoCache *ttlNetCache[cid.ID, *container.DelInfo]
|
||||
}
|
||||
|
||||
func newCachedContainerStorage(v container.Source, ttl time.Duration) ttlContainerStorage {
|
||||
const containerCacheSize = 100
|
||||
|
||||
lruCnrCache := newNetworkTTLCache(containerCacheSize, ttl, func(id cid.ID) (*container.Container, error) {
|
||||
return v.Get(id)
|
||||
})
|
||||
lruDelInfoCache := newNetworkTTLCache(containerCacheSize, ttl, func(id cid.ID) (*container.DelInfo, error) {
|
||||
return v.DeletionInfo(id)
|
||||
})
|
||||
|
||||
return ttlContainerStorage{
|
||||
containerCache: lruCnrCache,
|
||||
delInfoCache: lruDelInfoCache,
|
||||
}
|
||||
}
|
||||
|
||||
func (s ttlContainerStorage) handleRemoval(cnr cid.ID) {
|
||||
s.containerCache.set(cnr, nil, new(apistatus.ContainerNotFound))
|
||||
|
||||
// The removal invalidates possibly stored error response.
|
||||
s.delInfoCache.remove(cnr)
|
||||
}
|
||||
|
||||
// Get returns container value from the cache. If value is missing in the cache
|
||||
// or expired, then it returns value from side chain and updates the cache.
|
||||
func (s ttlContainerStorage) Get(cnr cid.ID) (*container.Container, error) {
|
||||
return s.containerCache.get(cnr)
|
||||
}
|
||||
|
||||
func (s ttlContainerStorage) DeletionInfo(cnr cid.ID) (*container.DelInfo, error) {
|
||||
return s.delInfoCache.get(cnr)
|
||||
}
|
||||
|
||||
type ttlEACLStorage struct {
|
||||
*ttlNetCache[cid.ID, *container.EACL]
|
||||
}
|
||||
|
||||
func newCachedEACLStorage(v container.EACLSource, ttl time.Duration) ttlEACLStorage {
|
||||
const eaclCacheSize = 100
|
||||
|
||||
lruCnrCache := newNetworkTTLCache(eaclCacheSize, ttl, func(id cid.ID) (*container.EACL, error) {
|
||||
return v.GetEACL(id)
|
||||
})
|
||||
|
||||
return ttlEACLStorage{lruCnrCache}
|
||||
}
|
||||
|
||||
// GetEACL returns eACL value from the cache. If value is missing in the cache
|
||||
// or expired, then it returns value from side chain and updates cache.
|
||||
func (s ttlEACLStorage) GetEACL(cnr cid.ID) (*container.EACL, error) {
|
||||
return s.get(cnr)
|
||||
}
|
||||
|
||||
// InvalidateEACL removes cached eACL value.
|
||||
func (s ttlEACLStorage) InvalidateEACL(cnr cid.ID) {
|
||||
s.remove(cnr)
|
||||
}
|
||||
|
||||
type lruNetmapSource struct {
|
||||
netState netmap.State
|
||||
|
||||
cache *lruNetCache
|
||||
}
|
||||
|
||||
func newCachedNetmapStorage(s netmap.State, v netmap.Source) netmap.Source {
|
||||
const netmapCacheSize = 10
|
||||
|
||||
lruNetmapCache := newNetworkLRUCache(netmapCacheSize, func(key uint64) (*netmapSDK.NetMap, error) {
|
||||
return v.GetNetMapByEpoch(key)
|
||||
})
|
||||
|
||||
return &lruNetmapSource{
|
||||
netState: s,
|
||||
cache: lruNetmapCache,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) GetNetMap(diff uint64) (*netmapSDK.NetMap, error) {
|
||||
return s.getNetMapByEpoch(s.netState.CurrentEpoch() - diff)
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) GetNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
return s.getNetMapByEpoch(epoch)
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) getNetMapByEpoch(epoch uint64) (*netmapSDK.NetMap, error) {
|
||||
val, err := s.cache.get(epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (s *lruNetmapSource) Epoch() (uint64, error) {
|
||||
return s.netState.CurrentEpoch(), nil
|
||||
}
|
||||
|
||||
type cachedIRFetcher struct {
|
||||
*ttlNetCache[struct{}, [][]byte]
|
||||
}
|
||||
|
||||
func newCachedIRFetcher(f interface{ InnerRingKeys() ([][]byte, error) }) cachedIRFetcher {
|
||||
const (
|
||||
irFetcherCacheSize = 1 // we intend to store only one value
|
||||
|
||||
// Without the cache in the testnet we can see several hundred simultaneous
|
||||
// requests (frostfs-node #1278), so limiting the request rate solves the issue.
|
||||
//
|
||||
// Exact request rate doesn't really matter because Inner Ring list update
|
||||
// happens extremely rare, but there is no side chain events for that as
|
||||
// for now (frostfs-contract v0.15.0 notary disabled env) to monitor it.
|
||||
irFetcherCacheTTL = 30 * time.Second
|
||||
)
|
||||
|
||||
irFetcherCache := newNetworkTTLCache(irFetcherCacheSize, irFetcherCacheTTL,
|
||||
func(_ struct{}) ([][]byte, error) {
|
||||
return f.InnerRingKeys()
|
||||
},
|
||||
)
|
||||
|
||||
return cachedIRFetcher{irFetcherCache}
|
||||
}
|
||||
|
||||
// InnerRingKeys returns cached list of Inner Ring keys. If keys are missing in
|
||||
// the cache or expired, then it returns keys from side chain and updates
|
||||
// the cache.
|
||||
func (f cachedIRFetcher) InnerRingKeys() ([][]byte, error) {
|
||||
val, err := f.get(struct{}{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
type ttlMaxObjectSizeCache struct {
|
||||
mtx sync.RWMutex
|
||||
lastUpdated time.Time
|
||||
lastSize uint64
|
||||
src putsvc.MaxSizeSource
|
||||
}
|
||||
|
||||
func newCachedMaxObjectSizeSource(src putsvc.MaxSizeSource) putsvc.MaxSizeSource {
|
||||
return &ttlMaxObjectSizeCache{
|
||||
src: src,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ttlMaxObjectSizeCache) MaxObjectSize() uint64 {
|
||||
const ttl = time.Second * 30
|
||||
|
||||
c.mtx.RLock()
|
||||
prevUpdated := c.lastUpdated
|
||||
size := c.lastSize
|
||||
c.mtx.RUnlock()
|
||||
|
||||
if time.Since(prevUpdated) < ttl {
|
||||
return size
|
||||
}
|
||||
|
||||
c.mtx.Lock()
|
||||
size = c.lastSize
|
||||
if !c.lastUpdated.After(prevUpdated) {
|
||||
size = c.src.MaxObjectSize()
|
||||
c.lastSize = size
|
||||
c.lastUpdated = time.Now()
|
||||
}
|
||||
c.mtx.Unlock()
|
||||
|
||||
return size
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTTLNetCache(t *testing.T) {
|
||||
ttlDuration := time.Millisecond * 50
|
||||
cache := newNetworkTTLCache[string, time.Time](10, ttlDuration, testNetValueReader)
|
||||
|
||||
key := "key"
|
||||
|
||||
t.Run("Test Add and Get", func(t *testing.T) {
|
||||
ti := time.Now()
|
||||
cache.set(key, ti, nil)
|
||||
val, err := cache.get(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ti, val)
|
||||
})
|
||||
|
||||
t.Run("Test TTL", func(t *testing.T) {
|
||||
ti := time.Now()
|
||||
cache.set(key, ti, nil)
|
||||
time.Sleep(2 * ttlDuration)
|
||||
val, err := cache.get(key)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, val, ti)
|
||||
})
|
||||
|
||||
t.Run("Test Remove", func(t *testing.T) {
|
||||
ti := time.Now()
|
||||
cache.set(key, ti, nil)
|
||||
cache.remove(key)
|
||||
val, err := cache.get(key)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, val, ti)
|
||||
})
|
||||
|
||||
t.Run("Test Cache Error", func(t *testing.T) {
|
||||
cache.set("error", time.Now(), errors.New("mock error"))
|
||||
_, err := cache.get("error")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "mock error", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func testNetValueReader(key string) (time.Time, error) {
|
||||
if key == "error" {
|
||||
return time.Now(), errors.New("mock error")
|
||||
}
|
||||
return time.Now(), nil
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package main
|
||||
|
||||
type closer struct {
|
||||
name string
|
||||
fn func()
|
||||
}
|
||||
|
||||
func getCloser(c *cfg, name string) *closer {
|
||||
for _, clsr := range c.closers {
|
||||
if clsr.name == name {
|
||||
return &clsr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delCloser(c *cfg, name string) {
|
||||
for i, clsr := range c.closers {
|
||||
if clsr.name == name {
|
||||
c.closers[i] = c.closers[len(c.closers)-1]
|
||||
c.closers = c.closers[:len(c.closers)-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue