Compare commits
85
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8124ee9eec | ||
|
|
ccbde3ff91 | ||
|
|
3a0455877a | ||
|
|
6c2c7f9d0f | ||
|
|
11618da189 | ||
|
|
9d2a859760 | ||
|
|
76042d9ea8 | ||
|
|
96473f15f9 | ||
|
|
7207d0fa75 | ||
|
|
0fb58e3c43 | ||
|
|
de7edfd31d | ||
|
|
e6c3f4c6f4 | ||
|
|
b4d172be35 | ||
|
|
a2debfca68 | ||
|
|
4c3c43f8ac | ||
|
|
6638889126 | ||
|
|
7fb5426ae9 | ||
|
|
b4af7409af | ||
|
|
645b1f3081 | ||
|
|
9a21a0ca65 | ||
|
|
28e4610d25 | ||
|
|
2ddefa539f | ||
|
|
3aa307f034 | ||
|
|
3ed7a0bc5e | ||
|
|
ab77f487e3 | ||
|
|
ba9be46acd | ||
|
|
04c7d61ee9 | ||
|
|
d0ae63eb50 | ||
|
|
4c34a47179 | ||
|
|
df16ab627a | ||
|
|
44c895dc30 | ||
|
|
fdea595913 | ||
|
|
ef4c91ecc8 | ||
|
|
5192c6cf50 | ||
|
|
cb639a130d | ||
|
|
892e3a8b6d | ||
|
|
0bd9c26620 | ||
|
|
ae2b1f7802 | ||
|
|
883b2e45da | ||
|
|
09df74cfac | ||
|
|
2ef3f012c9 | ||
|
|
7175d99c64 | ||
|
|
230a26772e | ||
|
|
637684c579 | ||
|
|
c541dd422f | ||
|
|
22c39561cf | ||
|
|
d4c67b4879 | ||
|
|
d1b7167861 | ||
|
|
33b561b73a | ||
|
|
a95a60c600 | ||
|
|
b3bc815354 | ||
|
|
42fc45d6da | ||
|
|
3fbe2e7f8a | ||
|
|
310c512b43 | ||
|
|
fd3e3b4ef0 | ||
|
|
93cf69f89f | ||
|
|
a8dd8bf06c | ||
|
|
7e23b57014 | ||
|
|
b2427836a1 | ||
|
|
2ed6760447 | ||
|
|
a1d215ea2f | ||
|
|
4fa442a5de | ||
|
|
3a057ed5af | ||
|
|
0ad042b28b | ||
|
|
4d75c6c8f9 | ||
|
|
9ec18f0aa1 | ||
|
|
facaa2cc6a | ||
|
|
d2ac8e3827 | ||
|
|
d6dacc67db | ||
|
|
cbe51595cb | ||
|
|
6bd0830ab8 | ||
|
|
796e82f34d | ||
|
|
db39283970 | ||
|
|
9f8c686321 | ||
|
|
2618e559d1 | ||
|
|
037d2bc162 | ||
|
|
f0b64adb32 | ||
|
|
f06d64d879 | ||
|
|
d164499a1c | ||
|
|
725e9aa8ae | ||
|
|
0715feffa1 | ||
|
|
e9f37e8dc3 | ||
|
|
4fbe4b5654 | ||
|
|
9fb645693b | ||
|
|
9e47828383 |
@@ -1,28 +1,29 @@
|
||||
FROM node:16-bullseye
|
||||
FROM node:16-bookworm
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
ADD ./docker/vitastor.gpg /etc/apt/trusted.gpg.d
|
||||
ADD ./docker/etc/apt/trusted.gpg.d /etc/apt/trusted.gpg.d
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian bullseye-backports main' >> /etc/apt/sources.list; \
|
||||
echo 'deb http://vitastor.io/debian bullseye main' >> /etc/apt/sources.list; \
|
||||
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list; \
|
||||
echo 'deb http://vitastor.io/debian bookworm main' >> /etc/apt/sources.list; \
|
||||
echo >> /etc/apt/preferences; \
|
||||
echo 'Package: *' >> /etc/apt/preferences; \
|
||||
echo 'Pin: release a=bullseye-backports' >> /etc/apt/preferences; \
|
||||
echo 'Pin: release n=bookworm-backports' >> /etc/apt/preferences; \
|
||||
echo 'Pin-Priority: 500' >> /etc/apt/preferences; \
|
||||
echo >> /etc/apt/preferences; \
|
||||
echo 'Package: *' >> /etc/apt/preferences; \
|
||||
echo 'Pin: origin "vitastor.io"' >> /etc/apt/preferences; \
|
||||
echo 'Pin-Priority: 1000' >> /etc/apt/preferences; \
|
||||
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/debian.sources; \
|
||||
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
|
||||
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
|
||||
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan5 \
|
||||
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan8 \
|
||||
libgoogle-perftools-dev devscripts libjerasure-dev cmake libibverbs-dev libisal-dev
|
||||
RUN apt-get -y build-dep fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
|
||||
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted
|
||||
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted libc-ares-dev udev
|
||||
RUN apt-get --download-only source fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
|
||||
|
||||
RUN set -ex; \
|
||||
|
||||
@@ -234,6 +234,60 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https_antietcd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_interrupted_rebalance:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -648,6 +702,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_snapshot_chain:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1206,6 +1278,96 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum_xxhash:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: OLD=1 /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_corrupt_all:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_corrupt_all.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_corrupt_all:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: OLD=1 /root/vitastor/tests/test_corrupt_all.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_reweight_half:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1980,3 +2142,39 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted_ec:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
|
||||
@@ -38,6 +38,14 @@ for my $line (<>)
|
||||
{
|
||||
$test_name .= '_antietcd';
|
||||
}
|
||||
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
|
||||
{
|
||||
$test_name .= '_https';
|
||||
}
|
||||
elsif ($1 eq 'ENCRYPTED')
|
||||
{
|
||||
$test_name .= '_encrypted';
|
||||
}
|
||||
elsif ($1 eq 'OLD')
|
||||
{
|
||||
$test_name =~ s/^test_/test_old_/s;
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
package-lock.json
|
||||
fio
|
||||
qemu
|
||||
node_modules
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.2")
|
||||
set(VITASTOR_VERSION "3.0.5")
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
|
||||
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](docs/config/osd.ru.md)
|
||||
- [Параметры мониторов](docs/config/monitor.ru.md)
|
||||
- [Безопасность](docs/config/security.ru.md)
|
||||
- [Настройки пулов](docs/config/pool.ru.md)
|
||||
- [Метаданные образов в etcd](docs/config/inode.ru.md)
|
||||
- Использование
|
||||
|
||||
@@ -62,6 +62,7 @@ Read more details in the documentation. You can start from here: [Quick Start](d
|
||||
- [OSD Disk Layout](docs/config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](docs/config/osd.en.md)
|
||||
- [Monitor](docs/config/monitor.en.md)
|
||||
- [Security](docs/config/security.en.md)
|
||||
- [Pool configuration](docs/config/pool.en.md)
|
||||
- [Image metadata in etcd](docs/config/inode.en.md)
|
||||
- Usage
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
# Compile stage
|
||||
FROM golang:bookworm AS build
|
||||
FROM golang:trixie AS build
|
||||
|
||||
ADD go.sum go.mod /app/
|
||||
RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go mod download -x
|
||||
@@ -9,7 +9,7 @@ RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)e
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi
|
||||
|
||||
# Final stage
|
||||
FROM debian:bookworm
|
||||
FROM debian:trixie
|
||||
|
||||
LABEL maintainers="Vitaliy Filippov <vitalif@yourcmc.ru>"
|
||||
LABEL description="Vitastor CSI Driver"
|
||||
@@ -25,20 +25,20 @@ RUN apt-get update && \
|
||||
# NFS mount dependencies
|
||||
nfs-common netbase \
|
||||
# dependencies of qemu-storage-daemon
|
||||
libnuma1 liburing2 libglib2.0-0 libfuse3-3 libaio1 libzstd1 libnettle8 \
|
||||
libgmp10 libhogweed6 libp11-kit0 libidn2-0 libunistring2 libtasn1-6 libpcre2-8-0 libffi8 && \
|
||||
libaio1t64 libc6 libfuse3-4 libglib2.0-0t64 libgmp10 libgnutls30t64 \
|
||||
libhogweed6t64 libnettle8t64 libnuma1 libselinux1 liburing2 libzstd1 zlib1g && \
|
||||
apt-get clean && \
|
||||
(echo options nbd nbds_max=128 > /etc/modprobe.d/nbd.conf)
|
||||
|
||||
COPY --from=build /app/vitastor-csi /bin/
|
||||
|
||||
RUN (echo deb http://vitastor.io/debian bookworm main > /etc/apt/sources.list.d/vitastor.list) && \
|
||||
RUN (echo deb http://vitastor.io/debian trixie main > /etc/apt/sources.list.d/vitastor.list) && \
|
||||
((echo 'Package: *'; echo 'Pin: origin "vitastor.io"'; echo 'Pin-Priority: 1000') > /etc/apt/preferences.d/vitastor.pref) && \
|
||||
wget -q -O /etc/apt/trusted.gpg.d/vitastor.gpg https://vitastor.io/debian/pubkey.gpg && \
|
||||
apt-get update && \
|
||||
apt-get install -y vitastor-client ibverbs-providers && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.2%2Bds-2%2Bvitastor1/qemu-utils_10.0.2%2Bds-2%2Bvitastor1_amd64.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.2%2Bds-2%2Bvitastor1/qemu-block-extra_10.0.2%2Bds-2%2Bvitastor1_amd64.deb && \
|
||||
dpkg -x qemu-utils*.deb tmp1 && \
|
||||
dpkg -x qemu-block-extra*.deb tmp1 && \
|
||||
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Compile stage
|
||||
FROM golang:bookworm AS build
|
||||
FROM golang:trixie AS build
|
||||
|
||||
ADD go.sum go.mod /app/
|
||||
RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go mod download -x
|
||||
@@ -9,7 +9,7 @@ RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)e
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi
|
||||
|
||||
# Final stage
|
||||
FROM debian:bookworm
|
||||
FROM debian:trixie
|
||||
|
||||
LABEL maintainers="Vitaliy Filippov <vitalif@yourcmc.ru>"
|
||||
LABEL description="Vitastor CSI Driver"
|
||||
@@ -36,8 +36,8 @@ ADD deb /deb
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install /deb/vitastor-client_*.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
|
||||
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
|
||||
dpkg -x qemu-utils*.deb tmp1 && \
|
||||
dpkg -x qemu-block-extra*.deb tmp1 && \
|
||||
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.2
|
||||
VITASTOR_VERSION ?= v3.0.5
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.2
|
||||
image: vitalif/vitastor-csi:v3.0.5
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.2
|
||||
image: vitalif/vitastor-csi:v3.0.5
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.2"
|
||||
vitastorCSIDriverVersion = "3.0.5"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# 26.04 Resolute Raccoon
|
||||
|
||||
docker build --build-arg DISTRO=ubuntu --build-arg REL=resolute -t vitastor-buildenv:resolute -f vitastor-buildenv.Dockerfile .
|
||||
docker run -it --rm -e REL=resolute -v `dirname $0`/../:/root/vitastor vitastor-buildenv:resolute /root/vitastor/debian/vitastor-build.sh
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.2-1) unstable; urgency=medium
|
||||
vitastor (3.0.5-1) unstable; urgency=medium
|
||||
|
||||
* Bugfixes
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Vitaliy Filippov <vitalif@yourcmc.ru>
|
||||
Build-Depends: debhelper, g++ (>= 8), libstdc++6 (>= 8),
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev, libc-ares-dev,
|
||||
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
|
||||
node-bindings <!nocheck>, node-gyp, node-nan
|
||||
Standards-Version: 4.5.0
|
||||
|
||||
Vendored
+1
-1
@@ -25,7 +25,7 @@ RUN set -e -x; \
|
||||
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev \
|
||||
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
|
||||
apt-get -y build-dep fio && \
|
||||
apt-get --download-only source fio
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Build Docker image with Vitastor packages
|
||||
|
||||
FROM debian:bookworm
|
||||
FROM debian:trixie
|
||||
|
||||
ADD etc/apt /etc/apt/
|
||||
RUN apt-get update && apt-get -y install vitastor ibverbs-providers udev systemd qemu-system-x86 qemu-system-common qemu-block-extra qemu-utils jq nfs-common && apt-get clean
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.2
|
||||
VITASTOR_VERSION ?= v3.0.5
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
Package: *
|
||||
Pin: release n=bookworm-backports
|
||||
Pin: release n=trixie-backports
|
||||
Pin-Priority: 500
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
deb http://vitastor.io/debian bookworm main
|
||||
deb http://http.debian.net/debian/ bookworm-backports main
|
||||
deb http://vitastor.io/debian trixie main
|
||||
#deb http://http.debian.net/debian/ trixie-backports main
|
||||
|
||||
@@ -7,7 +7,7 @@ PartOf=vitastor.target
|
||||
[Service]
|
||||
Restart=always
|
||||
EnvironmentFile=/etc/vitastor/docker.conf
|
||||
ExecStart=bash -c 'docker run --rm -i -v /etc/vitastor:/etc/vitastor -v /dev:/dev -v /run:/run \
|
||||
ExecStart=bash -c 'docker run --rm -i -v /etc/vitastor:/etc/vitastor -v /dev:/dev -v /run:/run -e SYSTEMD_IN_CHROOT=0 \
|
||||
--security-opt seccomp=unconfined --privileged --pid=host --log-driver none --network host --name vitastor vitastor:$VITASTOR_VERSION \
|
||||
sleep.sh'
|
||||
ExecStartPost=udevadm trigger
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.2
|
||||
VITASTOR_VERSION=v3.0.5
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
+2
-3
@@ -2,8 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
cp -urv /etc/default /host-etc/
|
||||
cp -urv /etc/systemd /host-etc/
|
||||
cp -urv /etc/udev /host-etc/
|
||||
cp -urv /etc/systemd/system/vitastor* /host-etc/systemd/system/
|
||||
cp -urv /etc/udev/rules.d /host-etc/udev/
|
||||
cp -urnv /etc/vitastor /host-etc/
|
||||
cp -urnv /opt/scripts/* /host-bin/
|
||||
|
||||
@@ -38,3 +38,4 @@ In the future, additional configuration methods may be added:
|
||||
- [OSD Disk Layout](config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](config/osd.en.md)
|
||||
- [Monitor](config/monitor.en.md)
|
||||
- [Security Parameters](config/security.en.md)
|
||||
|
||||
@@ -41,3 +41,4 @@
|
||||
- [Дисковые параметры OSD](config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](config/osd.ru.md)
|
||||
- [Параметры мониторов](config/monitor.ru.md)
|
||||
- [Параметры безопасности](config/security.ru.md)
|
||||
|
||||
@@ -198,8 +198,14 @@ put a modified value into etcd key /vitastor/config/global.
|
||||
- Type: string
|
||||
- Default: none
|
||||
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
|
||||
## csum_block_size
|
||||
|
||||
|
||||
@@ -209,8 +209,12 @@ journal_block_size и meta_block_size. Однако на данный момен
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: none
|
||||
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
+17
-28
@@ -22,7 +22,6 @@ between clients, OSDs and etcd.
|
||||
- [rdma_max_msg](#rdma_max_msg)
|
||||
- [rdma_max_recv](#rdma_max_recv)
|
||||
- [rdma_max_send](#rdma_max_send)
|
||||
- [rdma_odp](#rdma_odp)
|
||||
- [peer_connect_interval](#peer_connect_interval)
|
||||
- [peer_connect_timeout](#peer_connect_timeout)
|
||||
- [osd_idle_timeout](#osd_idle_timeout)
|
||||
@@ -102,11 +101,6 @@ found or if `osd_network` is not specified. Auto-selection is also
|
||||
unsupported with old libibverbs < v32, like in Debian 10 Buster or
|
||||
CentOS 7.
|
||||
|
||||
Vitastor supports all adapters, even ones without ODP support, like
|
||||
Mellanox ConnectX-3 and non-Mellanox cards. Versions up to Vitastor
|
||||
1.2.0 required ODP which is only present in Mellanox ConnectX >= 4.
|
||||
See also [rdma_odp](#rdma_odp).
|
||||
|
||||
Run `ibv_devinfo -v` as root to list available RDMA devices and their
|
||||
features.
|
||||
|
||||
@@ -116,6 +110,23 @@ the manual of your network vendor for details about setting up the switch
|
||||
for RoCEv2 correctly. Usually it means setting up Lossless Ethernet with
|
||||
PFC (Priority Flow Control) and ECN (Explicit Congestion Notification).
|
||||
|
||||
Vitastor supports all adapters, even ones without ODP (On-Demand Paging)
|
||||
support, like Mellanox ConnectX-3 and non-Mellanox cards. ODP is only present
|
||||
in Mellanox ConnectX >= 4 adapters and allows to skip memory registration
|
||||
for RDMA and thus, in theory, avoid memory copying.
|
||||
|
||||
Versions up to Vitastor 1.2.0 required ODP, then it was disabled by default,
|
||||
but it was still supported up to 3.0.3. Now ODP support is removed because it
|
||||
actually only hurts performance: an example 3-node cluster with 8 NVMe in each
|
||||
node and 2*25 GBit/s ConnectX-6 RDMA network pushed 3950000 read iops without
|
||||
ODP, but only 239000 iops with ODP.
|
||||
|
||||
This happens because Mellanox ODP implementation seems to be based on
|
||||
message retransmissions when the adapter doesn't know about the buffer yet -
|
||||
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
|
||||
which is generally slow in RDMA/RoCE networks. Here's a presentation about
|
||||
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
|
||||
## rdma_port_num
|
||||
|
||||
- Type: integer
|
||||
@@ -187,28 +198,6 @@ less than `rdma_max_recv` so the receiving side doesn't run out of buffers.
|
||||
Doesn't affect memory usage - additional memory isn't allocated for send
|
||||
operations.
|
||||
|
||||
## rdma_odp
|
||||
|
||||
- Type: boolean
|
||||
- Default: false
|
||||
|
||||
Use RDMA with On-Demand Paging. ODP is currently only available on Mellanox
|
||||
ConnectX-4 and newer adapters. ODP allows to not register memory explicitly
|
||||
for RDMA adapter to be able to use it. This, in turn, allows to skip memory
|
||||
copying during sending. One would think this should improve performance, but
|
||||
**in reality** RDMA performance with ODP is **drastically** worse. Example
|
||||
3-node cluster with 8 NVMe in each node and 2*25 GBit/s ConnectX-6 RDMA network
|
||||
without ODP pushes 3950000 read iops, but only 239000 iops with ODP...
|
||||
|
||||
This happens because Mellanox ODP implementation seems to be based on
|
||||
message retransmissions when the adapter doesn't know about the buffer yet -
|
||||
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
|
||||
which is generally slow in RDMA/RoCE networks. Here's a presentation about
|
||||
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
|
||||
ODP support is retained in the code just in case a good ODP implementation
|
||||
appears one day.
|
||||
|
||||
## peer_connect_interval
|
||||
|
||||
- Type: seconds
|
||||
|
||||
+18
-30
@@ -22,7 +22,6 @@
|
||||
- [rdma_max_msg](#rdma_max_msg)
|
||||
- [rdma_max_recv](#rdma_max_recv)
|
||||
- [rdma_max_send](#rdma_max_send)
|
||||
- [rdma_odp](#rdma_odp)
|
||||
- [peer_connect_interval](#peer_connect_interval)
|
||||
- [peer_connect_timeout](#peer_connect_timeout)
|
||||
- [osd_idle_timeout](#osd_idle_timeout)
|
||||
@@ -101,12 +100,6 @@ RoCEv1/RoCEv2, и даже позволяет полностью отключи
|
||||
не задана. Также автовыбор не поддерживается со старыми версиями библиотеки
|
||||
libibverbs < v32, например в Debian 10 Buster или CentOS 7.
|
||||
|
||||
Vitastor поддерживает все модели адаптеров, включая те, у которых
|
||||
нет поддержки ODP, то есть вы можете использовать RDMA с ConnectX-3 и
|
||||
картами производства не Mellanox. Версии Vitastor до 1.2.0 включительно
|
||||
требовали ODP, который есть только на Mellanox ConnectX 4 и более новых.
|
||||
См. также [rdma_odp](#rdma_odp).
|
||||
|
||||
Запустите `ibv_devinfo -v` от имени суперпользователя, чтобы посмотреть
|
||||
список доступных RDMA-устройств, их параметры и возможности.
|
||||
|
||||
@@ -117,6 +110,24 @@ Vitastor поддерживает все модели адаптеров, вкл
|
||||
подразумевает настройку сети без потерь на основе PFC (Priority Flow
|
||||
Control) и ECN (Explicit Congestion Notification).
|
||||
|
||||
Vitastor поддерживает все модели адаптеров, включая те, у которых нет
|
||||
поддержки ODP (On-Demand Paging), например, ConnectX-3 и карты производства
|
||||
не Mellanox. Функция ODP доступна только на адаптерах Mellanox ConnectX-4 и
|
||||
более новых и позволяет не регистрировать память для её использования RDMA-картой,
|
||||
благодаря чему в теории можно избежать лишних копирований памяти.
|
||||
|
||||
Версии Vitastor до 1.2.0 включительно требовали ODP, потом функция был отключена
|
||||
по умолчанию, но поддерживалась вплоть до версии 3.0.3. Сейчас поддержка ODP
|
||||
полностью удалена, так как на самом деле она только портит производительность:
|
||||
например, на 3-узловом кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на
|
||||
чтение с RDMA без ODP удаётся снять 3950000 iops, а с ODP - всего 239000 iops.
|
||||
|
||||
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
|
||||
основана на повторной передаче сообщений, когда карте не известен буфер -
|
||||
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
|
||||
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
|
||||
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
|
||||
## rdma_port_num
|
||||
|
||||
- Тип: целое число
|
||||
@@ -192,29 +203,6 @@ OSD в любом случае согласовывают реальное зн
|
||||
Не влияет на потребление памяти - дополнительная память на операции отправки
|
||||
не выделяется.
|
||||
|
||||
## rdma_odp
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
- Значение по умолчанию: false
|
||||
|
||||
Использовать RDMA с On-Demand Paging. ODP - функция, доступная пока что
|
||||
исключительно на адаптерах Mellanox ConnectX-4 и более новых. ODP позволяет
|
||||
не регистрировать память для её использования RDMA-картой. Благодаря этому
|
||||
можно не копировать данные при отправке их в сеть и, казалось бы, это должно
|
||||
улучшать производительность - но **по факту** получается так, что
|
||||
производительность только ухудшается, причём сильно. Пример - на 3-узловом
|
||||
кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на чтение с RDMA без ODP
|
||||
удаётся снять 3950000 iops, а с ODP - всего 239000 iops...
|
||||
|
||||
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
|
||||
основана на повторной передаче сообщений, когда карте не известен буфер -
|
||||
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
|
||||
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
|
||||
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
|
||||
Возможность использования ODP сохранена в коде на случай, если вдруг в один
|
||||
прекрасный день появится хорошая реализация ODP.
|
||||
|
||||
## peer_connect_interval
|
||||
|
||||
- Тип: секунды
|
||||
|
||||
+27
-7
@@ -38,6 +38,7 @@ with an OSD restart or, for some of them, even without restarting by updating co
|
||||
- [journal_io](#journal_io)
|
||||
- [journal_sector_buffer_count](#journal_sector_buffer_count)
|
||||
- [journal_no_same_sector_overwrites](#journal_no_same_sector_overwrites)
|
||||
- [skip_corrupted_meta_entries](#skip_corrupted_meta_entries)
|
||||
- [throttle_small_writes](#throttle_small_writes)
|
||||
- [throttle_target_iops](#throttle_target_iops)
|
||||
- [throttle_target_mbs](#throttle_target_mbs)
|
||||
@@ -279,13 +280,19 @@ Maximum number of journal flushers (see above min_flusher_count).
|
||||
- Type: boolean
|
||||
- Default: true
|
||||
|
||||
This parameter makes Vitastor always keep metadata area of the block device
|
||||
in memory. It's required for good performance because it allows to avoid
|
||||
additional read-modify-write cycles during metadata modifications. Metadata
|
||||
area size is currently roughly 224 MB per 1 TB of data. You can turn it off
|
||||
to reduce memory usage by this value, but it will hurt performance. This
|
||||
restriction is likely to be removed in the future along with the upgrade
|
||||
of the metadata storage scheme.
|
||||
Only for the old store ([meta_format](layout-osd.en.md#meta_format) 2).
|
||||
|
||||
This parameter makes Vitastor keep a copy of metadata area in memory as it is
|
||||
on disk, in addition to the metadata database. When the option is enabled, every
|
||||
metadata entry is effectively stored in RAM twice. It's required for good performance
|
||||
because it allows to avoid additional read-modify-write cycles during metadata
|
||||
modifications. Metadata area size with the old store is roughly 224 MB per 1 TB
|
||||
of data. You can turn the option off to reduce memory usage by this value, but
|
||||
it will reduce performance.
|
||||
|
||||
For the new store ([meta_format](layout-osd.en.md#meta_format) 3), the option
|
||||
may be changed in the future to support operation without loading full metadata
|
||||
database in memory.
|
||||
|
||||
## inmemory_journal
|
||||
|
||||
@@ -364,6 +371,8 @@ blocks. The only situation when you should increase it to a larger value
|
||||
is when you enable journal_no_same_sector_overwrites. In this case set
|
||||
it to, for example, 1024.
|
||||
|
||||
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
|
||||
## journal_no_same_sector_overwrites
|
||||
|
||||
- Type: boolean
|
||||
@@ -377,6 +386,17 @@ journal after writing it instead of possibly overwriting it the second time.
|
||||
|
||||
Most (99%) other SSDs don't need this option.
|
||||
|
||||
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
|
||||
## skip_corrupted_meta_entries
|
||||
|
||||
- Type: boolean
|
||||
- Default: false
|
||||
|
||||
Only for the new store ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
Allow OSD to start when some metadata entries or blocks are corrupted by
|
||||
skipping them. Should be only used as an emergency measure.
|
||||
|
||||
## throttle_small_writes
|
||||
|
||||
- Type: boolean
|
||||
|
||||
+28
-7
@@ -39,6 +39,7 @@
|
||||
- [journal_io](#journal_io)
|
||||
- [journal_sector_buffer_count](#journal_sector_buffer_count)
|
||||
- [journal_no_same_sector_overwrites](#journal_no_same_sector_overwrites)
|
||||
- [skip_corrupted_meta_entries](#skip_corrupted_meta_entries)
|
||||
- [throttle_small_writes](#throttle_small_writes)
|
||||
- [throttle_target_iops](#throttle_target_iops)
|
||||
- [throttle_target_mbs](#throttle_target_mbs)
|
||||
@@ -287,13 +288,19 @@ Flusher - это микро-поток (корутина), которая коп
|
||||
- Тип: булево (да/нет)
|
||||
- Значение по умолчанию: true
|
||||
|
||||
Данный параметр заставляет Vitastor всегда держать область метаданных диска
|
||||
в памяти. Это нужно, чтобы избегать дополнительных операций чтения с диска
|
||||
при записи. Размер области метаданных на данный момент составляет примерно
|
||||
224 МБ на 1 ТБ данных. При включении потребление памяти снизится примерно
|
||||
на эту величину, но при этом также снизится и производительность. В будущем,
|
||||
после обновления схемы хранения метаданных, это ограничение, скорее всего,
|
||||
будет ликвидировано.
|
||||
Только для старого хранилища ([meta_format](layout-osd.en.md#meta_format) 2).
|
||||
|
||||
Данный параметр заставляет Vitastor всегда держать копию области метаданных
|
||||
в памяти в том же виде, как она лежит на диске, в дополнение к БД метаданных.
|
||||
То есть, с включённой опцией каждая запись метаданных хранится в памяти дважды.
|
||||
Это нужно, чтобы избегать дополнительных операций чтения с диска при записи.
|
||||
Размер области метаданных в старом хранилище составляет примерно 224 МБ на
|
||||
1 ТБ данных. Вы можете отключить опцию, чтобы снизить потребление памяти
|
||||
примерно на эту величину, но при этом также снизится и производительность.
|
||||
|
||||
Для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3) опция,
|
||||
возможно, будет переработана в будущем для поддержки работы без полной
|
||||
загрузки метаданных в памяти.
|
||||
|
||||
## inmemory_journal
|
||||
|
||||
@@ -376,6 +383,8 @@ fsync небезопасным даже с режимом "directsync".
|
||||
нужно менять - это если вы включаете journal_no_same_sector_overwrites. В
|
||||
этом случае установите данный параметр, например, в 1024.
|
||||
|
||||
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
|
||||
## journal_no_same_sector_overwrites
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
@@ -391,6 +400,18 @@ fsync небезопасным даже с режимом "directsync".
|
||||
|
||||
Почти все другие SSD (99% моделей) не требуют данной опции.
|
||||
|
||||
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
|
||||
## skip_corrupted_meta_entries
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
- Значение по умолчанию: false
|
||||
|
||||
Только для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
Разрешить OSD запускаться, даже если часть блоков или записей метаданных
|
||||
повреждена, пропуская их. Опция предназначена для использования только в
|
||||
целях аварийного восстановления.
|
||||
|
||||
## throttle_small_writes
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
[Documentation](../../README.md#documentation) → [Configuration](../config.en.md) → Security Parameters
|
||||
|
||||
-----
|
||||
|
||||
[Читать на русском](security.ru.md)
|
||||
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
|
||||
- [etcd_client_cert](#etcd_client_cert)
|
||||
- [etcd_client_key](#etcd_client_key)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [osd_etcd_client_cert](#osd_etcd_client_cert)
|
||||
- [osd_etcd_client_key](#osd_etcd_client_key)
|
||||
- [mon_etcd_client_cert](#mon_etcd_client_cert)
|
||||
- [mon_etcd_client_key](#mon_etcd_client_key)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
- [vault_client_key](#vault_client_key)
|
||||
- [vault_ca](#vault_ca)
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
|
||||
etcd https connections. May be path to a file or just a PEM string with certificate.
|
||||
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for etcd_client_cert (also a file or a PEM string).
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify etcd server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
|
||||
OSDs, clients and monitors should have different permissions, so they should
|
||||
use different certificates.
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
|
||||
|
||||
## vault_url
|
||||
|
||||
- Type: string
|
||||
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
|
||||
## vault_secret_api_path
|
||||
|
||||
- Type: string
|
||||
- Default: /v1/secret/
|
||||
|
||||
Vault v1 secret API mount path to use.
|
||||
|
||||
## vault_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
|
||||
may be path to a file or just a certificate in PEM string.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for vault_client_cert (also a file or a PEM string).
|
||||
|
||||
## vault_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
|
||||
## vault_timeout_ms
|
||||
|
||||
- Type: integer
|
||||
- Default: 5000
|
||||
|
||||
Timeout for Vault requests in milliseconds.
|
||||
|
||||
## vault_error_timeout_sec
|
||||
|
||||
- Type: integer
|
||||
- Default: 60
|
||||
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
|
||||
## vault_refresh_leeway_sec
|
||||
|
||||
- Type: integer
|
||||
- Default: 60
|
||||
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
|
||||
## max_aes_xts_pool_size
|
||||
|
||||
- Type: integer
|
||||
- Default: 256
|
||||
|
||||
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
|
||||
doesn't require modification.
|
||||
@@ -0,0 +1,154 @@
|
||||
[Документация](../../README-ru.md#документация) → [Конфигурация](../config.ru.md) → Параметры безопасности
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
|
||||
- [etcd_client_cert](#etcd_client_cert)
|
||||
- [etcd_client_key](#etcd_client_key)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [osd_etcd_client_cert](#osd_etcd_client_cert)
|
||||
- [osd_etcd_client_key](#osd_etcd_client_key)
|
||||
- [mon_etcd_client_cert](#mon_etcd_client_cert)
|
||||
- [mon_etcd_client_key](#mon_etcd_client_key)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
- [vault_client_key](#vault_client_key)
|
||||
- [vault_ca](#vault_ca)
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
|
||||
|
||||
## vault_url
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
|
||||
## vault_secret_api_path
|
||||
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: /v1/secret/
|
||||
|
||||
Путь к API секретов v1 для использования клиентами.
|
||||
|
||||
## vault_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
|
||||
|
||||
## vault_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
|
||||
## vault_timeout_ms
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 5000
|
||||
|
||||
Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
|
||||
## vault_error_timeout_sec
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 60
|
||||
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
|
||||
## vault_refresh_leeway_sec
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 60
|
||||
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
|
||||
## max_aes_xts_pool_size
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 256
|
||||
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
|
||||
Вряд ли требует изменения.
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.en.md|indent=2}}
|
||||
|
||||
{{../../config/security.en.md|indent=2}}
|
||||
|
||||
{{../../config/pool.en.md|indent=2}}
|
||||
|
||||
{{../../config/inode.en.md|indent=2}}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.ru.md|indent=2}}
|
||||
|
||||
{{../../config/security.ru.md|indent=2}}
|
||||
|
||||
{{../../config/pool.ru.md|indent=2}}
|
||||
|
||||
{{../../config/inode.ru.md|indent=2}}
|
||||
|
||||
@@ -233,11 +233,21 @@
|
||||
type: string
|
||||
default: none
|
||||
info: |
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
info_ru: |
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
+35
-50
@@ -84,11 +84,6 @@
|
||||
unsupported with old libibverbs < v32, like in Debian 10 Buster or
|
||||
CentOS 7.
|
||||
|
||||
Vitastor supports all adapters, even ones without ODP support, like
|
||||
Mellanox ConnectX-3 and non-Mellanox cards. Versions up to Vitastor
|
||||
1.2.0 required ODP which is only present in Mellanox ConnectX >= 4.
|
||||
See also [rdma_odp](#rdma_odp).
|
||||
|
||||
Run `ibv_devinfo -v` as root to list available RDMA devices and their
|
||||
features.
|
||||
|
||||
@@ -97,6 +92,23 @@
|
||||
the manual of your network vendor for details about setting up the switch
|
||||
for RoCEv2 correctly. Usually it means setting up Lossless Ethernet with
|
||||
PFC (Priority Flow Control) and ECN (Explicit Congestion Notification).
|
||||
|
||||
Vitastor supports all adapters, even ones without ODP (On-Demand Paging)
|
||||
support, like Mellanox ConnectX-3 and non-Mellanox cards. ODP is only present
|
||||
in Mellanox ConnectX >= 4 adapters and allows to skip memory registration
|
||||
for RDMA and thus, in theory, avoid memory copying.
|
||||
|
||||
Versions up to Vitastor 1.2.0 required ODP, then it was disabled by default,
|
||||
but it was still supported up to 3.0.3. Now ODP support is removed because it
|
||||
actually only hurts performance: an example 3-node cluster with 8 NVMe in each
|
||||
node and 2*25 GBit/s ConnectX-6 RDMA network pushed 3950000 read iops without
|
||||
ODP, but only 239000 iops with ODP.
|
||||
|
||||
This happens because Mellanox ODP implementation seems to be based on
|
||||
message retransmissions when the adapter doesn't know about the buffer yet -
|
||||
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
|
||||
which is generally slow in RDMA/RoCE networks. Here's a presentation about
|
||||
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
info_ru: |
|
||||
Название RDMA-устройства для связи с Vitastor OSD (например, "rocep5s0f0").
|
||||
Если не указано, Vitastor попробует найти RoCE-устройство, соответствующее
|
||||
@@ -105,12 +117,6 @@
|
||||
не задана. Также автовыбор не поддерживается со старыми версиями библиотеки
|
||||
libibverbs < v32, например в Debian 10 Buster или CentOS 7.
|
||||
|
||||
Vitastor поддерживает все модели адаптеров, включая те, у которых
|
||||
нет поддержки ODP, то есть вы можете использовать RDMA с ConnectX-3 и
|
||||
картами производства не Mellanox. Версии Vitastor до 1.2.0 включительно
|
||||
требовали ODP, который есть только на Mellanox ConnectX 4 и более новых.
|
||||
См. также [rdma_odp](#rdma_odp).
|
||||
|
||||
Запустите `ibv_devinfo -v` от имени суперпользователя, чтобы посмотреть
|
||||
список доступных RDMA-устройств, их параметры и возможности.
|
||||
|
||||
@@ -120,6 +126,24 @@
|
||||
коммутатора для RoCEv2 ищите в документации производителя. Обычно это
|
||||
подразумевает настройку сети без потерь на основе PFC (Priority Flow
|
||||
Control) и ECN (Explicit Congestion Notification).
|
||||
|
||||
Vitastor поддерживает все модели адаптеров, включая те, у которых нет
|
||||
поддержки ODP (On-Demand Paging), например, ConnectX-3 и карты производства
|
||||
не Mellanox. Функция ODP доступна только на адаптерах Mellanox ConnectX-4 и
|
||||
более новых и позволяет не регистрировать память для её использования RDMA-картой,
|
||||
благодаря чему в теории можно избежать лишних копирований памяти.
|
||||
|
||||
Версии Vitastor до 1.2.0 включительно требовали ODP, потом функция был отключена
|
||||
по умолчанию, но поддерживалась вплоть до версии 3.0.3. Сейчас поддержка ODP
|
||||
полностью удалена, так как на самом деле она только портит производительность:
|
||||
например, на 3-узловом кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на
|
||||
чтение с RDMA без ODP удаётся снять 3950000 iops, а с ODP - всего 239000 iops.
|
||||
|
||||
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
|
||||
основана на повторной передаче сообщений, когда карте не известен буфер -
|
||||
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
|
||||
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
|
||||
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
- name: rdma_port_num
|
||||
type: int
|
||||
info: |
|
||||
@@ -218,45 +242,6 @@
|
||||
у принимающей стороны в процессе работы не заканчивались буферы на приём.
|
||||
Не влияет на потребление памяти - дополнительная память на операции отправки
|
||||
не выделяется.
|
||||
- name: rdma_odp
|
||||
type: bool
|
||||
default: false
|
||||
online: false
|
||||
info: |
|
||||
Use RDMA with On-Demand Paging. ODP is currently only available on Mellanox
|
||||
ConnectX-4 and newer adapters. ODP allows to not register memory explicitly
|
||||
for RDMA adapter to be able to use it. This, in turn, allows to skip memory
|
||||
copying during sending. One would think this should improve performance, but
|
||||
**in reality** RDMA performance with ODP is **drastically** worse. Example
|
||||
3-node cluster with 8 NVMe in each node and 2*25 GBit/s ConnectX-6 RDMA network
|
||||
without ODP pushes 3950000 read iops, but only 239000 iops with ODP...
|
||||
|
||||
This happens because Mellanox ODP implementation seems to be based on
|
||||
message retransmissions when the adapter doesn't know about the buffer yet -
|
||||
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
|
||||
which is generally slow in RDMA/RoCE networks. Here's a presentation about
|
||||
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
|
||||
ODP support is retained in the code just in case a good ODP implementation
|
||||
appears one day.
|
||||
info_ru: |
|
||||
Использовать RDMA с On-Demand Paging. ODP - функция, доступная пока что
|
||||
исключительно на адаптерах Mellanox ConnectX-4 и более новых. ODP позволяет
|
||||
не регистрировать память для её использования RDMA-картой. Благодаря этому
|
||||
можно не копировать данные при отправке их в сеть и, казалось бы, это должно
|
||||
улучшать производительность - но **по факту** получается так, что
|
||||
производительность только ухудшается, причём сильно. Пример - на 3-узловом
|
||||
кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на чтение с RDMA без ODP
|
||||
удаётся снять 3950000 iops, а с ODP - всего 239000 iops...
|
||||
|
||||
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
|
||||
основана на повторной передаче сообщений, когда карте не известен буфер -
|
||||
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
|
||||
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
|
||||
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
|
||||
|
||||
Возможность использования ODP сохранена в коде на случай, если вдруг в один
|
||||
прекрасный день появится хорошая реализация ODP.
|
||||
- name: peer_connect_interval
|
||||
type: sec
|
||||
min: 1
|
||||
|
||||
+46
-14
@@ -253,21 +253,33 @@
|
||||
type: bool
|
||||
default: true
|
||||
info: |
|
||||
This parameter makes Vitastor always keep metadata area of the block device
|
||||
in memory. It's required for good performance because it allows to avoid
|
||||
additional read-modify-write cycles during metadata modifications. Metadata
|
||||
area size is currently roughly 224 MB per 1 TB of data. You can turn it off
|
||||
to reduce memory usage by this value, but it will hurt performance. This
|
||||
restriction is likely to be removed in the future along with the upgrade
|
||||
of the metadata storage scheme.
|
||||
Only for the old store ([meta_format](layout-osd.en.md#meta_format) 2).
|
||||
|
||||
This parameter makes Vitastor keep a copy of metadata area in memory as it is
|
||||
on disk, in addition to the metadata database. When the option is enabled, every
|
||||
metadata entry is effectively stored in RAM twice. It's required for good performance
|
||||
because it allows to avoid additional read-modify-write cycles during metadata
|
||||
modifications. Metadata area size with the old store is roughly 224 MB per 1 TB
|
||||
of data. You can turn the option off to reduce memory usage by this value, but
|
||||
it will reduce performance.
|
||||
|
||||
For the new store ([meta_format](layout-osd.en.md#meta_format) 3), the option
|
||||
may be changed in the future to support operation without loading full metadata
|
||||
database in memory.
|
||||
info_ru: |
|
||||
Данный параметр заставляет Vitastor всегда держать область метаданных диска
|
||||
в памяти. Это нужно, чтобы избегать дополнительных операций чтения с диска
|
||||
при записи. Размер области метаданных на данный момент составляет примерно
|
||||
224 МБ на 1 ТБ данных. При включении потребление памяти снизится примерно
|
||||
на эту величину, но при этом также снизится и производительность. В будущем,
|
||||
после обновления схемы хранения метаданных, это ограничение, скорее всего,
|
||||
будет ликвидировано.
|
||||
Только для старого хранилища ([meta_format](layout-osd.en.md#meta_format) 2).
|
||||
|
||||
Данный параметр заставляет Vitastor всегда держать копию области метаданных
|
||||
в памяти в том же виде, как она лежит на диске, в дополнение к БД метаданных.
|
||||
То есть, с включённой опцией каждая запись метаданных хранится в памяти дважды.
|
||||
Это нужно, чтобы избегать дополнительных операций чтения с диска при записи.
|
||||
Размер области метаданных в старом хранилище составляет примерно 224 МБ на
|
||||
1 ТБ данных. Вы можете отключить опцию, чтобы снизить потребление памяти
|
||||
примерно на эту величину, но при этом также снизится и производительность.
|
||||
|
||||
Для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3) опция,
|
||||
возможно, будет переработана в будущем для поддержки работы без полной
|
||||
загрузки метаданных в памяти.
|
||||
- name: inmemory_journal
|
||||
type: bool
|
||||
default: true
|
||||
@@ -386,11 +398,15 @@
|
||||
blocks. The only situation when you should increase it to a larger value
|
||||
is when you enable journal_no_same_sector_overwrites. In this case set
|
||||
it to, for example, 1024.
|
||||
|
||||
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
info_ru: |
|
||||
Максимальное число буферов, разрешённых для использования под записываемые
|
||||
в журнал блоки метаданных. Единственная ситуация, в которой этот параметр
|
||||
нужно менять - это если вы включаете journal_no_same_sector_overwrites. В
|
||||
этом случае установите данный параметр, например, в 1024.
|
||||
|
||||
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
- name: journal_no_same_sector_overwrites
|
||||
type: bool
|
||||
default: false
|
||||
@@ -402,6 +418,8 @@
|
||||
journal after writing it instead of possibly overwriting it the second time.
|
||||
|
||||
Most (99%) other SSDs don't need this option.
|
||||
|
||||
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
info_ru: |
|
||||
Включайте данную опцию для SSD вроде Intel D3-S4510 и D3-S4610, которые
|
||||
ОЧЕНЬ не любят, когда ПО перезаписывает один и тот же сектор несколько раз
|
||||
@@ -412,6 +430,20 @@
|
||||
самого сектора.
|
||||
|
||||
Почти все другие SSD (99% моделей) не требуют данной опции.
|
||||
|
||||
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
- name: skip_corrupted_meta_entries
|
||||
type: bool
|
||||
default: false
|
||||
info: |
|
||||
Only for the new store ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
Allow OSD to start when some metadata entries or blocks are corrupted by
|
||||
skipping them. Should be only used as an emergency measure.
|
||||
info_ru: |
|
||||
Только для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3).
|
||||
Разрешить OSD запускаться, даже если часть блоков или записей метаданных
|
||||
повреждена, пропуская их. Опция предназначена для использования только в
|
||||
целях аварийного восстановления.
|
||||
- name: throttle_small_writes
|
||||
type: bool
|
||||
default: false
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
@@ -0,0 +1,131 @@
|
||||
- name: etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
|
||||
etcd https connections. May be path to a file or just a PEM string with certificate.
|
||||
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
- name: etcd_client_key
|
||||
type: string
|
||||
info: Private key for etcd_client_cert (also a file or a PEM string).
|
||||
info_ru: Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
|
||||
- name: etcd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify etcd server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: osd_etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
|
||||
OSDs, clients and monitors should have different permissions, so they should
|
||||
use different certificates.
|
||||
info_ru: |
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
- name: osd_etcd_client_key
|
||||
type: string
|
||||
info: Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
|
||||
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
|
||||
- name: mon_etcd_client_cert
|
||||
type: string
|
||||
info: Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
|
||||
info_ru: Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
|
||||
- name: mon_etcd_client_key
|
||||
type: string
|
||||
info: Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
|
||||
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
|
||||
- name: vault_url
|
||||
type: string
|
||||
info: |
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
info_ru: |
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
- name: vault_secret_api_path
|
||||
type: string
|
||||
default: /v1/secret/
|
||||
info: Vault v1 secret API mount path to use.
|
||||
info_ru: Путь к API секретов v1 для использования клиентами.
|
||||
- name: vault_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
|
||||
may be path to a file or just a certificate in PEM string.
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
- name: vault_client_key
|
||||
type: string
|
||||
info: Private key for vault_client_cert (also a file or a PEM string).
|
||||
info_ru: Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
|
||||
- name: vault_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: vault_timeout_ms
|
||||
type: int
|
||||
default: 5000
|
||||
info: Timeout for Vault requests in milliseconds.
|
||||
info_ru: Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
- name: vault_error_timeout_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
info_ru: |
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
- name: vault_refresh_leeway_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
info_ru: |
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
- name: max_aes_xts_pool_size
|
||||
type: int
|
||||
default: 256
|
||||
info: |
|
||||
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
|
||||
doesn't require modification.
|
||||
info_ru: |
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
|
||||
Вряд ли требует изменения.
|
||||
@@ -26,13 +26,37 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
|
||||
The instruction is very simple.
|
||||
|
||||
1. Download a Docker image of the desired version: \
|
||||
`docker pull vitalif/vitastor:v3.0.2`
|
||||
`docker pull vitalif/vitastor:v3.0.5`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.2 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.5 install.sh`
|
||||
3. Reload udev rules: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Enable the vitastor-host service: \
|
||||
`systemctl enable --now vitastor-host`
|
||||
|
||||
And you can return to [Quick Start](../intro/quickstart.en.md).
|
||||
After these steps, you can return to [Quick Start](../intro/quickstart.en.md).
|
||||
|
||||
## Podman
|
||||
|
||||
If you use Podman, run the following commands as root before installing Vitastor containers:
|
||||
|
||||
```
|
||||
ln -s podman /usr/bin/docker
|
||||
|
||||
mkdir -p /etc/systemd/system/systemd-udevd.service.d
|
||||
|
||||
cat >/etc/systemd/system/systemd-udevd.service.d/override.conf <<EOF
|
||||
[Service]
|
||||
CapabilityBoundingSet=~
|
||||
SystemCallFilter=@mount capset
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
systemctl restart systemd-udevd
|
||||
```
|
||||
|
||||
Without it, udev fails to do calls into a Podman container and Vitastor disk detection doesn't work.
|
||||
|
||||
## Upgrading Containers
|
||||
|
||||
|
||||
@@ -25,14 +25,39 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
|
||||
Инструкция по установке максимально простая.
|
||||
|
||||
1. Скачайте Docker-образ желаемой версии: \
|
||||
`docker pull vitalif/vitastor:v3.0.2`
|
||||
`docker pull vitalif/vitastor:v3.0.5`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.2 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.5 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
`systemctl enable --now vitastor-host`
|
||||
|
||||
После этого вы можете возвращаться к разделу [Быстрый старт](../intro/quickstart.ru.md).
|
||||
|
||||
## Podman
|
||||
|
||||
Если вы используете Podman, перед установкой контейнеров Vitastor выполните следующие
|
||||
команды от имени суперпользователя:
|
||||
|
||||
```
|
||||
ln -s podman /usr/bin/docker
|
||||
|
||||
mkdir -p /etc/systemd/system/systemd-udevd.service.d
|
||||
|
||||
cat >/etc/systemd/system/systemd-udevd.service.d/override.conf <<EOF
|
||||
[Service]
|
||||
CapabilityBoundingSet=~
|
||||
SystemCallFilter=@mount capset
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
systemctl restart systemd-udevd
|
||||
```
|
||||
|
||||
Без этих настроек udev не может делать вызовы внутрь Podman-контейнеров и определение дисков Vitastor не работает.
|
||||
|
||||
## Обновление контейнеров
|
||||
|
||||
Сначала обязательно проверьте раздел [Обновление Vitastor](../usage/admin.ru.md#обновление-vitastor),
|
||||
|
||||
@@ -33,15 +33,17 @@
|
||||
- CentOS 7: `yum install https://vitastor.io/rpms/centos/7/vitastor-release.rpm`
|
||||
- CentOS 8: `dnf install https://vitastor.io/rpms/centos/8/vitastor-release.rpm`
|
||||
- AlmaLinux 9 and other RHEL 9 clones (Rocky, Oracle...): `dnf install https://vitastor.io/rpms/centos/9/vitastor-release.rpm`
|
||||
- AlmaLinux 10 and other RHEL 10 clones: `dnf install https://vitastor.io/rpms/centos/10/vitastor-release.rpm`
|
||||
- Enable EPEL: `yum/dnf install epel-release`
|
||||
- Enable additional CentOS repositories:
|
||||
- CentOS 7: `yum install centos-release-scl`
|
||||
- CentOS 8: `dnf install centos-release-advanced-virtualization`
|
||||
- RHEL 9 clones: not required
|
||||
- RHEL 9/10 clones: not required
|
||||
- Enable elrepo-kernel:
|
||||
- CentOS 7: `yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm`
|
||||
- CentOS 8: `dnf install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm`
|
||||
- RHEL 9 clones: `dnf install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm`
|
||||
- RHEL 10 clones: not required
|
||||
- Install packages: `yum/dnf install vitastor lpsolve etcd kernel-ml qemu-kvm`
|
||||
|
||||
## Installation requirements
|
||||
|
||||
@@ -33,15 +33,17 @@
|
||||
- CentOS 7: `yum install https://vitastor.io/rpms/centos/7/vitastor-release.rpm`
|
||||
- CentOS 8: `dnf install https://vitastor.io/rpms/centos/8/vitastor-release.rpm`
|
||||
- AlmaLinux 9 и другие клоны RHEL 9 (Rocky, Oracle...): `dnf install https://vitastor.io/rpms/centos/9/vitastor-release.rpm`
|
||||
- AlmaLinux 10 и другие клоны RHEL 10: `dnf install https://vitastor.io/rpms/centos/10/vitastor-release.rpm`
|
||||
- Включите EPEL: `yum/dnf install epel-release`
|
||||
- Включите дополнительные репозитории CentOS:
|
||||
- CentOS 7: `yum install centos-release-scl`
|
||||
- CentOS 8: `dnf install centos-release-advanced-virtualization`
|
||||
- Клоны RHEL 9: не нужно
|
||||
- Клоны RHEL 9/10: не нужно
|
||||
- Включите elrepo-kernel:
|
||||
- CentOS 7: `yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm`
|
||||
- CentOS 8: `dnf install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm`
|
||||
- Клоны RHEL 9: `dnf install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm`
|
||||
- Клоны RHEL 10: не нужно
|
||||
- Установите пакеты: `yum/dnf install vitastor lpsolve etcd kernel-ml qemu-kvm`
|
||||
|
||||
## Установочные требования
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
- gcc and g++ 8 or newer, clang 10 or newer, or other compiler with C++11 plus
|
||||
designated initializers support from C++20
|
||||
- CMake
|
||||
- jerasure headers and libraries
|
||||
- ISA-L, libibverbs and librdmacm headers and libraries (optional)
|
||||
- jerasure, c-ares headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
- tcmalloc (google-perftools-dev)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
|
||||
назначенных инициализаторов (designated initializers) из C++20
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
- tcmalloc (google-perftools-dev)
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
|
||||
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
|
||||
- [S3](../installation/s3.en.md)
|
||||
- [TLS support for etcd connections](../config/security.en.md)
|
||||
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
|
||||
|
||||
## Plugins and tools
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
|
||||
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
|
||||
- [S3](../installation/s3.ru.md)
|
||||
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
|
||||
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
|
||||
|
||||
## Драйверы и инструменты
|
||||
|
||||
|
||||
+21
-7
@@ -125,18 +125,31 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
|
||||
|
||||
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
|
||||
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
|
||||
Pool must be specified if there is more than one pool.
|
||||
Create an image. Options:
|
||||
|
||||
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
|
||||
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
|
||||
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
|
||||
If parent is not a snapshot, it must be a read-only image.
|
||||
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
|
||||
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
|
||||
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
|
||||
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
|
||||
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
|
||||
|
||||
Options:
|
||||
|
||||
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
|
||||
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
|
||||
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
|
||||
By default, the image retains its old encryption key when taking a snapshot.
|
||||
|
||||
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
|
||||
|
||||
@@ -151,6 +164,7 @@ You should resize file system in the image, if present, before shrinking it.
|
||||
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
|
||||
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
|
||||
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
|
||||
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+22
-8
@@ -127,19 +127,32 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
|
||||
|
||||
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
|
||||
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
|
||||
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
|
||||
Создать образ. Опции:
|
||||
|
||||
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
|
||||
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
|
||||
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
|
||||
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
|
||||
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
|
||||
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
|
||||
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
|
||||
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
|
||||
клиентов, если пишущий клиент максимум 1.
|
||||
Создать снимок образа `<image>` (можно использовать любую форму команды).
|
||||
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
|
||||
|
||||
Опции:
|
||||
|
||||
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
|
||||
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
|
||||
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
|
||||
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
|
||||
|
||||
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
|
||||
|
||||
@@ -156,6 +169,7 @@ vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
|
||||
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
|
||||
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
|
||||
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+11
-7
@@ -18,7 +18,7 @@ class AntiEtcdAdapter
|
||||
cluster = cluster ? (''+(cluster||'')).split(/,+/) : [];
|
||||
cluster = Object.keys(cluster.reduce((a, url) =>
|
||||
{
|
||||
a[url.toLowerCase().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '')] = true;
|
||||
a[url.toLowerCase().replace(/^(https?:\/\/)?(.*?)(\/.*)?$/, (m, m1, m2) => (m1||'http://')+m2)] = true;
|
||||
return a;
|
||||
}, {}));
|
||||
const cfg_port = config.antietcd_port;
|
||||
@@ -26,7 +26,8 @@ class AntiEtcdAdapter
|
||||
is_local['0.0.0.0'] = true;
|
||||
is_local['::'] = true;
|
||||
is_local[''] = true;
|
||||
const selected = cluster.map(s => s.split(':', 2)).filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
|
||||
// split :, 3 -> <schema>:<//ip>:<port>
|
||||
const selected = cluster.map(s => s.split(':', 3)).filter(ip => is_local[ip[1].substr(2)] && (!cfg_port || ip[2] == cfg_port));
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
@@ -35,12 +36,15 @@ class AntiEtcdAdapter
|
||||
else if (selected.length == 1)
|
||||
{
|
||||
const antietcd_config = {
|
||||
ip: selected[0][0],
|
||||
port: selected[0][1],
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
|
||||
ip: selected[0][1].substr(2),
|
||||
port: selected[0][2],
|
||||
cert: config.antietcd_cert,
|
||||
key: config.antietcd_key,
|
||||
ca: config.etcd_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][2]+'.json.gz'),
|
||||
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
|
||||
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
|
||||
node_id: selected[0][1].substr(2)+':'+selected[0][2], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
|
||||
cluster_key: (config.etcd_prefix || '/vitastor'),
|
||||
stale_read: 1,
|
||||
log_level: 1,
|
||||
|
||||
+27
-6
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -15,11 +17,30 @@ class EtcdAdapter
|
||||
this.ws = null;
|
||||
this.ws_alive = false;
|
||||
this.ws_keepalive_timer = null;
|
||||
this.opts = {};
|
||||
}
|
||||
|
||||
parse_config(config)
|
||||
{
|
||||
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
|
||||
if (config.mon_etcd_client_cert || config.etcd_client_cert)
|
||||
{
|
||||
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
|
||||
if (this.opts.cert.substr(0, 5) != '-----')
|
||||
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.mon_etcd_client_key || config.etcd_client_key)
|
||||
{
|
||||
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
|
||||
if (this.opts.key.substr(0, 5) != '-----')
|
||||
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.etcd_ca)
|
||||
{
|
||||
this.opts.ca = config.etcd_ca;
|
||||
if (this.opts.ca.substr(0, 5) != '-----')
|
||||
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
|
||||
}
|
||||
}
|
||||
|
||||
parse_etcd_addresses(addrs)
|
||||
@@ -39,7 +60,7 @@ class EtcdAdapter
|
||||
for (let url of addrs)
|
||||
{
|
||||
let scheme = 'http';
|
||||
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
|
||||
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
|
||||
const slash = url.indexOf('/');
|
||||
const colon = url.indexOf(':');
|
||||
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
|
||||
@@ -130,7 +151,7 @@ class EtcdAdapter
|
||||
}
|
||||
ok(false);
|
||||
}, this.mon.config.etcd_mon_timeout);
|
||||
this.ws = new WebSocket(base+'/watch');
|
||||
this.ws = new WebSocket(base+'/watch', this.opts);
|
||||
this.ws_used_url = cur_addr;
|
||||
const fail = () =>
|
||||
{
|
||||
@@ -272,7 +293,7 @@ class EtcdAdapter
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
}
|
||||
const res = await POST(base+path, body, timeout);
|
||||
const res = await POST(base+path, body, timeout, this.opts);
|
||||
if (this.mon.stopped)
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
@@ -298,7 +319,7 @@ class EtcdAdapter
|
||||
}
|
||||
}
|
||||
|
||||
function POST(url, body, timeout)
|
||||
function POST(url, body, timeout, opts)
|
||||
{
|
||||
return new Promise(ok =>
|
||||
{
|
||||
@@ -310,10 +331,10 @@ function POST(url, body, timeout)
|
||||
req = null;
|
||||
ok({ error: 'timeout' });
|
||||
}, timeout) : null;
|
||||
let req = http.request(url, { method: 'POST', headers: {
|
||||
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body_text.length,
|
||||
} }, (res) =>
|
||||
}, ...(opts||{}) }, (res) =>
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
|
||||
+11
-1
@@ -45,7 +45,14 @@ const etcd_tree = {
|
||||
config_path: "/etc/vitastor/vitastor.conf",
|
||||
etcd_prefix: "/vitastor",
|
||||
// etcd connection - configurable online
|
||||
etcd_address: "10.0.115.10:2379/v3",
|
||||
etcd_address: "http://10.0.115.10:2379/v3",
|
||||
etcd_client_cert: "",
|
||||
etcd_client_key: "",
|
||||
osd_etcd_client_cert: "",
|
||||
osd_etcd_client_key: "",
|
||||
mon_etcd_client_cert: "",
|
||||
mon_etcd_client_key: "",
|
||||
etcd_ca: "",
|
||||
// mon
|
||||
etcd_mon_ttl: 5, // min: 1
|
||||
etcd_mon_timeout: 1000, // ms. min: 0
|
||||
@@ -217,6 +224,8 @@ const etcd_tree = {
|
||||
parent_id?: <inode_t>,
|
||||
readonly?: boolean,
|
||||
deleted?: boolean,
|
||||
enc_key?: string,
|
||||
meta?: any,
|
||||
}
|
||||
}
|
||||
}, */
|
||||
@@ -383,6 +392,7 @@ const etcd_tree = {
|
||||
/* <name>: {
|
||||
id: uint64_t,
|
||||
pool_id: uint64_t,
|
||||
// ...plus a copy of everything from config/inode/x/y
|
||||
}, */
|
||||
},
|
||||
maxid: {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ async function create_http_server(cfg, handler)
|
||||
};
|
||||
if (cfg.mon_https_ca)
|
||||
{
|
||||
tls.mon_https_ca = await fsp.readFile(cfg.mon_https_ca);
|
||||
tls.ca = await fsp.readFile(cfg.mon_https_ca);
|
||||
}
|
||||
if (cfg.mon_https_client_auth)
|
||||
{
|
||||
|
||||
@@ -10,16 +10,19 @@ const NO_OSD = 'Z';
|
||||
async function lp_solve(text)
|
||||
{
|
||||
const cp = child_process.spawn('lp_solve');
|
||||
let stdout = '', stderr = '', finish_cb;
|
||||
let stdout = '', stderr = '', finish_cb, finished = 0;
|
||||
cp.stdout.on('data', buf => stdout += buf.toString());
|
||||
cp.stderr.on('data', buf => stderr += buf.toString());
|
||||
cp.on('exit', () => finish_cb && finish_cb());
|
||||
cp.stdout.on('end', () => finish_cb());
|
||||
cp.stderr.on('end', () => finish_cb());
|
||||
cp.stdin.write(text);
|
||||
cp.stdin.end();
|
||||
if (cp.exitCode == null)
|
||||
await new Promise(ok => (finish_cb = () =>
|
||||
{
|
||||
await new Promise(ok => finish_cb = ok);
|
||||
}
|
||||
finished++;
|
||||
if (finished == 2)
|
||||
ok();
|
||||
}));
|
||||
if (!stdout.trim())
|
||||
{
|
||||
return null;
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.2",
|
||||
"version": "3.0.5",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "Vitaliy Filippov",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"antietcd": "^1.2.2",
|
||||
"antietcd": "^1.2.4",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"ws": "^7.2.5"
|
||||
},
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ async function generate_pool_pgs(state, global_config, pool_id, osd_tree, levels
|
||||
const folded_tree = make_hier_tree(global_config, folded.nodes.reduce((a, c) => { a[c.id] = c; return a; }, {}));
|
||||
const old_pg_count = prev_pgs.length;
|
||||
const optimize_cfg = {
|
||||
osd_weights: folded.nodes.reduce((a, c) => { if (Number(c.id)) { a[c.id] = c.size; } return a; }, {}),
|
||||
osd_weights: folded.nodes.reduce((a, c) => { if (/^\d+$/.exec(c.id) && c.size != null) { a[c.id] = c.size||0; } return a; }, {}),
|
||||
combinator: use_rules
|
||||
// new algorithm:
|
||||
? new RuleCombinator(folded_tree, rules, pool_cfg.max_osd_combinations)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor",
|
||||
"version": "3.0.2",
|
||||
"version": "3.0.5",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
+30
-232
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.2'
|
||||
VITASTOR_VERSION = '3.0.5'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@@ -275,7 +275,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
LOG.exception('error getting vitastor pool stats: '+str(e))
|
||||
|
||||
self._stats = stats
|
||||
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats.
|
||||
If 'refresh' is True, run update the stats first.
|
||||
@@ -291,6 +291,14 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
else:
|
||||
return (1 + resp['kvs'][0]['value'], resp['kvs'][0]['mod_revision'])
|
||||
|
||||
def _cli(self, descr, *args):
|
||||
args = [ 'vitastor-cli', *args, *(self._vitastor_args()) ]
|
||||
try:
|
||||
self._execute(*args)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
LOG.error("Failed to "+descr+": "+exc)
|
||||
raise exception.VolumeBackendAPIException(data = exc.stderr)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a logical volume."""
|
||||
|
||||
@@ -302,7 +310,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
|
||||
LOG.debug("creating volume '%s'", vol_name)
|
||||
|
||||
self._create_image(vol_name, { 'size': size })
|
||||
self._cli('create volume', 'create', vol_name, '--size', size)
|
||||
|
||||
if volume.encryption_key_id:
|
||||
self._create_encrypted_volume(volume, volume.obj_context)
|
||||
@@ -346,7 +354,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
snap_name = utils.convert_str(snapshot.name)
|
||||
if snap_name.find('@') >= 0 or snap_name.find('/') >= 0:
|
||||
raise exception.VolumeBackendAPIException(data = '@ and / are forbidden in volume and snapshot names')
|
||||
self._create_snapshot(vol_name, vol_name+'@'+snap_name)
|
||||
self._cli('create snapshot', 'snap-create', vol_name+'@'+snap_name)
|
||||
|
||||
def snapshot_revert_use_temp_snapshot(self):
|
||||
"""Disable the use of a temporary snapshot on revert."""
|
||||
@@ -359,21 +367,8 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
snap_name = utils.convert_str(snapshot.name)
|
||||
|
||||
# Delete the image and recreate it from the snapshot
|
||||
args = [ 'vitastor-cli', 'rm', vol_name, *(self._vitastor_args()) ]
|
||||
try:
|
||||
self._execute(*args)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
LOG.error("Failed to delete image "+vol_name+": "+exc)
|
||||
raise exception.VolumeBackendAPIException(data = exc.stderr)
|
||||
args = [
|
||||
'vitastor-cli', 'create', '--parent', vol_name+'@'+snap_name,
|
||||
vol_name, *(self._vitastor_args())
|
||||
]
|
||||
try:
|
||||
self._execute(*args)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
LOG.error("Failed to recreate image "+vol_name+" from "+vol_name+"@"+snap_name+": "+exc)
|
||||
raise exception.VolumeBackendAPIException(data = exc.stderr)
|
||||
self._cli('delete image', 'rm', vol_name)
|
||||
self._cli('recreate image', 'create', '--parent', vol_name+'@'+snap_name, vol_name)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
@@ -381,15 +376,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
vol_name = utils.convert_str(snapshot.volume_name)
|
||||
snap_name = utils.convert_str(snapshot.name)
|
||||
|
||||
args = [
|
||||
'vitastor-cli', 'rm', vol_name+'@'+snap_name,
|
||||
*(self._vitastor_args())
|
||||
]
|
||||
try:
|
||||
self._execute(*args)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
LOG.error("Failed to remove snapshot "+vol_name+'@'+snap_name+": "+exc)
|
||||
raise exception.VolumeBackendAPIException(data = exc.stderr)
|
||||
self._cli('remove snapshot', 'rm', vol_name+'@'+snap_name)
|
||||
|
||||
def _child_count(self, parents):
|
||||
children = 0
|
||||
@@ -427,13 +414,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
if src_vref.admin_metadata.get('readonly') == 'True':
|
||||
# source volume is a volume-image cache entry or other readonly volume
|
||||
# clone without intermediate snapshot
|
||||
src = self._get_image(src_name)
|
||||
LOG.debug("creating image '%s' from '%s'", dest_name, src_name)
|
||||
new_cfg = self._create_image(dest_name, {
|
||||
'size': size,
|
||||
'parent_id': src['idx']['id'],
|
||||
'parent_pool_id': src['idx']['pool_id'],
|
||||
})
|
||||
self._cli('create clone', 'create', '--parent', src_name, '--size', size, dest_name)
|
||||
return {}
|
||||
|
||||
clone_snap = "%s@%s.clone_snap" % (src_name, dest_name)
|
||||
@@ -446,15 +427,12 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
clone_snap = dest_name
|
||||
make_img = False
|
||||
|
||||
LOG.debug("creating layer '%s' under '%s'", clone_snap, src_name)
|
||||
new_cfg = self._create_snapshot(src_name, clone_snap, True)
|
||||
LOG.debug("creating snapshot '%s'", clone_snap)
|
||||
self._cli('create base snapshot', 'snap-create', '--allow-existing', '1', clone_snap)
|
||||
|
||||
if make_img:
|
||||
# Then create a clone from it
|
||||
new_cfg = self._create_image(dest_name, {
|
||||
'size': size,
|
||||
'parent_id': new_cfg['parent_id'],
|
||||
'parent_pool_id': new_cfg['parent_pool_id'],
|
||||
})
|
||||
self._cli('create clone', 'create', '--parent', clone_snap, '--size', size, dest_name)
|
||||
|
||||
return {}
|
||||
|
||||
@@ -464,7 +442,8 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
vol_name = utils.convert_str(volume.name)
|
||||
snap_name = utils.convert_str(snapshot.name)
|
||||
|
||||
snap = self._get_image('volume-'+snapshot.volume_id+'@'+snap_name)
|
||||
src_snap = 'volume-'+snapshot.volume_id+'@'+snap_name
|
||||
snap = self._get_image(src_snap)
|
||||
if not snap:
|
||||
raise exception.SnapshotNotFound(snapshot_id = snap_name)
|
||||
snap_inode_id = int(resp['responses'][0]['kvs'][0]['value']['id'])
|
||||
@@ -473,12 +452,8 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
size = snap['cfg']['size']
|
||||
if int(volume.size):
|
||||
size = int(volume.size) * units.Gi
|
||||
new_cfg = self._create_image(vol_name, {
|
||||
'size': size,
|
||||
'parent_id': snap['idx']['id'],
|
||||
'parent_pool_id': snap['idx']['pool_id'],
|
||||
})
|
||||
|
||||
self._cli('create clone', 'create', vol_name, '--size', size, '--parent', src_snap)
|
||||
return {}
|
||||
|
||||
def _vitastor_args(self):
|
||||
@@ -505,49 +480,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
"""Deletes a logical volume."""
|
||||
|
||||
vol_name = utils.convert_str(volume.name)
|
||||
|
||||
# Find the volume and all its snapshots
|
||||
range_end = b'index/image/' + vol_name.encode('utf-8')
|
||||
range_end = range_end[0 : len(range_end)-1] + six.int2byte(range_end[len(range_end)-1] + 1)
|
||||
resp = self._etcd_txn({ 'success': [
|
||||
{ 'request_range': { 'key': 'index/image/'+vol_name, 'range_end': range_end } },
|
||||
] })
|
||||
if len(resp['responses'][0]['kvs']) == 0:
|
||||
# already deleted
|
||||
LOG.info("volume %s no longer exists in backend", vol_name)
|
||||
return
|
||||
layers = resp['responses'][0]['kvs']
|
||||
layer_ids = {}
|
||||
for kv in layers:
|
||||
inode_id = int(kv['value']['id'])
|
||||
pool_id = int(kv['value']['pool_id'])
|
||||
inode_pool_id = (pool_id << 48) | (inode_id & 0xffffffffffff)
|
||||
layer_ids[inode_pool_id] = True
|
||||
|
||||
# Check if the volume has clones and raise 'busy' if so
|
||||
children = self._child_count(layer_ids)
|
||||
if children > 0:
|
||||
raise exception.VolumeIsBusy(volume_name = vol_name)
|
||||
|
||||
# Clear data
|
||||
for kv in layers:
|
||||
args = [
|
||||
'vitastor-cli', 'rm-data', '--pool', str(kv['value']['pool_id']),
|
||||
'--inode', str(kv['value']['id']), '--progress', '0',
|
||||
*(self._vitastor_args())
|
||||
]
|
||||
try:
|
||||
self._execute(*args)
|
||||
except processutils.ProcessExecutionError as exc:
|
||||
LOG.error("Failed to remove layer "+kv['key']+": "+exc)
|
||||
raise exception.VolumeBackendAPIException(data = exc.stderr)
|
||||
|
||||
# Delete all layers from etcd
|
||||
requests = []
|
||||
for kv in layers:
|
||||
requests.append({ 'request_delete_range': { 'key': kv['key'] } })
|
||||
requests.append({ 'request_delete_range': { 'key': 'config/inode/'+str(kv['value']['pool_id'])+'/'+str(kv['value']['id']) } })
|
||||
self._etcd_txn({ 'success': requests })
|
||||
self._cli('delete volume', 'rm', '--matching', vol_name, vol_name+'@*', '--progress', '0')
|
||||
|
||||
def retype(self, context, volume, new_type, diff, host):
|
||||
"""Change extra type specifications for a volume."""
|
||||
@@ -567,98 +500,6 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
"""Removes an export for a logical volume."""
|
||||
pass
|
||||
|
||||
def _create_image(self, vol_name, cfg):
|
||||
pool_s = str(self.cfg['pool_id'])
|
||||
image_id = 0
|
||||
while image_id == 0:
|
||||
# check if the image already exists and find a free ID
|
||||
resp = self._etcd_txn({ 'success': [
|
||||
{ 'request_range': { 'key': 'index/image/'+vol_name } },
|
||||
{ 'request_range': { 'key': 'index/maxid/'+pool_s } },
|
||||
] })
|
||||
if len(resp['responses'][0]['kvs']) > 0:
|
||||
# already exists
|
||||
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' already exists')
|
||||
image_id, id_mod = self._next_id(resp['responses'][1])
|
||||
# try to create the image
|
||||
resp = self._etcd_txn({ 'compare': [
|
||||
{ 'target': 'MOD', 'mod_revision': id_mod, 'key': 'index/maxid/'+pool_s },
|
||||
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+vol_name },
|
||||
{ 'target': 'VERSION', 'version': 0, 'key': 'config/inode/'+pool_s+'/'+str(image_id) },
|
||||
], 'success': [
|
||||
{ 'request_put': { 'key': 'index/maxid/'+pool_s, 'value': image_id } },
|
||||
{ 'request_put': { 'key': 'index/image/'+vol_name, 'value': json.dumps({
|
||||
'id': image_id, 'pool_id': self.cfg['pool_id']
|
||||
}) } },
|
||||
{ 'request_put': { 'key': 'config/inode/'+pool_s+'/'+str(image_id), 'value': json.dumps({
|
||||
**cfg, 'name': vol_name,
|
||||
}) } },
|
||||
] })
|
||||
if not resp.get('succeeded'):
|
||||
# repeat
|
||||
image_id = 0
|
||||
|
||||
def _create_snapshot(self, vol_name, snap_vol_name, allow_existing = False):
|
||||
while True:
|
||||
# check if the image already exists and snapshot doesn't
|
||||
resp = self._etcd_txn({ 'success': [
|
||||
{ 'request_range': { 'key': 'index/image/'+vol_name } },
|
||||
{ 'request_range': { 'key': 'index/image/'+snap_vol_name } },
|
||||
] })
|
||||
if len(resp['responses'][0]['kvs']) == 0:
|
||||
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
|
||||
if len(resp['responses'][1]['kvs']) > 0:
|
||||
if allow_existing:
|
||||
snap_idx = resp['responses'][1]['kvs'][0]['value']
|
||||
resp = self._etcd_txn({ 'success': [
|
||||
{ 'request_range': { 'key': 'config/inode/'+str(snap_idx['pool_id'])+'/'+str(snap_idx['id']) } },
|
||||
] })
|
||||
if len(resp['responses'][0]['kvs']) == 0:
|
||||
raise exception.VolumeBackendAPIException(data =
|
||||
'Volume '+snap_vol_name+' is already indexed, but does not exist'
|
||||
)
|
||||
return resp['responses'][0]['kvs'][0]['value']
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data = 'Volume '+snap_vol_name+' already exists'
|
||||
)
|
||||
vol_idx = resp['responses'][0]['kvs'][0]['value']
|
||||
vol_idx_mod = resp['responses'][0]['kvs'][0]['mod_revision']
|
||||
# get image inode config and find a new ID
|
||||
resp = self._etcd_txn({ 'success': [
|
||||
{ 'request_range': { 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']) } },
|
||||
{ 'request_range': { 'key': 'index/maxid/'+str(self.cfg['pool_id']) } },
|
||||
] })
|
||||
if len(resp['responses'][0]['kvs']) == 0:
|
||||
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
|
||||
vol_cfg = resp['responses'][0]['kvs'][0]['value']
|
||||
vol_mod = resp['responses'][0]['kvs'][0]['mod_revision']
|
||||
new_id, id_mod = self._next_id(resp['responses'][1])
|
||||
# try to redirect image to the new inode
|
||||
new_cfg = {
|
||||
**vol_cfg, 'name': vol_name, 'parent_id': vol_idx['id'], 'parent_pool_id': vol_idx['pool_id']
|
||||
}
|
||||
resp = self._etcd_txn({ 'compare': [
|
||||
{ 'target': 'MOD', 'mod_revision': vol_idx_mod, 'key': 'index/image/'+vol_name },
|
||||
{ 'target': 'MOD', 'mod_revision': vol_mod, 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']) },
|
||||
{ 'target': 'MOD', 'mod_revision': id_mod, 'key': 'index/maxid/'+str(self.cfg['pool_id']) },
|
||||
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+snap_vol_name },
|
||||
{ 'target': 'VERSION', 'version': 0, 'key': 'config/inode/'+str(self.cfg['pool_id'])+'/'+str(new_id) },
|
||||
], 'success': [
|
||||
{ 'request_put': { 'key': 'index/maxid/'+str(self.cfg['pool_id']), 'value': new_id } },
|
||||
{ 'request_put': { 'key': 'index/image/'+vol_name, 'value': json.dumps({
|
||||
'id': new_id, 'pool_id': self.cfg['pool_id']
|
||||
}) } },
|
||||
{ 'request_put': { 'key': 'config/inode/'+str(self.cfg['pool_id'])+'/'+str(new_id), 'value': json.dumps(new_cfg) } },
|
||||
{ 'request_put': { 'key': 'index/image/'+snap_vol_name, 'value': json.dumps({
|
||||
'id': vol_idx['id'], 'pool_id': vol_idx['pool_id']
|
||||
}) } },
|
||||
{ 'request_put': { 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']), 'value': json.dumps({
|
||||
**vol_cfg, 'name': snap_vol_name, 'readonly': True
|
||||
}) } }
|
||||
] })
|
||||
if resp.get('succeeded'):
|
||||
return new_cfg
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
data = {
|
||||
'driver_volume_type': 'vitastor',
|
||||
@@ -697,13 +538,9 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
size = int(volume.size) * units.Gi
|
||||
dest_name = utils.convert_str(volume.name)
|
||||
# Find or create the base snapshot
|
||||
snap_cfg = self._create_snapshot(base_vol.name, base_vol.name+'@.clone_snap', True)
|
||||
self._cli('create base snapshot', 'create', '--allow-existing', '1', base_vol.name+'@.clone_snap')
|
||||
# Then create a clone from it
|
||||
new_cfg = self._create_image(dest_name, {
|
||||
'size': size,
|
||||
'parent_id': snap_cfg['parent_id'],
|
||||
'parent_pool_id': snap_cfg['parent_pool_id'],
|
||||
})
|
||||
self._cli('create clone', 'create', dest_name, '--size', size, '--parent', base_vol.name+'@.clone_snap')
|
||||
return ({}, True)
|
||||
return ({}, False)
|
||||
|
||||
@@ -770,26 +607,8 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume."""
|
||||
vol_name = utils.convert_str(volume.name)
|
||||
while True:
|
||||
vol = self._get_image(vol_name)
|
||||
if not vol:
|
||||
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
|
||||
# change size
|
||||
size = int(new_size) * units.Gi
|
||||
if size == vol['cfg']['size']:
|
||||
break
|
||||
resp = self._etcd_txn({ 'compare': [ {
|
||||
'target': 'MOD',
|
||||
'mod_revision': vol['cfg_mod'],
|
||||
'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
|
||||
} ], 'success': [
|
||||
{ 'request_put': {
|
||||
'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
|
||||
'value': json.dumps({ **vol['cfg'], 'size': size }),
|
||||
} },
|
||||
] })
|
||||
if resp.get('succeeded'):
|
||||
break
|
||||
size = int(new_size) * units.Gi
|
||||
self._cli('extend volume', 'modify', vol_name, '--resize', new_size)
|
||||
LOG.debug(
|
||||
"Extend volume from %(old_size)s GB to %(new_size)s GB.",
|
||||
{'old_size': volume.size, 'new_size': new_size}
|
||||
@@ -862,28 +681,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
"""
|
||||
from_name = self._get_existing_name(existing_ref)
|
||||
to_name = utils.convert_str(volume.name)
|
||||
self._rename(from_name, to_name)
|
||||
|
||||
def _rename(self, from_name, to_name):
|
||||
while True:
|
||||
vol = self._get_image(from_name)
|
||||
if not vol:
|
||||
raise exception.VolumeBackendAPIException(data = 'Volume '+from_name+' does not exist')
|
||||
to = self._get_image(to_name)
|
||||
if to:
|
||||
raise exception.VolumeBackendAPIException(data = 'Volume '+to_name+' already exists')
|
||||
resp = self._etcd_txn({ 'compare': [
|
||||
{ 'target': 'MOD', 'mod_revision': vol['idx_mod'], 'key': 'index/image/'+vol['cfg']['name'] },
|
||||
{ 'target': 'MOD', 'mod_revision': vol['cfg_mod'], 'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']) },
|
||||
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+to_name },
|
||||
], 'success': [
|
||||
{ 'request_delete_range': { 'key': 'index/image/'+vol['cfg']['name'] } },
|
||||
{ 'request_put': { 'key': 'index/image/'+to_name, 'value': json.dumps(vol['idx']) } },
|
||||
{ 'request_put': { 'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
|
||||
'value': json.dumps({ **vol['cfg'], 'name': to_name }) } },
|
||||
] })
|
||||
if resp.get('succeeded'):
|
||||
break
|
||||
self._cli('rename', 'modify', from_name, '--rename', to_name)
|
||||
|
||||
def unmanage(self, volume):
|
||||
pass
|
||||
@@ -956,7 +754,7 @@ class VitastorDriver(driver.CloneableImageVD,
|
||||
snap_name = self._get_existing_name(existing_ref)
|
||||
from_name = vol_name+'@'+snap_name
|
||||
to_name = vol_name+'@'+utils.convert_str(snapshot.name)
|
||||
self._rename(from_name, to_name)
|
||||
self._cli('rename', 'modify', from_name, '--rename', to_name)
|
||||
|
||||
def unmanage_snapshot(self, snapshot):
|
||||
"""Removes the specified snapshot from Cinder management."""
|
||||
|
||||
@@ -21,7 +21,7 @@ rpmbuild -bp fio.spec
|
||||
cd $VITASTOR
|
||||
VER=$(grep ^Version: rpm/vitastor-$REL.spec | awk '{print $2}')
|
||||
rm -rf fio
|
||||
ln -s ~/rpmbuild/BUILD/fio*/ fio
|
||||
ln -s $(ls -d ~/rpmbuild/BUILD/fio*/ | grep -v SPECPARTS) fio
|
||||
sh copy-fio-includes.sh
|
||||
rm fio
|
||||
mv fio-copy fio
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Build packages for AlmaLinux 10 inside a container
|
||||
# cd ..
|
||||
# docker pull --platform=linux/amd64/v2 quay.io/almalinuxorg/almalinux:10
|
||||
# docker build -t vitastor-buildenv:el10 -f rpm/vitastor-el10.Dockerfile .
|
||||
# docker run -i --rm -v ./:/root/vitastor vitastor-buildenv:el10 /root/vitastor/rpm/vitastor-build.sh
|
||||
|
||||
FROM quay.io/almalinuxorg/almalinux:10
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
@@ -0,0 +1,199 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.5
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.5.el10.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: nodejs >= 10
|
||||
BuildRequires: jerasure-devel
|
||||
BuildRequires: isa-l-devel
|
||||
BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
Requires: vitastor-client-devel = %{version}-%{release}
|
||||
Requires: vitastor-fio = %{version}-%{release}
|
||||
|
||||
%description
|
||||
Vitastor is a small, simple and fast clustered block storage (storage for VM drives),
|
||||
architecturally similar to Ceph which means strong consistency, primary-replication,
|
||||
symmetric clustering and automatic data distribution over any number of drives of any
|
||||
size with configurable redundancy (replication or erasure codes/XOR).
|
||||
|
||||
|
||||
%package -n vitastor-osd
|
||||
Summary: Vitastor - OSD
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
Requires: util-linux
|
||||
Requires: parted
|
||||
|
||||
|
||||
%description -n vitastor-osd
|
||||
Vitastor object storage daemon, i.e. server program that stores data.
|
||||
|
||||
|
||||
%package -n vitastor-mon
|
||||
Summary: Vitastor - monitor
|
||||
Requires: nodejs >= 10
|
||||
Requires: lpsolve
|
||||
|
||||
|
||||
%description -n vitastor-mon
|
||||
Vitastor monitor, i.e. server program responsible for watching cluster state and
|
||||
scheduling cluster-level operations.
|
||||
|
||||
|
||||
%package -n vitastor-client
|
||||
Summary: Vitastor - client
|
||||
|
||||
|
||||
%description -n vitastor-client
|
||||
Vitastor client library and command-line interface.
|
||||
|
||||
|
||||
%package -n vitastor-client-devel
|
||||
Summary: Vitastor - development files
|
||||
Group: Development/Libraries
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
|
||||
%description -n vitastor-client-devel
|
||||
Vitastor library headers for development.
|
||||
|
||||
|
||||
%package -n vitastor-fio
|
||||
Summary: Vitastor - fio drivers
|
||||
Group: Development/Libraries
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
Requires: fio = 3.36-5.el10
|
||||
|
||||
|
||||
%description -n vitastor-fio
|
||||
Vitastor fio drivers for benchmarking.
|
||||
|
||||
|
||||
%package -n vitastor-opennebula
|
||||
Summary: Vitastor for OpenNebula
|
||||
Group: Development/Libraries
|
||||
Requires: vitastor-client
|
||||
Requires: jq
|
||||
Requires: python3-lxml
|
||||
Requires: patch
|
||||
Requires: qemu-kvm-block-vitastor
|
||||
|
||||
|
||||
%description -n vitastor-opennebula
|
||||
Vitastor storage plugin for OpenNebula.
|
||||
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
|
||||
%build
|
||||
%cmake
|
||||
%cmake_build
|
||||
|
||||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
%cmake_install
|
||||
cd mon
|
||||
npm install --production
|
||||
cd ..
|
||||
mkdir -p %buildroot/usr/lib/vitastor
|
||||
cp -r mon %buildroot/usr/lib/vitastor
|
||||
mv %buildroot/usr/lib/vitastor/mon/scripts/make-etcd %buildroot/usr/lib/vitastor/mon/
|
||||
mkdir -p %buildroot/lib/systemd/system
|
||||
cp mon/scripts/vitastor.target mon/scripts/vitastor-mon.service mon/scripts/vitastor-osd@.service %buildroot/lib/systemd/system
|
||||
mkdir -p %buildroot/lib/udev/rules.d
|
||||
cp mon/scripts/90-vitastor.rules %buildroot/lib/udev/rules.d
|
||||
mkdir -p %buildroot/var/lib/one
|
||||
cp -r opennebula/remotes %buildroot/var/lib/one
|
||||
cp opennebula/install.sh %buildroot/var/lib/one/remotes/datastore/vitastor/
|
||||
mkdir -p %buildroot/etc/
|
||||
cp -r opennebula/sudoers.d %buildroot/etc/
|
||||
|
||||
|
||||
%files
|
||||
%doc GPL-2.0.txt VNPL-1.1.txt README.md README-ru.md
|
||||
|
||||
|
||||
%files -n vitastor-osd
|
||||
%_bindir/vitastor-osd
|
||||
%_bindir/vitastor-disk
|
||||
%_bindir/vitastor-dump-journal
|
||||
/lib/systemd/system/vitastor-osd@.service
|
||||
/lib/systemd/system/vitastor.target
|
||||
/lib/udev/rules.d/90-vitastor.rules
|
||||
|
||||
|
||||
%pre -n vitastor-osd
|
||||
groupadd -r -f vitastor 2>/dev/null ||:
|
||||
useradd -r -g vitastor -s /sbin/nologin -c "Vitastor daemons" -M -d /nonexistent vitastor 2>/dev/null ||:
|
||||
install -o vitastor -g vitastor -d /var/log/vitastor
|
||||
mkdir -p /etc/vitastor
|
||||
|
||||
|
||||
%files -n vitastor-mon
|
||||
/usr/lib/vitastor/mon
|
||||
/lib/systemd/system/vitastor-mon.service
|
||||
|
||||
|
||||
%pre -n vitastor-mon
|
||||
groupadd -r -f vitastor 2>/dev/null ||:
|
||||
useradd -r -g vitastor -s /sbin/nologin -c "Vitastor daemons" -M -d /nonexistent vitastor 2>/dev/null ||:
|
||||
mkdir -p /etc/vitastor
|
||||
mkdir -p /var/lib/vitastor
|
||||
chown vitastor:vitastor /var/lib/vitastor
|
||||
|
||||
|
||||
%files -n vitastor-client
|
||||
%_bindir/vitastor-nbd
|
||||
%_bindir/vitastor-ublk
|
||||
%_bindir/vitastor-nfs
|
||||
%_bindir/vitastor-cli
|
||||
%_bindir/vitastor-rm
|
||||
%_bindir/vitastor-kv
|
||||
%_bindir/vitastor-kv-stress
|
||||
%_bindir/vita
|
||||
%_libdir/libvitastor_client.so*
|
||||
%_libdir/libvitastor_kv.so*
|
||||
|
||||
|
||||
%files -n vitastor-client-devel
|
||||
%_includedir/vitastor_c.h
|
||||
%_includedir/vitastor_kv.h
|
||||
%_libdir/pkgconfig
|
||||
|
||||
|
||||
%files -n vitastor-fio
|
||||
%_libdir/libfio_vitastor.so
|
||||
%_libdir/libfio_vitastor_blk.so
|
||||
%_libdir/libfio_vitastor_sec.so
|
||||
|
||||
|
||||
%files -n vitastor-opennebula
|
||||
/var/lib/one
|
||||
/etc/sudoers.d/opennebula-vitastor
|
||||
|
||||
|
||||
%triggerin -n vitastor-opennebula -- opennebula
|
||||
[ $2 = 0 ] || exit 0
|
||||
/var/lib/one/remotes/datastore/vitastor/install.sh
|
||||
|
||||
|
||||
# Turn off the brp-python-bytecompile script
|
||||
%global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g')
|
||||
|
||||
|
||||
%changelog
|
||||
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
|
||||
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
|
||||
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
|
||||
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.2
|
||||
Version: 3.0.5
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.2.el7.tar.gz
|
||||
Source0: vitastor-3.0.5.el7.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: devtoolset-9-gcc-c++
|
||||
@@ -17,6 +17,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake3
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
|
||||
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
|
||||
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.2
|
||||
Version: 3.0.5
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.2.el8.tar.gz
|
||||
Source0: vitastor-3.0.5.el8.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-toolset-9-gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.2
|
||||
Version: 3.0.5
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.2.el9.tar.gz
|
||||
Source0: vitastor-3.0.5.el9.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
+9
-1
@@ -21,7 +21,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
|
||||
endif()
|
||||
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
|
||||
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.2")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.5")
|
||||
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
|
||||
add_link_options(-fno-omit-frame-pointer)
|
||||
if (${WITH_ASAN})
|
||||
@@ -75,6 +75,14 @@ if (RDMACM_LIBRARIES)
|
||||
add_definitions(-DWITH_RDMACM)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (OPENSSL_FOUND)
|
||||
add_definitions(-DWITH_OPENSSL)
|
||||
endif (OPENSSL_FOUND)
|
||||
|
||||
pkg_check_modules(CARES REQUIRED libcares)
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
|
||||
if (${WITH_SYSTEM_LIBURING})
|
||||
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
|
||||
include_directories(${LIBURING_INCLUDE_DIRS})
|
||||
|
||||
@@ -4,7 +4,7 @@ project(vitastor)
|
||||
|
||||
# libvitastor_blk.a
|
||||
add_library(vitastor_blk STATIC
|
||||
../util/allocator.cpp ../util/crc32c.c ../util/ringloop.cpp
|
||||
../util/allocator.cpp ../util/crc32c.c ../util/xxhash.c ../util/ringloop.cpp
|
||||
multilist.cpp blockstore_heap.cpp blockstore_disk.cpp
|
||||
blockstore.cpp blockstore_impl.cpp blockstore_init.cpp blockstore_open.cpp
|
||||
blockstore_flush.cpp blockstore_read.cpp blockstore_stable.cpp blockstore_sync.cpp blockstore_write.cpp
|
||||
|
||||
@@ -187,7 +187,6 @@ public:
|
||||
// MUST be called only when nobody makes any modifications to the DB for this pool
|
||||
virtual void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit) = 0;
|
||||
virtual bool reshard_continue(void *reshard_state, uint64_t chunk_limit) = 0;
|
||||
virtual void reshard_abort(void *reshard_state) = 0;
|
||||
|
||||
// Event loop
|
||||
virtual void loop() = 0;
|
||||
|
||||
@@ -83,13 +83,17 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
|
||||
}
|
||||
else if (config["data_csum_type"] == "xxh3_32")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
|
||||
}
|
||||
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
|
||||
}
|
||||
csum_block_size = parse_size(config["csum_block_size"]);
|
||||
discard_on_start = config.find("discard_on_start") != config.end() &&
|
||||
@@ -173,9 +177,7 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
}
|
||||
if (data_block_size / bitmap_granularity < 8)
|
||||
{
|
||||
fprintf(stderr, "Warning: block_size (%u) / bitmap_granularity (%u) = %u bits. "
|
||||
"Consider using larger block_size or bitmap_granularity for better performance.\n",
|
||||
data_block_size, bitmap_granularity, data_block_size / bitmap_granularity);
|
||||
throw std::runtime_error("Data block size must be at least bitmap_granularity*8");
|
||||
}
|
||||
if (!data_csum_type)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define BLOCKSTORE_CSUM_NONE 0
|
||||
// Lower byte of checksum type is its length
|
||||
#define BLOCKSTORE_CSUM_CRC32C 0x104
|
||||
#define BLOCKSTORE_CSUM_XXH3_32 0x204
|
||||
|
||||
#define MOCK_DATA_FD 1000
|
||||
#define MOCK_META_FD 1001
|
||||
@@ -26,14 +27,14 @@ class allocator_t;
|
||||
struct blockstore_disk_t
|
||||
{
|
||||
std::string data_device, meta_device, journal_device;
|
||||
uint32_t data_block_size;
|
||||
uint64_t data_block_size;
|
||||
uint64_t cfg_journal_size, cfg_data_size;
|
||||
// Required write alignment and journal/metadata/data areas' location alignment
|
||||
uint32_t disk_alignment = 4096;
|
||||
// Journal block size - minimum_io_size of the journal device is the best choice
|
||||
uint32_t journal_block_size = 4096;
|
||||
uint64_t journal_block_size = 4096;
|
||||
// Metadata block size - minimum_io_size of the metadata device is the best choice
|
||||
uint32_t meta_block_size = 4096;
|
||||
uint64_t meta_block_size = 4096;
|
||||
// Atomic write size of the data block device
|
||||
uint32_t atomic_write_size = 4096;
|
||||
// Whether we should set RWF_ATOMIC on atomic writes
|
||||
|
||||
+232
-139
@@ -12,6 +12,7 @@
|
||||
#include "blockstore_heap.h"
|
||||
#include "../util/allocator.h"
|
||||
#include "../util/crc32c.h"
|
||||
#include "../util/xxhash.h"
|
||||
#include "../util/malloc_or_die.h"
|
||||
|
||||
#define BS_HEAP_FREE_MVCC 1
|
||||
@@ -62,19 +63,19 @@ uint32_t blockstore_heap_t::get_simple_entry_size()
|
||||
uint32_t blockstore_heap_t::get_big_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_write_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_big_intent_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
|
||||
{
|
||||
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
|
||||
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ uint32_t blockstore_heap_t::get_csum_size(heap_entry_t *wr)
|
||||
|
||||
uint32_t blockstore_heap_t::get_csum_size(uint32_t entry_type, uint32_t offset, uint32_t len)
|
||||
{
|
||||
if (!dsk->data_csum_type)
|
||||
if (!dsk->csum_block_size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -212,15 +213,24 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
|
||||
big().block_num = location / heap->dsk->data_block_size;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_crc32c()
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
|
||||
{
|
||||
auto old_crc32c = crc32c;
|
||||
crc32c = 0;
|
||||
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
|
||||
crc32c = old_crc32c;
|
||||
auto old_checksum = checksum;
|
||||
checksum = 0;
|
||||
uint32_t res = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
res = (uint32_t)XXH3_64bits(this, size);
|
||||
else
|
||||
res = ::crc32c(0, (uint8_t*)this, size);
|
||||
checksum = old_checksum;
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
|
||||
{
|
||||
return calc_checksum(heap->dsk);
|
||||
}
|
||||
|
||||
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
|
||||
{
|
||||
uint64_t pg_num = 0;
|
||||
@@ -301,12 +311,13 @@ int blockstore_heap_t::read_blocks(uint64_t disk_offset, uint64_t disk_size, uin
|
||||
heap_entry_t *wr = (heap_entry_t*)data;
|
||||
if (wr->size > dsk->meta_block_size-block_offset)
|
||||
{
|
||||
fprintf(stderr, "Error: entry is too large in metadata block %u at %u (%u > max %u bytes). ",
|
||||
fprintf(stderr, "Error: entry is too large in metadata block %u at %u (%u > max %ju bytes). ",
|
||||
block_num, block_offset, wr->size, dsk->meta_block_size-block_offset);
|
||||
corrupted_block:
|
||||
if (allow_corrupted)
|
||||
{
|
||||
fprintf(stderr, "Metadata block is corrupted, skipping\n");
|
||||
recheck_modified_blocks.insert(block_num);
|
||||
break;
|
||||
}
|
||||
else
|
||||
@@ -331,7 +342,19 @@ corrupted_block:
|
||||
block_num, block_offset, wr->size, sizeof(heap_entry_t));
|
||||
goto corrupted_block;
|
||||
}
|
||||
wr->entry_type &= ~BS_HEAP_GARBAGE;
|
||||
if (wr->is_garbage())
|
||||
{
|
||||
// Garbage collection is only performed when writing new entries into the block
|
||||
// because it needs a fake LSN and modified blocks require consecutive modified LSNs
|
||||
// That's why garbage entries may persist on disk
|
||||
if (log_level > 5)
|
||||
{
|
||||
fprintf(stderr, "Notice: skipping garbage entry %jx:%jx v%ju l%ju in metadata block %u at %u\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn, block_num, block_offset);
|
||||
}
|
||||
block_offset += wr->size;
|
||||
continue;
|
||||
}
|
||||
if ((wr->entry_type & BS_HEAP_TYPE) < BS_HEAP_BIG_WRITE ||
|
||||
(wr->entry_type & BS_HEAP_TYPE) > BS_HEAP_ROLLBACK ||
|
||||
(wr->entry_type & ~(BS_HEAP_TYPE|BS_HEAP_STABLE)) ||
|
||||
@@ -345,6 +368,7 @@ corrupted_object:
|
||||
if (allow_corrupted)
|
||||
{
|
||||
fprintf(stderr, "Entry is corrupted, skipping\n");
|
||||
recheck_modified_blocks.insert(block_num);
|
||||
block_offset += wr->size;
|
||||
continue;
|
||||
}
|
||||
@@ -360,7 +384,7 @@ corrupted_object:
|
||||
{
|
||||
// Small writes require accessing offset & len to calculate correct length,
|
||||
// so require at least sizeof(heap_small_write_t) for them
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u < min %zu bytes). Metadata is corrupted, aborting\n",
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u < min %zu bytes)\n",
|
||||
wr->inode, wr->stripe, wr->version, block_num, block_offset, wr->size, sizeof(heap_small_write_t));
|
||||
goto corrupted_object;
|
||||
}
|
||||
@@ -371,12 +395,12 @@ corrupted_object:
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify crc
|
||||
uint32_t expected_crc32c = wr->calc_crc32c();
|
||||
if (wr->crc32c != expected_crc32c)
|
||||
uint32_t expected_checksum = wr->calc_checksum(this);
|
||||
if (wr->checksum != expected_checksum)
|
||||
{
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). Metadata is corrupted, aborting\n",
|
||||
wr->inode, wr->stripe, wr->version,
|
||||
block_num, block_offset, expected_crc32c, wr->crc32c);
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn,
|
||||
block_num, block_offset, expected_checksum, wr->checksum);
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify offset & len
|
||||
@@ -385,7 +409,7 @@ corrupted_object:
|
||||
wr->small().offset % dsk->bitmap_granularity ||
|
||||
wr->small().len % dsk->bitmap_granularity))
|
||||
{
|
||||
fprintf(stderr, "Error: %s entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters, aborting\n",
|
||||
fprintf(stderr, "Error: %s entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters. ",
|
||||
wr->type() == BS_HEAP_SMALL_WRITE ? "small_write" : "intent_write",
|
||||
wr->inode, wr->stripe, wr->version, wr->small().offset, wr->small().len);
|
||||
goto corrupted_object;
|
||||
@@ -395,7 +419,7 @@ corrupted_object:
|
||||
wr->big_intent().offset % dsk->bitmap_granularity ||
|
||||
wr->big_intent().len % dsk->bitmap_granularity))
|
||||
{
|
||||
fprintf(stderr, "Error: big_intent entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters, aborting\n",
|
||||
fprintf(stderr, "Error: big_intent entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters. ",
|
||||
wr->inode, wr->stripe, wr->version, wr->big_intent().offset, wr->big_intent().len);
|
||||
goto corrupted_object;
|
||||
}
|
||||
@@ -422,7 +446,7 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
|
||||
next_lsn = wr->lsn;
|
||||
}
|
||||
entries_loaded++;
|
||||
insert_list_item(li);
|
||||
loaded_list_items.push_back(li);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
if (!inf.entries.size())
|
||||
@@ -458,19 +482,22 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
|
||||
next_wr = wr;
|
||||
if (wr->type() == BS_HEAP_ROLLBACK)
|
||||
{
|
||||
if (commit_wr && wr->version > commit_wr->version)
|
||||
{
|
||||
// rollback may not come before commit with a smaller version
|
||||
fprintf(stderr, "Error: rollback entry %jx:%jx v%ju l%ju comes before a commit entry v%ju l%ju\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn, commit_wr->version, commit_wr->lsn);
|
||||
return false;
|
||||
}
|
||||
rollback_wr = wr;
|
||||
continue;
|
||||
}
|
||||
if (wr->type() == BS_HEAP_COMMIT)
|
||||
{
|
||||
commit_wr = wr;
|
||||
if (commit_wr && wr->version > commit_wr->version)
|
||||
{
|
||||
// commit may not come before commit with a smaller version
|
||||
fprintf(stderr, "Error: commit entry %jx:%jx v%ju l%ju comes before a commit entry v%ju l%ju\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn, commit_wr->version, commit_wr->lsn);
|
||||
return false;
|
||||
}
|
||||
if (!commit_wr)
|
||||
{
|
||||
commit_wr = wr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (wr->entry_type & BS_HEAP_STABLE)
|
||||
@@ -523,6 +550,23 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::finish_load()
|
||||
{
|
||||
if (loaded_list_items.size())
|
||||
{
|
||||
// Sort everything and load in correct order
|
||||
std::sort(loaded_list_items.begin(), loaded_list_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
|
||||
{
|
||||
return a->entry.lsn < b->entry.lsn;
|
||||
});
|
||||
for (auto & li: loaded_list_items)
|
||||
{
|
||||
insert_list_item(li);
|
||||
}
|
||||
loaded_list_items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::fill_recheck_queue()
|
||||
{
|
||||
for (auto & pgp: block_index)
|
||||
@@ -709,6 +753,7 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
|
||||
}
|
||||
if (!recheck_queue_filled)
|
||||
{
|
||||
finish_load();
|
||||
fill_recheck_queue();
|
||||
recheck_queue_filled = true;
|
||||
}
|
||||
@@ -800,7 +845,7 @@ std::vector<uint32_t> blockstore_heap_t::get_recheck_modified_blocks()
|
||||
return modified;
|
||||
}
|
||||
|
||||
int blockstore_heap_t::finish_load(bool allow_corrupted)
|
||||
int blockstore_heap_t::finish_recheck()
|
||||
{
|
||||
if (!marked_used_blocks)
|
||||
{
|
||||
@@ -837,14 +882,17 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
|
||||
{
|
||||
return true;
|
||||
}
|
||||
uint32_t len = 0;
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE)
|
||||
len = wr->small().len;
|
||||
else if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
len = wr->big_intent().len;
|
||||
else
|
||||
assert(0);
|
||||
uint32_t real_csum = crc32c(0, data, len);
|
||||
uint32_t real_csum = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
real_csum = (uint32_t)XXH3_64bits(data, len);
|
||||
else
|
||||
real_csum = crc32c(0, data, len);
|
||||
if (set)
|
||||
{
|
||||
*wr_csum = real_csum;
|
||||
@@ -854,13 +902,14 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
|
||||
}
|
||||
if (wr->type() == BS_HEAP_BIG_WRITE)
|
||||
{
|
||||
assert(offset != UINT32_MAX && len != UINT32_MAX);
|
||||
return calc_block_checksums((uint32_t*)(wr->get_checksums(this) + offset/dsk->csum_block_size * (dsk->data_csum_type & 0xFF)),
|
||||
data, wr->get_int_bitmap(this), offset, offset+len, set, NULL);
|
||||
}
|
||||
if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
auto & bi = wr->big_intent();
|
||||
return calc_block_checksums((uint32_t*)(wr->get_checksums(this) + offset/dsk->csum_block_size * (dsk->data_csum_type & 0xFF)),
|
||||
return calc_block_checksums((uint32_t*)(wr->get_checksums(this) + bi.offset/dsk->csum_block_size * (dsk->data_csum_type & 0xFF)),
|
||||
data, wr->get_int_bitmap(this), bi.offset, bi.offset+bi.len, set, NULL);
|
||||
}
|
||||
assert(wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE);
|
||||
@@ -893,11 +942,26 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
|
||||
return prev_crc;
|
||||
}
|
||||
|
||||
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
|
||||
{
|
||||
uint32_t cur_len = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
uint8_t *data = next(pos, cur_len);
|
||||
assert(data);
|
||||
cur_len = (cur_len < size ? cur_len : size);
|
||||
XXH3_64bits_update(xxh3_state, data, cur_len);
|
||||
pos += cur_len;
|
||||
size -= cur_len;
|
||||
}
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
|
||||
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
|
||||
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
|
||||
{
|
||||
bool res = true;
|
||||
XXH3_state_t* xxh3_state = NULL;
|
||||
uint32_t pos = start;
|
||||
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
|
||||
uint32_t block_crc = 0;
|
||||
@@ -914,42 +978,89 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
|
||||
pos += dsk->bitmap_granularity;
|
||||
// zero padding at the beginning or at the end of the block is not counted
|
||||
if (pos > prev && prev > 0 && pos < block_end)
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
{
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
uint32_t zeropad = pos-prev;
|
||||
while (zeropad > 0)
|
||||
{
|
||||
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
|
||||
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
|
||||
zeropad -= zerolen;
|
||||
}
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
}
|
||||
prev = pos;
|
||||
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
if (pos > prev)
|
||||
{
|
||||
isset = true;
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, prev, pos-prev);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
}
|
||||
prev = pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
pos = (end > block_end ? block_end : end);
|
||||
isset = true;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
if (set)
|
||||
{
|
||||
*block_csums = block_crc;
|
||||
}
|
||||
else if (isset && block_crc != *block_csums)
|
||||
{
|
||||
res = false;
|
||||
if (bad_block_cb)
|
||||
{
|
||||
bad_block_cb(blk_start, *block_csums, block_crc);
|
||||
res = false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
block_end += dsk->csum_block_size;
|
||||
block_crc = 0;
|
||||
block_csums++;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_freeState(xxh3_state);
|
||||
xxh3_state = NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1077,16 +1188,6 @@ bool blockstore_heap_t::reshard_check(pool_id_t pool, uint32_t pg_count, uint32_
|
||||
set_it->second.pg_stripe_size == pg_stripe_size);
|
||||
}
|
||||
|
||||
void blockstore_heap_t::reshard_abort(void* reshard_state)
|
||||
{
|
||||
heap_reshard_state_t *st = (heap_reshard_state_t*)reshard_state;
|
||||
for (auto sh_it = st->old_shards.begin(); sh_it != st->old_shards.end(); sh_it++)
|
||||
{
|
||||
block_index[sh_it->first] = std::move(sh_it->second);
|
||||
}
|
||||
delete st;
|
||||
}
|
||||
|
||||
heap_entry_t *blockstore_heap_t::lock_and_read_entry(object_id oid)
|
||||
{
|
||||
auto obj = read_entry(oid);
|
||||
@@ -1099,27 +1200,6 @@ heap_entry_t *blockstore_heap_t::lock_and_read_entry(object_id oid)
|
||||
return obj;
|
||||
}
|
||||
|
||||
heap_entry_t *blockstore_heap_t::read_locked_entry(object_id oid, uint64_t lsn)
|
||||
{
|
||||
auto obj = read_entry(oid);
|
||||
assert(obj);
|
||||
for (auto wr = obj; wr; wr = prev(wr))
|
||||
{
|
||||
if (wr->is_overwrite())
|
||||
{
|
||||
if (lsn == wr->lsn)
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = prev(wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::unlock_entry(object_id oid)
|
||||
{
|
||||
auto mvcc_it = object_mvcc.find(oid);
|
||||
@@ -1156,6 +1236,35 @@ heap_entry_t *blockstore_heap_t::read_entry(object_id oid)
|
||||
return &li->entry;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::gc_block(heap_block_info_t & inf)
|
||||
{
|
||||
if (inf.has_garbage)
|
||||
{
|
||||
size_t i = 0, j = 0;
|
||||
for (; i < inf.entries.size(); i++)
|
||||
{
|
||||
if (inf.entries[i]->entry.is_garbage())
|
||||
{
|
||||
// old entry invalidated by a newer one, mark it as freeable on block write
|
||||
// assign a 'virtual' LSN to track GC completion
|
||||
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn);
|
||||
uint64_t gc_lsn = ++next_lsn;
|
||||
inf.mod_lsn = inf.mod_lsn ? inf.mod_lsn : gc_lsn;
|
||||
inf.mod_lsn_to = gc_lsn;
|
||||
push_inflight_lsn(gc_lsn, &inf.entries[i]->entry, HEAP_INFLIGHT_GC);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j != i)
|
||||
inf.entries[j] = inf.entries[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
inf.entries.resize(j);
|
||||
inf.has_garbage = false;
|
||||
}
|
||||
}
|
||||
|
||||
int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free)
|
||||
{
|
||||
if (last_allocated_block != UINT32_MAX)
|
||||
@@ -1222,31 +1331,7 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
|
||||
}
|
||||
// Write into the same block
|
||||
auto & inf = block_info.at(last_allocated_block);
|
||||
if (inf.has_garbage)
|
||||
{
|
||||
size_t i = 0, j = 0;
|
||||
for (; i < inf.entries.size(); i++)
|
||||
{
|
||||
if (inf.entries[i]->entry.is_garbage())
|
||||
{
|
||||
// old entry invalidated by a newer one, mark it as freeable on block write
|
||||
// assign a 'virtual' LSN to track GC completion
|
||||
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn);
|
||||
uint64_t gc_lsn = ++next_lsn;
|
||||
inf.mod_lsn = inf.mod_lsn ? inf.mod_lsn : gc_lsn;
|
||||
inf.mod_lsn_to = gc_lsn;
|
||||
push_inflight_lsn(gc_lsn, &inf.entries[i]->entry, HEAP_INFLIGHT_GC);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j != i)
|
||||
inf.entries[j] = inf.entries[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
inf.entries.resize(j);
|
||||
inf.has_garbage = false;
|
||||
}
|
||||
gc_block(inf);
|
||||
*block_num = last_allocated_block;
|
||||
modify_alloc(last_allocated_block, [&](heap_block_info_t & inf)
|
||||
{
|
||||
@@ -1327,7 +1412,7 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
|
||||
insert_list_item(li);
|
||||
li->block_num = block_num;
|
||||
new_wr->size = wr_size;
|
||||
new_wr->crc32c = new_wr->calc_crc32c();
|
||||
new_wr->checksum = new_wr->calc_checksum(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1347,7 +1432,6 @@ int blockstore_heap_t::add_small_write(object_id oid, heap_entry_t **obj_ptr, ui
|
||||
// Small writes are written in parallel with buffered data so they require explicit_complete
|
||||
return add_entry(wr_size, modified_block, false, true, [&](heap_entry_t *wr)
|
||||
{
|
||||
printf("add_small_write t%u %lx:%lx l%lu v%lu %u +%u loc:%lx\n", type, oid.inode, oid.stripe, wr->lsn, version, offset, len, location);
|
||||
wr->entry_type = type;
|
||||
wr->inode = oid.inode;
|
||||
wr->stripe = oid.stripe;
|
||||
@@ -1377,7 +1461,6 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
|
||||
// Big writes are written after writing data so they don't require explicit_complete
|
||||
return add_entry(wr_size, modified_block, false, false, [&](heap_entry_t *wr)
|
||||
{
|
||||
printf("add_big_write %lx:%lx l%lu v%lu loc:%lx\n", oid.inode, oid.stripe, wr->lsn, version, location);
|
||||
wr->entry_type = BS_HEAP_BIG_WRITE | (stable ? BS_HEAP_STABLE : 0);
|
||||
wr->inode = oid.inode;
|
||||
wr->stripe = oid.stripe;
|
||||
@@ -1389,7 +1472,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
{
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true, offset, len);
|
||||
@@ -1404,7 +1487,6 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
|
||||
// Big-redirect intents, just like regular big writes, are written after writing data so they don't require explicit_complete
|
||||
return add_entry(wr_size, modified_block, false, false, [&](heap_entry_t *wr)
|
||||
{
|
||||
printf("add_redir_intent %lx:%lx l%lu v%lu %u +%u loc:%lx\n", oid.inode, oid.stripe, wr->lsn, version, offset, len, location);
|
||||
wr->entry_type = BS_HEAP_BIG_INTENT|BS_HEAP_STABLE;
|
||||
wr->inode = oid.inode;
|
||||
wr->stripe = oid.stripe;
|
||||
@@ -1419,9 +1501,9 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true, offset, len);
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
*obj_ptr = wr;
|
||||
});
|
||||
}
|
||||
@@ -1441,7 +1523,6 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
|
||||
// Big intents are written before writing data so they require explicit_complete
|
||||
return add_entry(wr_size, modified_block, false, true, [&](heap_entry_t *wr)
|
||||
{
|
||||
printf("add_big_intent %lx:%lx l%lu v%lu %u +%u loc:%lx\n", oid.inode, oid.stripe, wr->lsn, version, offset, len, obj->big_location(this));
|
||||
wr->entry_type = BS_HEAP_BIG_INTENT | BS_HEAP_STABLE;
|
||||
wr->inode = oid.inode;
|
||||
wr->stripe = oid.stripe;
|
||||
@@ -1458,14 +1539,14 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
|
||||
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
{
|
||||
if (checksums)
|
||||
memcpy(wr->get_checksums(this), checksums, dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
|
||||
else
|
||||
{
|
||||
memcpy(wr->get_checksums(this), obj->get_checksums(this), dsk->clean_entry_bitmap_size);
|
||||
calc_checksums(wr, (uint8_t*)data, true, offset, len);
|
||||
memcpy(wr->get_checksums(this), obj->get_checksums(this), get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1497,7 +1578,6 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
|
||||
// Compaction entry is added after copying data so it doesn't require explicit_complete
|
||||
return add_entry(wr_size, modified_block, true, false, [&](heap_entry_t *new_wr)
|
||||
{
|
||||
printf("add_compact %lx:%lx l%lu v%lu loc:%lx\n", obj->inode, obj->stripe, compact_lsn, compact_version, compact_location);
|
||||
new_wr->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
|
||||
new_wr->inode = obj->inode;
|
||||
new_wr->stripe = obj->stripe;
|
||||
@@ -1506,7 +1586,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
|
||||
new_wr->set_big_location(this, compact_location);
|
||||
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
|
||||
if (dsk->data_csum_type && new_csums)
|
||||
if (dsk->csum_block_size && new_csums)
|
||||
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
});
|
||||
}
|
||||
@@ -1587,7 +1667,7 @@ int blockstore_heap_t::add_commit(heap_entry_t *obj, uint64_t version, uint32_t
|
||||
}
|
||||
if (!uncommitted)
|
||||
{
|
||||
return EBUSY;
|
||||
return 0;
|
||||
}
|
||||
return add_simple(obj, version, modified_block, BS_HEAP_COMMIT);
|
||||
}
|
||||
@@ -1597,23 +1677,32 @@ int blockstore_heap_t::add_rollback(heap_entry_t *obj, uint64_t version, uint32_
|
||||
heap_entry_t *wr = obj;
|
||||
bool found_uncommitted = false;
|
||||
uint64_t commit_version = 0;
|
||||
while (wr && !wr->is_overwrite())
|
||||
uint64_t rollback_version = UINT64_MAX;
|
||||
while (wr)
|
||||
{
|
||||
if (wr->type() == BS_HEAP_ROLLBACK)
|
||||
{
|
||||
auto rollback_version = wr->version;
|
||||
wr = prev(wr);
|
||||
while (wr->version > rollback_version)
|
||||
if (wr->version <= version)
|
||||
{
|
||||
assert(!(wr->entry_type & BS_HEAP_STABLE));
|
||||
wr = prev(wr);
|
||||
// All previous writes are already rolled back, stop
|
||||
break;
|
||||
}
|
||||
rollback_version = wr->version;
|
||||
wr = prev(wr);
|
||||
continue;
|
||||
}
|
||||
if (wr->type() == BS_HEAP_COMMIT)
|
||||
{
|
||||
if (commit_version < wr->version)
|
||||
{
|
||||
commit_version = wr->version;
|
||||
}
|
||||
wr = prev(wr);
|
||||
continue;
|
||||
}
|
||||
if (wr->version > rollback_version)
|
||||
{
|
||||
// Already rolled back, skip
|
||||
wr = prev(wr);
|
||||
continue;
|
||||
}
|
||||
@@ -1624,14 +1713,10 @@ int blockstore_heap_t::add_rollback(heap_entry_t *obj, uint64_t version, uint32_
|
||||
{
|
||||
return EBUSY;
|
||||
}
|
||||
else if (wr->version == version)
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (wr->version < version)
|
||||
{
|
||||
return ENOENT;
|
||||
}
|
||||
}
|
||||
else if (wr->version > version)
|
||||
{
|
||||
@@ -1812,9 +1897,9 @@ void blockstore_heap_t::iterate_with_stable(heap_entry_t *obj, uint64_t max_lsn,
|
||||
}
|
||||
else
|
||||
{
|
||||
// 1) 1 2 3 ROLLBACK(2) COMMIT(3) -> impossible
|
||||
// 1) 1 2 3 ROLLBACK(2) COMMIT(3) -> 3 is unstable
|
||||
// 2) 1 2 3 4 ROLLBACK(3) COMMIT(2) -> OK
|
||||
// 3) 1 2 3 ROLLBACK(2) 3 COMMIT(3) -> first 3 shouldn't be treated as stable
|
||||
// 3) 1 2 3 ROLLBACK(2) 3 COMMIT(3) -> first 3 is unstable
|
||||
// 4) 1 2 3 COMMIT(3) ROLLBACK(2) -> impossible
|
||||
// I.e. a rollback always has version >= previous commit
|
||||
// 5) 1 2 3 4 5 ROLLBACK(4) 5 ROLLBACK(3)
|
||||
@@ -1971,24 +2056,21 @@ int blockstore_heap_t::list_objects(uint32_t pg_num, object_id min_oid, object_i
|
||||
return;
|
||||
}
|
||||
uint64_t stable_version = 0;
|
||||
auto first_wr = obj;
|
||||
for (auto wr = first_wr; wr; wr = prev(wr))
|
||||
iterate_with_stable(obj, UINT64_MAX, [&](heap_entry_t* wr, bool stable)
|
||||
{
|
||||
if ((wr->entry_type & BS_HEAP_STABLE) || wr->type() == BS_HEAP_COMMIT || wr->type() == BS_HEAP_ROLLBACK)
|
||||
if (stable)
|
||||
{
|
||||
stable_version = wr->version;
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
if (unstable_size >= unstable_alloc)
|
||||
{
|
||||
if (unstable_size >= unstable_alloc)
|
||||
{
|
||||
unstable_alloc = (!unstable_alloc ? 128 : unstable_alloc*2);
|
||||
unstable = (obj_ver_id*)realloc_or_die(unstable, sizeof(obj_ver_id) * unstable_alloc);
|
||||
}
|
||||
unstable[unstable_size++] = (obj_ver_id){ .oid = oid, .version = wr->version };
|
||||
unstable_alloc = (!unstable_alloc ? 128 : unstable_alloc*2);
|
||||
unstable = (obj_ver_id*)realloc_or_die(unstable, sizeof(obj_ver_id) * unstable_alloc);
|
||||
}
|
||||
}
|
||||
unstable[unstable_size++] = (obj_ver_id){ .oid = oid, .version = wr->version };
|
||||
return true;
|
||||
});
|
||||
if (stable_version)
|
||||
{
|
||||
if (res_size >= res_alloc)
|
||||
@@ -2050,7 +2132,13 @@ void blockstore_heap_t::free_data(inode_t inode, uint64_t location)
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
assert(data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, false);
|
||||
inode_space_stats[inode] -= dsk->data_block_size;
|
||||
auto sp_it = inode_space_stats.find(inode);
|
||||
if (sp_it != inode_space_stats.end())
|
||||
{
|
||||
sp_it->second -= dsk->data_block_size;
|
||||
if (sp_it->second == 0)
|
||||
inode_space_stats.erase(sp_it);
|
||||
}
|
||||
data_used_space -= dsk->data_block_size;
|
||||
}
|
||||
|
||||
@@ -2279,7 +2367,9 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
// The last freed entry must be a deletion
|
||||
assert(!prev);
|
||||
assert(wr->entry_type == BS_HEAP_DELETE|BS_HEAP_STABLE);
|
||||
auto & pg_idx = block_index[get_pg_id(wr->inode, wr->stripe)];
|
||||
auto & inode_idx = pg_idx[wr->inode];
|
||||
heap_inode_map_t::iterator li_it;
|
||||
@@ -2333,7 +2423,7 @@ void blockstore_heap_t::set_no_inode_stats(const std::vector<uint64_t> & pool_id
|
||||
{
|
||||
// Recalculate if changed
|
||||
if (ps.second.no_inode_stats == 2 || ps.second.no_inode_stats == 1)
|
||||
recalc_inode_space_stats(ps.first, ps.second.no_inode_stats == 1);
|
||||
recalc_inode_space_stats(ps.first, ps.second.no_inode_stats == 2);
|
||||
ps.second.no_inode_stats &= 1;
|
||||
}
|
||||
}
|
||||
@@ -2344,8 +2434,8 @@ void blockstore_heap_t::recalc_inode_space_stats(uint64_t pool_id, bool per_inod
|
||||
auto sp_begin = inode_space_stats.lower_bound((pool_id << (64-POOL_ID_BITS)));
|
||||
auto sp_end = inode_space_stats.lower_bound(((pool_id+1) << (64-POOL_ID_BITS)));
|
||||
inode_space_stats.erase(sp_begin, sp_end);
|
||||
uint32_t pg_count = ps.pg_count ? ps.pg_count : 1;
|
||||
for (uint32_t pg_num = 1; pg_num <= pg_count; pg_num++)
|
||||
uint32_t pg_count = ps.pg_count;
|
||||
for (uint32_t pg_num = pg_count ? 1 : 0; pg_num <= pg_count; pg_num++)
|
||||
{
|
||||
auto & pg_idx = block_index[(pool_id << (64-POOL_ID_BITS)) | pg_num];
|
||||
for (auto & ip: pg_idx)
|
||||
@@ -2383,7 +2473,10 @@ void inode_map_get(void *inode_idx, heap_inode_map_t::iterator & li_it, heap_lis
|
||||
size_t map_n = ((size_t)inode_idx & IMAP_MALLOC_LOW_BITS);
|
||||
if (!map_n)
|
||||
{
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Warray-bounds"
|
||||
li_it = ((heap_inode_map_t*)inode_idx)->find(list_item_key(&stripe));
|
||||
#pragma GCC diagnostic pop
|
||||
li = li_it != ((heap_inode_map_t*)inode_idx)->end() ? *li_it : NULL;
|
||||
}
|
||||
else if (map_n == 1)
|
||||
|
||||
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t entry_type;
|
||||
uint32_t crc32c;
|
||||
uint32_t checksum;
|
||||
uint64_t lsn;
|
||||
uint64_t inode;
|
||||
uint64_t stripe;
|
||||
@@ -69,7 +69,8 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
uint32_t *get_checksum(blockstore_heap_t *heap);
|
||||
uint64_t big_location(blockstore_heap_t *heap);
|
||||
void set_big_location(blockstore_heap_t *heap, uint64_t location);
|
||||
uint32_t calc_crc32c();
|
||||
uint32_t calc_checksum(blockstore_heap_t *heap);
|
||||
uint32_t calc_checksum(blockstore_disk_t *dsk);
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_small_write_t
|
||||
@@ -80,7 +81,7 @@ struct __attribute__((__packed__)) heap_small_write_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
|
||||
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_big_write_t
|
||||
@@ -98,7 +99,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
|
||||
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_list_item_t
|
||||
@@ -200,6 +201,7 @@ class blockstore_heap_t
|
||||
|
||||
bool marked_used_blocks = false;
|
||||
bool recheck_queue_filled = false;
|
||||
std::vector<heap_list_item_t*> loaded_list_items;
|
||||
std::set<uint32_t> recheck_modified_blocks;
|
||||
std::deque<heap_entry_t*> recheck_queue;
|
||||
int recheck_in_progress = 0;
|
||||
@@ -215,6 +217,7 @@ class blockstore_heap_t
|
||||
void defragment_block(uint32_t block_num);
|
||||
void reshard_add(heap_reshard_state_t *st, heap_list_item_t *li);
|
||||
|
||||
void gc_block(heap_block_info_t & inf);
|
||||
int allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free);
|
||||
void insert_list_item(heap_list_item_t *li);
|
||||
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
|
||||
@@ -237,31 +240,29 @@ public:
|
||||
std::function<void(uint32_t, uint32_t, uint8_t*)> handle_block);
|
||||
int load_blocks(uint64_t disk_offset, uint64_t size, uint8_t *buf,
|
||||
bool allow_corrupted, uint64_t &entries_loaded);
|
||||
// finish loading
|
||||
int finish_load(bool allow_corrupted = false);
|
||||
// finish loading - should be called after load_blocks
|
||||
void finish_load();
|
||||
// get blocks which are modified during loading and should be written to the disk
|
||||
// before finishing initialization if not R/O
|
||||
std::vector<uint32_t> get_recheck_modified_blocks();
|
||||
// recheck small write data after reading the database from disk
|
||||
bool recheck_small_writes(std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> read_buffer, int queue_depth);
|
||||
int finish_recheck();
|
||||
// reshard database according to the pool's PG count
|
||||
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
|
||||
bool reshard_continue(void* reshard_state, uint64_t chunk_limit);
|
||||
bool reshard_check(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size);
|
||||
void reshard_abort(void* reshard_state);
|
||||
void set_no_inode_stats(const std::vector<uint64_t> & pool_ids);
|
||||
void recalc_inode_space_stats(uint64_t pool_id, bool per_inode);
|
||||
// read an object entry and lock it against removal
|
||||
// in the future, may become asynchronous
|
||||
heap_entry_t *lock_and_read_entry(object_id oid);
|
||||
// re-read a locked object entry with the given lsn (pointer may be invalidated)
|
||||
heap_entry_t *read_locked_entry(object_id oid, uint64_t lsn);
|
||||
// read an object entry without locking it
|
||||
heap_entry_t *read_entry(object_id oid);
|
||||
// unlock an entry
|
||||
bool unlock_entry(object_id oid);
|
||||
// set or verify checksums in a write request
|
||||
bool calc_checksums(heap_entry_t *wr, uint8_t *data, bool set, uint32_t offset = 0, uint32_t len = 0);
|
||||
bool calc_checksums(heap_entry_t *wr, uint8_t *data, bool set, uint32_t offset = UINT32_MAX, uint32_t len = UINT32_MAX);
|
||||
// set or verify raw block checksums
|
||||
bool calc_block_checksums(uint32_t *block_csums, uint8_t *data, uint8_t *bitmap, uint32_t start, uint32_t end,
|
||||
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb);
|
||||
|
||||
@@ -193,12 +193,12 @@ void blockstore_impl_t::loop()
|
||||
heap->start_block_write(block_num);
|
||||
mb.sent = true;
|
||||
}
|
||||
pending_modified_blocks.clear();
|
||||
int ret = ringloop->submit();
|
||||
if (ret < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("io_uring_submit: ") + strerror(-ret));
|
||||
}
|
||||
pending_modified_blocks.clear();
|
||||
if ((initial_ring_space - ringloop->space_left()) > 0)
|
||||
{
|
||||
live = true;
|
||||
@@ -406,8 +406,3 @@ bool blockstore_impl_t::reshard_continue(void *reshard_state, uint64_t chunk_lim
|
||||
{
|
||||
return heap->reshard_continue(reshard_state, chunk_limit);
|
||||
}
|
||||
|
||||
void blockstore_impl_t::reshard_abort(void *reshard_state)
|
||||
{
|
||||
return heap->reshard_abort(reshard_state);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ public:
|
||||
// Suitable only for server SSDs with capacitors, requires disabled data and journal fsyncs
|
||||
int immediate_commit = IMMEDIATE_NONE;
|
||||
bool inmemory_meta = false;
|
||||
bool skip_corrupted_meta_entries = false;
|
||||
uint32_t meta_write_recheck_parallelism = 0;
|
||||
// Maximum and minimum flusher count
|
||||
unsigned max_flusher_count = 0, min_flusher_count = 0;
|
||||
@@ -191,7 +192,6 @@ public:
|
||||
|
||||
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
|
||||
bool reshard_continue(void *reshard_state, uint64_t chunk_limit);
|
||||
void reshard_abort(void *reshard_state);
|
||||
|
||||
// Event loop
|
||||
void loop();
|
||||
|
||||
@@ -145,7 +145,7 @@ resume_1:
|
||||
printf(
|
||||
"Configuration stored in metadata superblock"
|
||||
" (meta_block_size=%u, data_block_size=%u, bitmap_granularity=%u, data_csum_type=%u, csum_block_size=%u, meta_area_size=%ju)"
|
||||
" differs from OSD configuration (%u/%u/%u, %u/%u, %ju).\n",
|
||||
" differs from OSD configuration (%ju/%ju/%u, %u/%u, %ju).\n",
|
||||
hdr->meta_block_size, hdr->data_block_size, hdr->bitmap_granularity,
|
||||
hdr->data_csum_type, hdr->csum_block_size, hdr->meta_area_size,
|
||||
bs->dsk.meta_block_size, bs->dsk.data_block_size, bs->dsk.bitmap_granularity,
|
||||
@@ -225,7 +225,7 @@ resume_4:
|
||||
{
|
||||
// Handle result
|
||||
uint64_t loaded = 0;
|
||||
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, false, loaded);
|
||||
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
|
||||
if (r != 0)
|
||||
exit(1);
|
||||
entries_loaded += loaded;
|
||||
@@ -239,6 +239,7 @@ resume_4:
|
||||
return 1;
|
||||
}
|
||||
// metadata read finished
|
||||
bs->heap->finish_load();
|
||||
printf("Metadata entries loaded: %ju, used blocks: %ju / %ju\n", entries_loaded, bs->heap->get_data_used_space() / bs->dsk.data_block_size, bs->dsk.block_count);
|
||||
if (zero_on_init && !bs->dsk.disable_meta_fsync)
|
||||
{
|
||||
@@ -284,7 +285,7 @@ resume_6:
|
||||
}, bs->meta_write_recheck_parallelism);
|
||||
return 1;
|
||||
resume_7:
|
||||
if (bs->heap->finish_load() != 0)
|
||||
if (bs->heap->finish_recheck() != 0)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ void blockstore_impl_t::parse_config(blockstore_config_t & config, bool init)
|
||||
throttle_target_parallelism = strtoull(config["throttle_target_parallelism"].c_str(), NULL, 10);
|
||||
throttle_threshold_us = strtoull(config["throttle_threshold_us"].c_str(), NULL, 10);
|
||||
perfect_csum_update = config["perfect_csum_update"] == "true" || config["perfect_csum_update"] == "1" || config["perfect_csum_update"] == "yes";
|
||||
skip_corrupted_meta_entries = config["skip_corrupted_meta_entries"] == "true" || config["skip_corrupted_meta_entries"] == "1" || config["skip_corrupted_meta_entries"] == "yes";
|
||||
if (config["autosync_writes"] != "")
|
||||
{
|
||||
autosync_writes = strtoull(config["autosync_writes"].c_str(), NULL, 10);
|
||||
|
||||
@@ -57,9 +57,9 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
}
|
||||
assert(res == 0);
|
||||
}
|
||||
resume_1:
|
||||
if (priv->modified_block != UINT32_MAX && priv->modified_block2 != priv->modified_block)
|
||||
{
|
||||
resume_1:
|
||||
BS_SUBMIT_CHECK_SQES(1);
|
||||
prepare_meta_block_write(priv->modified_block);
|
||||
resume_2:
|
||||
|
||||
@@ -13,38 +13,36 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
|
||||
void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
|
||||
{
|
||||
auto mod_it = modified_blocks.find(modified_block);
|
||||
if (mod_it != modified_blocks.end())
|
||||
{
|
||||
return !mod_it->second.sent;
|
||||
}
|
||||
if (modified_blocks.find(modified_block) != modified_blocks.end())
|
||||
return;
|
||||
io_uring_sqe *sqe = get_sqe();
|
||||
assert(sqe != NULL);
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, dsk.meta_block_size);
|
||||
data->iov = (struct iovec){ buf, (size_t)dsk.meta_block_size };
|
||||
data->callback = [this, modified_block, buf](ring_data_t *data)
|
||||
data->callback = [this, modified_block](ring_data_t *data)
|
||||
{
|
||||
free(buf);
|
||||
live = true;
|
||||
if (data->res != data->iov.iov_len)
|
||||
{
|
||||
// FIXME: our state becomes corrupted after a write error. maybe do something better than just die
|
||||
disk_error_abort("data write", data->res, data->iov.iov_len);
|
||||
}
|
||||
modified_blocks.erase(modified_block);
|
||||
auto it = modified_blocks.find(modified_block);
|
||||
assert(it != modified_blocks.end());
|
||||
free(it->second.buf);
|
||||
modified_blocks.erase(it);
|
||||
heap->complete_block_write(modified_block);
|
||||
ringloop->wakeup();
|
||||
};
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + (modified_block+1)*dsk.meta_block_size
|
||||
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
|
||||
);
|
||||
unsynced_meta_write_count++;
|
||||
pending_modified_blocks.push_back(modified_block);
|
||||
modified_blocks[modified_block] = { .sent = false, .buf = buf };
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blockstore_impl_t::meta_block_is_pending(uint32_t modified_block)
|
||||
@@ -125,7 +123,6 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
heap_entry_t *obj = heap->read_entry(op->oid);
|
||||
if (op->opcode == BS_OP_DELETE)
|
||||
{
|
||||
return continue_delete(op, 0);
|
||||
// Delete
|
||||
if (!obj || obj->type() == BS_HEAP_DELETE)
|
||||
{
|
||||
@@ -138,38 +135,17 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
BS_SUBMIT_CHECK_SQES(1);
|
||||
int res = heap->add_delete(obj, &PRIV(op)->modified_block);
|
||||
if (res == ENOSPC)
|
||||
{
|
||||
goto enospc;
|
||||
}
|
||||
assert(res == 0);
|
||||
prepare_meta_block_write(PRIV(op)->modified_block);
|
||||
PRIV(op)->pending_ops++;
|
||||
PRIV(op)->op_state = 5;
|
||||
write_iodepth++;
|
||||
resume_1:
|
||||
while (!prepare_meta_block_write(PRIV(op)->modified_block))
|
||||
{
|
||||
PRIV(op)->op_state = 1;
|
||||
return 1;
|
||||
}
|
||||
rseume_2:
|
||||
while (meta_block_is_pending(PRIV(op)->modified_block))
|
||||
{
|
||||
PRIV(op)->op_state = 2;
|
||||
return 1;
|
||||
}
|
||||
resume_3:
|
||||
resume_4:
|
||||
if (!throttle_write(op, 3))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
write_iodepth--;
|
||||
ack_write(op);
|
||||
return 2;
|
||||
}
|
||||
// FIXME: Allow to do initial writes as buffered, not redirected
|
||||
// FIXME: Allow to do direct writes over holes
|
||||
else if (!obj || obj->type() == BS_HEAP_DELETE || op->offset == 0 && op->len == dsk.data_block_size)
|
||||
{
|
||||
return continue_big_write(op, 10);
|
||||
// Big (redirect) write
|
||||
PRIV(op)->write_type = dsk.disable_data_fsync || op->opcode != BS_OP_WRITE_STABLE ? BS_HEAP_BIG_WRITE : _REDIRECT_INTENT;
|
||||
BS_SUBMIT_CHECK_SQES(1);
|
||||
@@ -189,7 +165,6 @@ enospc:
|
||||
flusher->request_trim();
|
||||
return 0;
|
||||
}
|
||||
write_iodepth++;
|
||||
uint64_t loc = PRIV(op)->location;
|
||||
#ifdef BLOCKSTORE_DEBUG
|
||||
printf(
|
||||
@@ -203,72 +178,18 @@ enospc:
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
|
||||
if (PRIV(op)->write_type == BS_HEAP_BIG_WRITE)
|
||||
inflight_big++;
|
||||
PRIV(op)->pending_ops++;
|
||||
resume_10:
|
||||
if (PRIV(op)->pending_ops > 0)
|
||||
{
|
||||
PRIV(op)->op_state = 10;
|
||||
return 1;
|
||||
}
|
||||
write_iodepth++;
|
||||
if (PRIV(op)->write_type == BS_HEAP_BIG_WRITE)
|
||||
{
|
||||
inflight_big--;
|
||||
resume_11:
|
||||
resume_12:
|
||||
resume_13:
|
||||
if (!fsync_big_write(op, 11))
|
||||
return 1;
|
||||
}
|
||||
heap_entry_t *obj = heap->read_entry(op->oid);
|
||||
int res = 0;
|
||||
if (PRIV(op)->write_type == _REDIRECT_INTENT)
|
||||
{
|
||||
res = heap->add_redirect_intent(op->oid, &obj, op->version, op->offset, op->len,
|
||||
PRIV(op)->location, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
|
||||
PRIV(op)->op_state = 1;
|
||||
inflight_big++;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = heap->add_big_write(op->oid, obj, op->opcode == BS_OP_WRITE_STABLE,
|
||||
op->version, op->offset, op->len, PRIV(op)->location, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
|
||||
}
|
||||
if (res == ENOSPC)
|
||||
{
|
||||
if (!heap->get_to_compact_count())
|
||||
{
|
||||
// no space
|
||||
heap->free_data(op->oid.inode, PRIV(op)->location);
|
||||
write_iodepth--;
|
||||
op->retval = -ENOSPC;
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
PRIV(op)->wait_for = WAIT_COMPACTION;
|
||||
PRIV(op)->wait_detail = heap->get_compacted_count();
|
||||
flusher->request_trim();
|
||||
return 0;
|
||||
}
|
||||
assert(res == 0);
|
||||
resume_14:
|
||||
while (!prepare_meta_block_write(PRIV(op)->modified_block))
|
||||
{
|
||||
PRIV(op)->op_state = 14;
|
||||
return 1;
|
||||
}
|
||||
resume_15:
|
||||
while (meta_block_is_pending(PRIV(op)->modified_block))
|
||||
{
|
||||
PRIV(op)->op_state = 15;
|
||||
return 1;
|
||||
}
|
||||
write_iodepth--;
|
||||
ack_write(op);
|
||||
return 2;
|
||||
PRIV(op)->op_state = 3;
|
||||
}
|
||||
else if (intent_write_allowed(op, obj))
|
||||
{
|
||||
return continue_intent_write(op, 20);
|
||||
// Direct intent-write
|
||||
BS_SUBMIT_CHECK_SQES(1);
|
||||
int res = 0;
|
||||
@@ -304,41 +225,13 @@ resume_15:
|
||||
assert(res == 0);
|
||||
PRIV(op)->lsn = obj->lsn;
|
||||
}
|
||||
write_iodepth++;
|
||||
resume_20:
|
||||
while (!prepare_meta_block_write(PRIV(op)->modified_block))
|
||||
{
|
||||
PRIV(op)->op_state = 20;
|
||||
return 1;
|
||||
}
|
||||
resume_21:
|
||||
while (meta_block_is_pending(PRIV(op)->modified_block))
|
||||
{
|
||||
PRIV(op)->op_state = 21;
|
||||
return 1;
|
||||
}
|
||||
// Direct intent-write
|
||||
// LSN is not marked as completed so big_write won't be freed
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + PRIV(op)->location + op->offset);
|
||||
if (dsk.use_atomic_flag)
|
||||
sqe->rw_flags = RWF_ATOMIC;
|
||||
prepare_meta_block_write(PRIV(op)->modified_block);
|
||||
PRIV(op)->pending_ops++;
|
||||
resume_22:
|
||||
if (PRIV(op)->pending_ops > 0)
|
||||
{
|
||||
PRIV(op)->op_state = 22;
|
||||
return 1;
|
||||
}
|
||||
write_iodepth--;
|
||||
ack_write(op);
|
||||
return 2;
|
||||
PRIV(op)->op_state = 9;
|
||||
write_iodepth++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return continue_small_write(op, 30);
|
||||
// Small (buffered) overwrite
|
||||
// First check if there is free buffer space
|
||||
PRIV(op)->write_type = BS_HEAP_SMALL_WRITE;
|
||||
@@ -351,20 +244,19 @@ resume_22:
|
||||
return 0;
|
||||
}
|
||||
// There is sufficient space. Check SQE(s)
|
||||
BS_SUBMIT_CHECK_SQES(1 + (op->len > 0 ? 1 : 0)); ---> refactor too
|
||||
BS_SUBMIT_CHECK_SQES(1 + (op->len > 0 ? 1 : 0));
|
||||
int res = heap->add_small_write(op->oid, &obj, (BS_HEAP_SMALL_WRITE | (op->opcode == BS_OP_WRITE_STABLE ? BS_HEAP_STABLE : 0)),
|
||||
op->version, op->offset, op->len, loc, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
|
||||
if (res == ENOSPC)
|
||||
goto enospc;
|
||||
assert(res == 0);
|
||||
PRIV(op)->lsn = obj->lsn;
|
||||
if (op->len)
|
||||
heap->use_buffer_area(op->oid.inode, loc, op->len);
|
||||
prepare_meta_block_write(PRIV(op)->modified_block);
|
||||
PRIV(op)->pending_ops++;
|
||||
if (op->len > 0)
|
||||
{
|
||||
// Prepare buffered data write
|
||||
heap->use_buffer_area(op->oid.inode, loc, op->len);
|
||||
if (dsk.inmemory_journal)
|
||||
{
|
||||
memcpy((uint8_t*)buffer_area + loc, op->buf, op->len);
|
||||
@@ -420,46 +312,27 @@ again:
|
||||
goto again;
|
||||
}
|
||||
resume_2:
|
||||
|
||||
|
||||
resume_4:
|
||||
resume_6:
|
||||
|
||||
resume_8:
|
||||
ack
|
||||
return 2;
|
||||
resume_10:
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool blockstore_impl_t::fsync_big_write(blockstore_op_t *op, int base_state)
|
||||
{
|
||||
if (PRIV(op)->state == base_state)
|
||||
goto resume_0;
|
||||
else if (PRIV(op)->state == base_state+1)
|
||||
goto resume_1;
|
||||
else if (PRIV(op)->state == base_state+2)
|
||||
goto resume_2;
|
||||
// We must fsync all big writes to avoid complex write workflows
|
||||
// It's OK for all HDDs and for server SSDs, but slightly worse for desktop SSDs
|
||||
inflight_big--;
|
||||
if (!dsk.disable_data_fsync)
|
||||
{
|
||||
// fsync data in a batch
|
||||
resume_0:
|
||||
resume_11:
|
||||
if (inflight_big > 0)
|
||||
{
|
||||
PRIV(op)->op_state = base_state;
|
||||
return false;
|
||||
PRIV(op)->op_state = 11;
|
||||
return 1;
|
||||
}
|
||||
if (fsyncing_data)
|
||||
{
|
||||
resume_1:
|
||||
resume_12:
|
||||
if (fsyncing_data)
|
||||
{
|
||||
PRIV(op)->op_state = base_state+1;
|
||||
return false;
|
||||
PRIV(op)->op_state = 12;
|
||||
return 1;
|
||||
}
|
||||
return true;
|
||||
goto resume_4;
|
||||
}
|
||||
fsyncing_data = true;
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
@@ -471,23 +344,48 @@ resume_1:
|
||||
handle_write_event(data, op);
|
||||
};
|
||||
PRIV(op)->pending_ops++;
|
||||
resume_2:
|
||||
if (PRIV(op)->pending_ops > 0)
|
||||
{
|
||||
PRIV(op)->op_state = base_state+2;
|
||||
return false;
|
||||
}
|
||||
PRIV(op)->op_state = 3;
|
||||
return 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blockstore_impl_t::throttle_write(blockstore_op_t *op, int base_state)
|
||||
{
|
||||
// Apply throttling to not fill the journal too quickly for the SSD+HDD case
|
||||
if (PRIV(op)->op_state >= base_state+1)
|
||||
resume_4:
|
||||
{
|
||||
return true;
|
||||
BS_SUBMIT_CHECK_SQES(1);
|
||||
auto obj = heap->read_entry(op->oid);
|
||||
int res = 0;
|
||||
if (PRIV(op)->write_type == _REDIRECT_INTENT)
|
||||
{
|
||||
res = heap->add_redirect_intent(op->oid, &obj, op->version, op->offset, op->len,
|
||||
PRIV(op)->location, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = heap->add_big_write(op->oid, obj, op->opcode == BS_OP_WRITE_STABLE,
|
||||
op->version, op->offset, op->len, PRIV(op)->location, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
|
||||
}
|
||||
if (res == ENOSPC)
|
||||
{
|
||||
if (!heap->get_to_compact_count())
|
||||
{
|
||||
// no space
|
||||
heap->free_data(op->oid.inode, PRIV(op)->location);
|
||||
write_iodepth--;
|
||||
op->retval = -ENOSPC;
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
PRIV(op)->wait_for = WAIT_COMPACTION;
|
||||
PRIV(op)->wait_detail = heap->get_compacted_count();
|
||||
flusher->request_trim();
|
||||
return 0;
|
||||
}
|
||||
assert(res == 0);
|
||||
prepare_meta_block_write(PRIV(op)->modified_block);
|
||||
PRIV(op)->pending_ops++;
|
||||
PRIV(op)->op_state = 5;
|
||||
return 1;
|
||||
}
|
||||
resume_6:
|
||||
// Apply throttling to not fill the journal too quickly for the SSD+HDD case
|
||||
if (PRIV(op)->write_type == BS_HEAP_SMALL_WRITE && throttle_small_writes)
|
||||
{
|
||||
// Apply throttling
|
||||
@@ -508,21 +406,18 @@ bool blockstore_impl_t::throttle_write(blockstore_op_t *op, int base_state)
|
||||
if (ref_us > exec_us + throttle_threshold_us)
|
||||
{
|
||||
// Pause reply
|
||||
PRIV(op)->op_state = base_state;
|
||||
PRIV(op)->pending_ops++;
|
||||
PRIV(op)->op_state = 7;
|
||||
// Remember that the timer can in theory be called right here
|
||||
tfd->set_timer_us(ref_us-exec_us, false, [this, op](int timer_id)
|
||||
{
|
||||
PRIV(op)->op_state++;
|
||||
PRIV(op)->pending_ops--;
|
||||
ringloop->wakeup();
|
||||
});
|
||||
return false;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_impl_t::ack_write(blockstore_op_t *op)
|
||||
{
|
||||
resume_8:
|
||||
// Acknowledge write
|
||||
#ifdef BLOCKSTORE_DEBUG
|
||||
printf("Ack write %jx:%jx v%ju\n", op->oid.inode, op->oid.stripe, op->version);
|
||||
@@ -549,7 +444,21 @@ void blockstore_impl_t::ack_write(blockstore_op_t *op)
|
||||
unsynced_data_write_count++;
|
||||
intent_write_counter++;
|
||||
}
|
||||
write_iodepth--;
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
resume_10:
|
||||
// Direct intent-write
|
||||
// LSN is not marked as completed so big_write won't be freed
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + PRIV(op)->location + op->offset);
|
||||
if (dsk.use_atomic_flag)
|
||||
sqe->rw_flags = RWF_ATOMIC;
|
||||
PRIV(op)->pending_ops++;
|
||||
PRIV(op)->op_state = 7;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void blockstore_impl_t::handle_write_event(ring_data_t *data, blockstore_op_t *op)
|
||||
|
||||
@@ -480,17 +480,6 @@ resume_1:
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_impl_t::reshard_abort(void *reshard_state)
|
||||
{
|
||||
bs_reshard_state_t *st = (bs_reshard_state_t*)reshard_state;
|
||||
for (auto sh_it = st->old_shards.begin(); sh_it != st->old_shards.end(); sh_it++)
|
||||
{
|
||||
auto & to = clean_db_shards[sh_it->first];
|
||||
to.swap(sh_it->second);
|
||||
}
|
||||
delete st;
|
||||
}
|
||||
|
||||
void blockstore_impl_t::process_list(blockstore_op_t *op)
|
||||
{
|
||||
uint32_t list_pg = op->pg_number+1;
|
||||
|
||||
@@ -290,7 +290,6 @@ public:
|
||||
// Reshard database for a pool
|
||||
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
|
||||
bool reshard_continue(void *reshard_state, uint64_t chunk_limit);
|
||||
void reshard_abort(void *reshard_state);
|
||||
|
||||
// Event loop
|
||||
void loop();
|
||||
|
||||
@@ -189,7 +189,7 @@ resume_1:
|
||||
printf(
|
||||
"Configuration stored in metadata superblock"
|
||||
" (meta_block_size=%u, data_block_size=%u, bitmap_granularity=%u, data_csum_type=%u, csum_block_size=%u)"
|
||||
" differs from OSD configuration (%u/%u/%u, %u/%u).\n",
|
||||
" differs from OSD configuration (%ju/%ju/%u, %u/%u).\n",
|
||||
hdr->meta_block_size, hdr->data_block_size, hdr->bitmap_granularity,
|
||||
hdr->data_csum_type, hdr->csum_block_size,
|
||||
bs->dsk.meta_block_size, bs->dsk.data_block_size, bs->dsk.bitmap_granularity,
|
||||
|
||||
@@ -620,8 +620,7 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
else if (from_journal)
|
||||
{
|
||||
// Don't scan bitmap - journal writes don't have holes (internal bitmap)!
|
||||
uint8_t *csum = !dsk.csum_block_size ? 0 : (clean_entry_bitmap + dsk.clean_entry_bitmap_size +
|
||||
item_start/dsk.csum_block_size*(dsk.data_csum_type & 0xFF));
|
||||
uint8_t *csum = !dsk.csum_block_size ? 0 : (clean_entry_bitmap + dsk.clean_entry_bitmap_size);
|
||||
if (!fulfill_read(read_op, fulfilled, item_start, item_end,
|
||||
(BS_ST_BIG_WRITE | BS_ST_STABLE), 0, clean_loc + item_start, 0, csum, dyn_data))
|
||||
{
|
||||
|
||||
@@ -13,10 +13,10 @@ if (RDMACM_LIBRARIES)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
add_library(vitastor_common STATIC
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp
|
||||
msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
|
||||
msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
|
||||
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
|
||||
)
|
||||
target_link_libraries(vitastor_common pthread)
|
||||
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES})
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client.so
|
||||
@@ -24,6 +24,7 @@ add_library(vitastor_client SHARED
|
||||
cluster_client.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
cluster_client_icache.cpp
|
||||
vitastor_c.cpp
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
|
||||
@@ -33,6 +34,7 @@ target_link_libraries(vitastor_client
|
||||
${LIBURING_LIBRARIES}
|
||||
${IBVERBS_LIBRARIES}
|
||||
${RDMACM_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
|
||||
configure_file(vitastor.pc.in vitastor.pc @ONLY)
|
||||
@@ -98,9 +100,10 @@ endif (${WITH_QEMU})
|
||||
add_executable(test_cluster_client
|
||||
EXCLUDE_FROM_ALL
|
||||
../test/test_cluster_client.cpp
|
||||
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp
|
||||
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp cluster_client_icache.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp
|
||||
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp
|
||||
)
|
||||
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES})
|
||||
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
|
||||
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
|
||||
add_dependencies(build_tests test_cluster_client)
|
||||
|
||||
@@ -62,6 +62,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli.on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
|
||||
st_cli.on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
|
||||
st_cli.on_reload_hook = [this]() { st_cli.load_global_config(); };
|
||||
st_cli.on_inode_change_hook = [this](uint64_t inode, bool removed) { on_change_inode_hook(inode, removed); };
|
||||
|
||||
st_cli.parse_config(config);
|
||||
st_cli.infinite_start = false;
|
||||
@@ -77,6 +78,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
vault_destroy();
|
||||
if (retry_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(retry_timeout_id);
|
||||
@@ -481,6 +483,8 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
self_tree_metrics.clear();
|
||||
client_hostname = new_hostname;
|
||||
}
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config);
|
||||
st_cli.parse_config(config);
|
||||
st_cli.load_pgs();
|
||||
@@ -607,6 +611,9 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
}
|
||||
inode_cache.clear();
|
||||
inode_cache_children.clear();
|
||||
vault_keys.clear();
|
||||
continue_ops();
|
||||
}
|
||||
|
||||
@@ -673,6 +680,10 @@ bool cluster_client_t::flush()
|
||||
{
|
||||
if (!ringloop)
|
||||
{
|
||||
if (vault_loading)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (wb->writeback_queue.size())
|
||||
{
|
||||
wb->start_writebacks(this, 0);
|
||||
@@ -695,7 +706,7 @@ bool cluster_client_t::flush()
|
||||
sync_done = true;
|
||||
};
|
||||
execute(sync);
|
||||
while (!sync_done)
|
||||
while (!sync_done || vault_loading)
|
||||
{
|
||||
ringloop->loop();
|
||||
if (!sync_done)
|
||||
@@ -958,10 +969,40 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
{
|
||||
op->flags |= OP_IMMEDIATE_COMMIT;
|
||||
}
|
||||
bool searched = false;
|
||||
std::shared_ptr<inode_cache_t> icache;
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
|
||||
{
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (icache && icache->op_enc)
|
||||
{
|
||||
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
|
||||
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
|
||||
{
|
||||
auto ino_it = st_cli.inode_config.find(op->inode);
|
||||
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->readonly)
|
||||
{
|
||||
op->retval = -EROFS;
|
||||
auto cb = std::move(op->callback);
|
||||
@@ -972,33 +1013,39 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
op->deoptimise_snapshot = false;
|
||||
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
|
||||
{
|
||||
auto ino_it = st_cli.inode_config.find(op->inode);
|
||||
if (ino_it != st_cli.inode_config.end())
|
||||
if (!searched)
|
||||
{
|
||||
int chain_size = 0;
|
||||
while (ino_it != st_cli.inode_config.end() && ino_it->second.parent_id)
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache)
|
||||
{
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
chain_size > st_cli.inode_config.size())
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (INODE_POOL(ino_it->second.parent_id) == INODE_POOL(ino_it->first) &&
|
||||
wb->has_inode(ino_it->second.parent_id))
|
||||
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
|
||||
{
|
||||
// Deoptimise reads - we have dirty data for one of the parent layer(s).
|
||||
op->deoptimise_snapshot = true;
|
||||
break;
|
||||
}
|
||||
chain_size++;
|
||||
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (icache && icache->err_code)
|
||||
{
|
||||
if (icache->err_code == EPERM)
|
||||
{
|
||||
op->retval = -EPERM;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
else if (icache->err_code == EAGAIN)
|
||||
{
|
||||
key_wait_ops.push_back(op);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1121,31 +1168,33 @@ resume_2:
|
||||
// because if some operations were invalid for the new PG count we'd get errors
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Check parent inode
|
||||
auto ino_it = st_cli.inode_config.find(op->cur_inode);
|
||||
// Skip parents from the same pool
|
||||
int skipped = 0;
|
||||
while (!op->deoptimise_snapshot &&
|
||||
ino_it != st_cli.inode_config.end() && ino_it->second.parent_id &&
|
||||
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
|
||||
uint64_t next_inode = 0;
|
||||
auto icache = inode_cache_get(op->cur_inode);
|
||||
if (icache)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
skipped > st_cli.inode_config.size())
|
||||
if (icache->has_parent_loop)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
skipped++;
|
||||
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
|
||||
if (op->deoptimise_snapshot)
|
||||
{
|
||||
if (icache->chain.size() > 1)
|
||||
next_inode = icache->chain[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (icache->other_pool_parent_id)
|
||||
next_inode = icache->other_pool_parent_id;
|
||||
}
|
||||
}
|
||||
if (ino_it != st_cli.inode_config.end() &&
|
||||
ino_it->second.parent_id &&
|
||||
ino_it->second.parent_id != op->inode)
|
||||
if (next_inode)
|
||||
{
|
||||
// Continue reading from the parent inode
|
||||
op->cur_inode = ino_it->second.parent_id;
|
||||
icache = inode_cache_get(next_inode);
|
||||
op->cur_inode = next_inode;
|
||||
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
|
||||
op->parts.clear();
|
||||
op->done_count = 0;
|
||||
goto resume_0;
|
||||
@@ -1252,7 +1301,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
// Allocate memory for the bitmap
|
||||
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
|
||||
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
|
||||
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
|
||||
unsigned bitmap_mem = object_bitmap_size +
|
||||
op->parts.size() * pg_data_size *
|
||||
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain info - 1 byte per block
|
||||
+ (op->enc ? op->len/pool_cfg.bitmap_granularity : 0));
|
||||
if (!op->bitmap_buf || op->bitmap_buf_size < bitmap_mem)
|
||||
{
|
||||
op->bitmap_buf = realloc_or_die(op->bitmap_buf, bitmap_mem);
|
||||
@@ -1407,9 +1460,9 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
int peer_fd = peer_it->second;
|
||||
part->flags |= PART_SENT|PART_VALID;
|
||||
op->inflight_count++;
|
||||
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
+ (op->opcode == OSD_OP_READ && op->enc ? pool_cfg.data_block_size/pool_cfg.bitmap_granularity : 0));
|
||||
uint64_t meta_rev = 0;
|
||||
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
|
||||
{
|
||||
@@ -1428,6 +1481,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
.inode = op->cur_inode,
|
||||
.offset = part->offset,
|
||||
.len = part->len,
|
||||
.flags = op->opcode == OSD_OP_READ && op->enc && !op->deoptimise_snapshot ? OSD_OP_RETURN_CHAIN : 0,
|
||||
.meta_revision = meta_rev,
|
||||
.version = op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE ? op->version : 0,
|
||||
} },
|
||||
@@ -1435,6 +1489,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
|
||||
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
|
||||
? pg_bitmap_size : 0),
|
||||
.enc = op->enc,
|
||||
.callback = cb ? cb : [this, part](osd_op_t *op_part)
|
||||
{
|
||||
handle_op_part(part);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "messenger.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_OPS 1024
|
||||
@@ -71,6 +72,7 @@ protected:
|
||||
cluster_op_t *prev = NULL, *next = NULL;
|
||||
int prev_wait = 0;
|
||||
uint64_t flush_id = 0;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
friend class cluster_client_t;
|
||||
friend class writeback_cache_t;
|
||||
};
|
||||
@@ -80,6 +82,25 @@ struct inode_list_osd_t;
|
||||
struct inode_list_pg_t;
|
||||
class writeback_cache_t;
|
||||
|
||||
struct inode_cache_t
|
||||
{
|
||||
std::vector<inode_t> chain;
|
||||
uint8_t *key_data = NULL;
|
||||
osd_op_enc_t *op_enc = NULL;
|
||||
bool readonly = false;
|
||||
bool has_parent_loop = false;
|
||||
inode_t other_pool_parent_id = 0;
|
||||
int err_code = 0;
|
||||
|
||||
~inode_cache_t();
|
||||
};
|
||||
|
||||
struct vault_load_key_t
|
||||
{
|
||||
int key_state = 0;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
// FIXME: Split into public and private interfaces
|
||||
class __attribute__((visibility("default"))) cluster_client_t
|
||||
{
|
||||
@@ -89,8 +110,8 @@ public:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
std::map<pool_pg_num_t, osd_num_t> pg_primary;
|
||||
// config:
|
||||
|
||||
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
|
||||
uint64_t client_max_dirty_bytes = 0;
|
||||
uint64_t client_max_dirty_ops = 0;
|
||||
@@ -102,12 +123,23 @@ public:
|
||||
uint64_t client_max_writeback_iodepth = 0;
|
||||
std::string conf_hostname;
|
||||
|
||||
std::string vault_url;
|
||||
std::string vault_client_cert;
|
||||
std::string vault_client_key;
|
||||
std::string vault_ca;
|
||||
std::string vault_secret_api_path;
|
||||
uint64_t vault_timeout_ms = 0;
|
||||
uint64_t vault_error_timeout_sec = 0;
|
||||
uint64_t vault_refresh_leeway_sec = 0;
|
||||
|
||||
int log_level = 0;
|
||||
int client_retry_interval = 50; // ms
|
||||
int client_eio_retry_interval = 1000; // ms
|
||||
bool client_retry_enospc = true;
|
||||
int client_wait_up_timeout = 16; // sec (for listings)
|
||||
|
||||
// state:
|
||||
|
||||
std::string client_hostname;
|
||||
std::map<std::string, int> self_tree_metrics;
|
||||
std::map<osd_num_t, int> osd_tree_metrics;
|
||||
@@ -115,6 +147,7 @@ public:
|
||||
int retry_timeout_id = -1;
|
||||
int retry_timeout_duration = 0;
|
||||
std::vector<cluster_op_t*> offline_ops;
|
||||
std::vector<cluster_op_t*> key_wait_ops;
|
||||
cluster_op_t *op_queue_head = NULL, *op_queue_tail = NULL;
|
||||
writeback_cache_t *wb = NULL;
|
||||
std::set<osd_num_t> dirty_osds;
|
||||
@@ -123,7 +156,22 @@ public:
|
||||
void *scrap_buffer = NULL;
|
||||
unsigned scrap_buffer_size = 0;
|
||||
|
||||
// inodes require some extra state for read/write, it's stored here.
|
||||
// moreover, robin_hood access is slightly faster than std::map :)
|
||||
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
|
||||
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
|
||||
|
||||
http_context_t *vault_http_ctx = NULL;
|
||||
http_co_t *vault_http_cli = NULL;
|
||||
bool vault_loading = false;
|
||||
std::string vault_token;
|
||||
bool vault_auth_error = false;
|
||||
timespec vault_token_expire = {};
|
||||
std::vector<std::string> vault_key_load_queue;
|
||||
std::map<std::string, vault_load_key_t> vault_keys;
|
||||
|
||||
bool pgs_loaded = false;
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
ring_consumer_t consumer;
|
||||
std::vector<std::function<void(void)>> on_ready_hooks;
|
||||
int list_retry_timeout_id = -1;
|
||||
@@ -163,6 +211,13 @@ protected:
|
||||
#endif
|
||||
void continue_ops(int time_passed = 0);
|
||||
|
||||
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
|
||||
void vault_parse_config();
|
||||
bool vault_check_token();
|
||||
void vault_load_keys();
|
||||
void vault_destroy();
|
||||
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
|
||||
|
||||
protected:
|
||||
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
|
||||
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
|
||||
@@ -173,6 +228,7 @@ protected:
|
||||
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
|
||||
void on_change_osd_state_hook(uint64_t peer_osd);
|
||||
void on_change_node_placement_hook();
|
||||
void on_change_inode_hook(uint64_t inode, bool removed);
|
||||
|
||||
void execute_internal(cluster_op_t *op);
|
||||
void execute_cas(cluster_op_t *op);
|
||||
@@ -189,6 +245,7 @@ protected:
|
||||
void erase_op(cluster_op_t *op);
|
||||
void calc_wait(cluster_op_t *op);
|
||||
void inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *next, int inc);
|
||||
|
||||
void continue_lists();
|
||||
bool continue_listing(inode_list_t *lst);
|
||||
bool restart_listing(inode_list_t* lst);
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
#include "cluster_client_impl.h"
|
||||
#include "http_client.h"
|
||||
#include "str_util.h"
|
||||
|
||||
#define VAULT_KEY_NOT_LOADED 0
|
||||
#define VAULT_KEY_LOADING 1
|
||||
#define VAULT_KEY_LOADED 2
|
||||
#define VAULT_KEY_ERROR 3
|
||||
|
||||
inode_cache_t::~inode_cache_t()
|
||||
{
|
||||
if (key_data)
|
||||
{
|
||||
free(key_data);
|
||||
key_data = NULL;
|
||||
op_enc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_destroy()
|
||||
{
|
||||
if (vault_http_ctx)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
http_destroy(vault_http_cli);
|
||||
http_context_destroy(vault_http_ctx);
|
||||
vault_http_cli = NULL;
|
||||
vault_http_ctx = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_config()
|
||||
{
|
||||
vault_url = config["vault_url"].string_value();
|
||||
vault_client_cert = config["vault_client_cert"].string_value();
|
||||
vault_client_key = config["vault_client_key"].string_value();
|
||||
vault_ca = config["vault_ca"].string_value();
|
||||
vault_secret_api_path = "/v1/secret/";
|
||||
if (config["vault_secret_api_path"].is_string())
|
||||
vault_secret_api_path = config["vault_secret_api_path"].string_value();
|
||||
vault_timeout_ms = config["vault_timeout_ms"].uint64_value();
|
||||
if (!vault_timeout_ms)
|
||||
vault_timeout_ms = 5000;
|
||||
vault_error_timeout_sec = config["vault_error_timeout_sec"].uint64_value();
|
||||
if (!vault_error_timeout_sec)
|
||||
vault_error_timeout_sec = 60;
|
||||
vault_refresh_leeway_sec = config["vault_refresh_leeway_sec"].uint64_value();
|
||||
if (!vault_refresh_leeway_sec)
|
||||
vault_refresh_leeway_sec = 60;
|
||||
}
|
||||
|
||||
// FIXME: Rework client API by adding open/close and cache inode information in the "FD" (maybe)
|
||||
void cluster_client_t::on_change_inode_hook(uint64_t inode, bool removed)
|
||||
{
|
||||
std::vector<inode_t> children = { inode };
|
||||
for (size_t i = 0; i < children.size(); i++)
|
||||
{
|
||||
auto it = inode_cache_children.lower_bound(std::make_pair(children[i], (inode_t)0));
|
||||
while (it != inode_cache_children.end() && it->first == children[i])
|
||||
{
|
||||
children.push_back(it->second);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
for (auto & inode: children)
|
||||
{
|
||||
auto it = inode_cache.find(inode);
|
||||
if (it != inode_cache.end())
|
||||
{
|
||||
auto icache = it->second;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
inode_cache_children.erase(std::make_pair(parent, inode));
|
||||
}
|
||||
inode_cache.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
{
|
||||
auto icache_it = inode_cache.find(ino);
|
||||
if (icache_it != inode_cache.end())
|
||||
{
|
||||
return icache_it->second;
|
||||
}
|
||||
// Fill inode cache
|
||||
auto ino_it = st_cli.inode_config.find(ino);
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto pool_it = st_cli.pool_config.find(INODE_POOL(ino));
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto & inode_cfg = ino_it->second;
|
||||
auto & pool_cfg = pool_it->second;
|
||||
std::shared_ptr<inode_cache_t> icache = std::make_shared<inode_cache_t>();
|
||||
icache->readonly = inode_cfg.readonly;
|
||||
icache->chain.push_back(ino);
|
||||
std::vector<inode_config_t*> chain_cfg;
|
||||
// FIXME: Allow unencrypted read & write when all chain is encrypted with the same key
|
||||
int enc_key_count = !inode_cfg.enc_key.empty() ? 1 : 0;
|
||||
if (inode_cfg.parent_id)
|
||||
{
|
||||
// Check for loops and cache the chain
|
||||
robin_hood::unordered_flat_set<inode_t> seen;
|
||||
seen.insert(ino);
|
||||
uint64_t parent_id = inode_cfg.parent_id;
|
||||
while (parent_id)
|
||||
{
|
||||
if (seen.find(parent_id) != seen.end())
|
||||
{
|
||||
icache->has_parent_loop = true;
|
||||
break;
|
||||
}
|
||||
seen.insert(parent_id);
|
||||
ino_it = st_cli.inode_config.find(parent_id);
|
||||
if (INODE_POOL(parent_id) == INODE_POOL(ino))
|
||||
{
|
||||
icache->chain.push_back(parent_id);
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
chain_cfg.push_back(NULL);
|
||||
else
|
||||
{
|
||||
chain_cfg.push_back(&ino_it->second);
|
||||
if (!ino_it->second.enc_key.empty())
|
||||
enc_key_count++;
|
||||
}
|
||||
}
|
||||
else if (!icache->other_pool_parent_id)
|
||||
icache->other_pool_parent_id = parent_id;
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
break;
|
||||
parent_id = ino_it->second.parent_id;
|
||||
}
|
||||
}
|
||||
// Check external keys and wait for loading, if required
|
||||
if (enc_key_count)
|
||||
{
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
|
||||
{
|
||||
auto & ik = vault_keys[inode_cfg.enc_key];
|
||||
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
|
||||
{
|
||||
icache->err_code = EPERM;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
|
||||
{
|
||||
ik.key_state = VAULT_KEY_LOADING;
|
||||
vault_key_load_queue.push_back(inode_cfg.enc_key);
|
||||
vault_load_keys();
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_LOADING)
|
||||
{
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ik.key_state == VAULT_KEY_LOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate encryption key chain, if applicable
|
||||
if (enc_key_count)
|
||||
{
|
||||
uint8_t *key_data = (uint8_t*)malloc_or_die(
|
||||
AES_256_XTS_KEY_SIZE * enc_key_count +
|
||||
sizeof(uint8_t*) * icache->chain.size() +
|
||||
sizeof(osd_op_enc_t)
|
||||
);
|
||||
uint8_t **keys = (uint8_t**)(key_data + AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
osd_op_enc_t *enc = (osd_op_enc_t*)((uint8_t*)keys + sizeof(uint8_t*)*icache->chain.size());
|
||||
size_t key_pos = 0;
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && !cfg->enc_key.empty())
|
||||
{
|
||||
const auto & key = cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX
|
||||
? vault_keys.at(cfg->enc_key).key
|
||||
: cfg->enc_key;
|
||||
assert(key_pos < AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
assert(key.size() == 2*AES_256_XTS_KEY_SIZE);
|
||||
keys[i] = key_data + key_pos;
|
||||
fromhexstr(key, AES_256_XTS_KEY_SIZE, key_data + key_pos);
|
||||
key_pos += AES_256_XTS_KEY_SIZE;
|
||||
}
|
||||
else
|
||||
keys[i] = NULL;
|
||||
}
|
||||
enc->key_chain = keys;
|
||||
enc->chain_size = icache->chain.size();
|
||||
enc->read_chain_bitmap_pos = pool_cfg.data_block_size/pool_cfg.bitmap_granularity/8;
|
||||
enc->bitmap_granularity = pool_cfg.bitmap_granularity;
|
||||
icache->key_data = key_data;
|
||||
icache->op_enc = enc;
|
||||
}
|
||||
inode_cache[ino] = icache;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
if (parent != ino)
|
||||
inode_cache_children.insert(std::make_pair(parent, ino));
|
||||
}
|
||||
return icache;
|
||||
}
|
||||
|
||||
#ifndef __MOCK__
|
||||
bool cluster_client_t::vault_check_token()
|
||||
{
|
||||
timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
if (!vault_token_expire.tv_sec || vault_token_expire.tv_sec < now.tv_sec)
|
||||
{
|
||||
vault_loading = true;
|
||||
http_json_post(
|
||||
vault_http_cli, vault_url+"/v1/auth/cert/login", json11::Json::object{}, "",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this](http_message_t *response)
|
||||
{
|
||||
clock_gettime(CLOCK_REALTIME, &vault_token_expire);
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "Vault request failed: %s\n", err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t ttl = data["auth"]["lease_duration"].uint64_value();
|
||||
vault_token = data["auth"]["client_token"].string_value();
|
||||
if (vault_token.empty() || !ttl)
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "No token or lease_duration in Vault response: %s\n", data.dump().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttl < vault_refresh_leeway_sec)
|
||||
vault_token_expire.tv_sec += ttl/2;
|
||||
else
|
||||
vault_token_expire.tv_sec += ttl - vault_refresh_leeway_sec;
|
||||
}
|
||||
}
|
||||
vault_load_keys();
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (vault_token.empty())
|
||||
{
|
||||
// Auth error happened, mark all loads as failed
|
||||
for (auto & key_id: vault_key_load_queue)
|
||||
{
|
||||
auto & k = vault_keys[key_id];
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
}
|
||||
vault_key_load_queue.clear();
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void cluster_client_t::vault_load_keys()
|
||||
{
|
||||
if (vault_loading || !vault_key_load_queue.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
#ifdef __MOCK__
|
||||
vault_loading = true;
|
||||
#else
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
vault_http_ctx = http_context_init(tfd, vault_client_cert, vault_client_key, vault_ca, true, error);
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context for Vault: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
vault_http_cli = http_init(vault_http_ctx);
|
||||
}
|
||||
if (!vault_check_token())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::string key_id = vault_key_load_queue[0];
|
||||
vault_key_load_queue.erase(vault_key_load_queue.begin());
|
||||
vault_loading = true;
|
||||
http_get(
|
||||
vault_http_cli, vault_url+vault_secret_api_path+key_id.substr(strlen(VAULT_KEY_PREFIX)), "X-Vault-Token: "+vault_token+"\r\n",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this, key_id](http_message_t *response)
|
||||
{
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
vault_parse_secret(key_id, err, data);
|
||||
}
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data)
|
||||
{
|
||||
vault_loading = false;
|
||||
auto & k = vault_keys[key_id];
|
||||
if (err != "")
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault %s%s%s request failed: %s\n", vault_url.c_str(),
|
||||
vault_secret_api_path.c_str(), key_id.c_str()+strlen(VAULT_KEY_PREFIX), err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hexkey = data["data"]["key"].string_value();
|
||||
if (hexkey.empty() || !ishexstr(hexkey) || hexkey.size() != 2*AES_256_XTS_KEY_SIZE)
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault /v1/secret/%s request failed: 'key' is empty or has invalid format\n", key_id.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
k.key_state = VAULT_KEY_LOADED;
|
||||
k.key = hexkey;
|
||||
}
|
||||
}
|
||||
if (vault_key_load_queue.empty())
|
||||
{
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
}
|
||||
else
|
||||
vault_load_keys();
|
||||
}
|
||||
+327
-168
@@ -1,7 +1,10 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "malloc_or_die.h"
|
||||
#include "osd_ops.h"
|
||||
#include "msgr_op.h"
|
||||
#include "pg_states.h"
|
||||
#include "etcd_state_client.h"
|
||||
#ifndef __MOCK__
|
||||
@@ -22,14 +25,19 @@ etcd_state_client_t::~etcd_state_client_t()
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
http_destroy(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (keepalive_client)
|
||||
{
|
||||
http_close(keepalive_client);
|
||||
http_destroy(keepalive_client);
|
||||
keepalive_client = NULL;
|
||||
}
|
||||
if (http_ctx)
|
||||
{
|
||||
http_context_destroy(http_ctx);
|
||||
http_ctx = NULL;
|
||||
}
|
||||
#endif
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
@@ -72,55 +80,51 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
|
||||
return addrs;
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
|
||||
http_context_t *etcd_state_client_t::get_http_ctx()
|
||||
{
|
||||
if (!http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
http_ctx = http_context_init(tfd, etcd_client_cert, etcd_client_key, etcd_ca, true, error);
|
||||
if (!http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return http_ctx;
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_oneshot(const std::string & etcd_url, const std::string & api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"+req;
|
||||
auto http_cli = http_init(tfd);
|
||||
auto cb = [http_cli, callback](const http_response_t *response)
|
||||
auto http_cli = http_init(get_http_ctx());
|
||||
http_json_post(http_cli, etcd_url+api, payload, "", { .timeout = timeout }, [http_cli, callback](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
callback(err, data);
|
||||
http_close(http_cli);
|
||||
};
|
||||
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
|
||||
http_destroy(http_cli);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int timeout,
|
||||
void etcd_state_client_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
pick_next_etcd([=]()
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
etcd_call_selected(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_selected(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
req = "POST "+url.path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+url.hostname+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: keep-alive\r\n"
|
||||
@@ -128,15 +132,15 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
cur_addr = selected_etcd_address](const http_response_t *response)
|
||||
cur_addr = url.addr](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
@@ -164,54 +168,58 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
{
|
||||
keepalive_client = http_init(tfd);
|
||||
}
|
||||
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
|
||||
keepalive_client = http_init(get_http_ctx());
|
||||
http_request(keepalive_client, url.addr, req, { .timeout = timeout, .keepalive = true, .ssl = url.ssl }, cb);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string addr)
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string etcd_address)
|
||||
{
|
||||
if (addr.length() > 0)
|
||||
if (etcd_address.size() > 0)
|
||||
{
|
||||
if (strtolower(addr.substr(0, 7)) == "http://")
|
||||
addr = addr.substr(7);
|
||||
else if (strtolower(addr.substr(0, 8)) == "https://")
|
||||
{
|
||||
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!local_ips.size())
|
||||
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
|
||||
std::string check_addr;
|
||||
int pos = addr.find('/');
|
||||
int pos2 = addr.find(':');
|
||||
if (pos2 >= 0)
|
||||
check_addr = addr.substr(0, pos2);
|
||||
else if (pos >= 0)
|
||||
check_addr = addr.substr(0, pos);
|
||||
else
|
||||
check_addr = addr;
|
||||
if (pos == std::string::npos)
|
||||
addr += "/v3";
|
||||
bool local = false;
|
||||
int i;
|
||||
for (i = 0; i < local_ips.size(); i++)
|
||||
{
|
||||
if (local_ips[i] == check_addr)
|
||||
{
|
||||
local = true;
|
||||
break;
|
||||
}
|
||||
// Fill local_ips
|
||||
for (auto & ip: getifaddr_list(std::vector<addr_mask_t>(), true))
|
||||
local_ips.insert(ip);
|
||||
}
|
||||
auto & to = local ? this->etcd_local : this->etcd_addresses;
|
||||
std::string etcd_api_path;
|
||||
bool ssl = false;
|
||||
if (etcd_address.substr(0, 8) == "https://")
|
||||
{
|
||||
ssl = true;
|
||||
etcd_address = etcd_address.substr(8);
|
||||
}
|
||||
else if (etcd_address.substr(0, 7) == "http://")
|
||||
etcd_address = etcd_address.substr(7);
|
||||
auto pos = etcd_address.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
else
|
||||
etcd_api_path = "/v3";
|
||||
pos = etcd_address.find(':');
|
||||
auto check_addr = (pos != std::string::npos ? etcd_address.substr(0, pos) : etcd_address);
|
||||
bool is_local = local_ips.find(check_addr) != local_ips.end();
|
||||
auto & to = (is_local ? etcd_local : etcd_addresses);
|
||||
check_addr = (ssl ? "https://" : "http://") + etcd_address + etcd_api_path;
|
||||
size_t i;
|
||||
for (i = 0; i < to.size(); i++)
|
||||
{
|
||||
if (to[i] == addr)
|
||||
if (to[i] == check_addr)
|
||||
break;
|
||||
}
|
||||
if (i >= to.size())
|
||||
to.push_back(addr);
|
||||
{
|
||||
to.push_back(check_addr);
|
||||
// Check if it's a domain name
|
||||
sockaddr_storage ss;
|
||||
bool is_name = !is_local && !string_to_addr(etcd_address, true, 0, &ss);
|
||||
auto & to_addr = (is_local ? etcd_local_addr_urls : (is_name ? etcd_name_urls : etcd_nonlocal_addr_urls));
|
||||
to_addr.push_back((http_url_t){ .ssl = ssl, .addr = etcd_address, .hostname = etcd_address, .path = etcd_api_path });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +227,9 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_local.clear();
|
||||
this->etcd_addresses.clear();
|
||||
this->etcd_local_addr_urls.clear();
|
||||
this->etcd_nonlocal_addr_urls.clear();
|
||||
this->etcd_name_urls.clear();
|
||||
if (config["etcd_address"].is_string())
|
||||
{
|
||||
std::string ea = config["etcd_address"].string_value();
|
||||
@@ -239,6 +250,17 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
add_etcd_url(ea.string_value());
|
||||
}
|
||||
}
|
||||
if (this->osd_num)
|
||||
{
|
||||
this->etcd_client_cert = config["osd_etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["osd_etcd_client_key"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->etcd_client_cert = config["etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["etcd_client_key"].string_value();
|
||||
}
|
||||
this->etcd_ca = config["etcd_ca"].string_value();
|
||||
this->etcd_prefix = config["etcd_prefix"].string_value();
|
||||
if (this->etcd_prefix == "")
|
||||
{
|
||||
@@ -282,6 +304,7 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_min_reload_interval = 50;
|
||||
}
|
||||
this->use_image_index_for_auth = config["use_image_index_for_auth"].bool_value();
|
||||
if (this->etcd_ws_keepalive_interval != old_etcd_ws_keepalive_interval && ws_keepalive_timer >= 0)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
@@ -291,66 +314,131 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::pick_next_etcd()
|
||||
{
|
||||
if (selected_etcd_address != "")
|
||||
return;
|
||||
if (addresses_to_try.size() == 0)
|
||||
{
|
||||
// Prefer local etcd, if any
|
||||
for (int i = 0; i < etcd_local.size(); i++)
|
||||
addresses_to_try.push_back(etcd_local[i]);
|
||||
std::vector<int> ns;
|
||||
for (int i = 0; i < etcd_addresses.size(); i++)
|
||||
ns.push_back(i);
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
while (ns.size())
|
||||
{
|
||||
int i = lrand48() % ns.size();
|
||||
addresses_to_try.push_back(etcd_addresses[ns[i]]);
|
||||
ns.erase(ns.begin()+i, ns.begin()+i+1);
|
||||
}
|
||||
}
|
||||
selected_etcd_address = addresses_to_try[0];
|
||||
addresses_to_try.erase(addresses_to_try.begin(), addresses_to_try.begin()+1);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher()
|
||||
void etcd_state_client_t::pick_next_etcd(std::function<void()> cb)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
if (selected_etcd_url.addr != "")
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
if (etcd_urls_to_try.size() != 0)
|
||||
{
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
on_resolve_queue.push_back(std::move(cb));
|
||||
if (on_resolve_queue.size() > 1)
|
||||
{
|
||||
// Already resolving
|
||||
return;
|
||||
}
|
||||
assert(!resolve_count);
|
||||
local_to_try = 0;
|
||||
for (auto & url: etcd_local_addr_urls)
|
||||
{
|
||||
// Prefer local IPs, if any
|
||||
etcd_urls_to_try.push_back(url);
|
||||
local_to_try++;
|
||||
}
|
||||
for (auto & url: etcd_nonlocal_addr_urls)
|
||||
{
|
||||
etcd_urls_to_try.push_back(url);
|
||||
}
|
||||
resolve_count++;
|
||||
for (auto & url: etcd_name_urls)
|
||||
{
|
||||
resolve_count++;
|
||||
http_resolve(get_http_ctx(), url.ssl, url.addr, [this, url](const std::string & error, const std::vector<std::string>& addresses)
|
||||
{
|
||||
if (error != "")
|
||||
fprintf(stderr, "Error resolving %s: %s\n", url.addr.c_str(), error.c_str());
|
||||
for (auto & addr: addresses)
|
||||
{
|
||||
auto url_copy = url;
|
||||
url_copy.addr = addr;
|
||||
if (local_ips.find(addr) != local_ips.end())
|
||||
{
|
||||
etcd_urls_to_try.insert(etcd_urls_to_try.begin(), std::move(url_copy));
|
||||
local_to_try++;
|
||||
}
|
||||
else
|
||||
etcd_urls_to_try.push_back(std::move(url_copy));
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
pick_next_etcd_on_resolve();
|
||||
});
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
{
|
||||
pick_next_etcd_on_resolve();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::pick_next_etcd_on_resolve()
|
||||
{
|
||||
if (!etcd_urls_to_try.size())
|
||||
{
|
||||
fprintf(stderr, "None of etcd_address could be resolved\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
// Shuffle addresses
|
||||
for (size_t i = etcd_urls_to_try.size()-1; i > local_to_try; i--)
|
||||
{
|
||||
size_t j = local_to_try + lrand48() % (i - local_to_try);
|
||||
if (j != i)
|
||||
std::swap(etcd_urls_to_try[i], etcd_urls_to_try[j]);
|
||||
}
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
auto cbs = std::move(on_resolve_queue);
|
||||
for (auto cb: cbs)
|
||||
{
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher()
|
||||
{
|
||||
pick_next_etcd([this]()
|
||||
{
|
||||
start_etcd_watcher_selected();
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher_selected()
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
etcd_watches_initialised = 0;
|
||||
ws_alive = 1;
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (this->log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s, watch from revision %ju/%ju/%ju\n", etcd_address.c_str(),
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s%s%s (hostname %s), watch from revision %ju/%ju/%ju\n",
|
||||
url.ssl ? "https://" : "http://", url.addr.c_str(), url.path.c_str(), url.hostname.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
etcd_watch_ws = open_websocket(tfd, etcd_address, etcd_api_path+"/watch", etcd_slow_timeout,
|
||||
[this, cur_addr = selected_etcd_address](const http_response_t *msg)
|
||||
if (!etcd_watch_ws)
|
||||
etcd_watch_ws = http_init(get_http_ctx());
|
||||
else
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_total_watches = (use_image_index_for_auth ? 4 : 3);
|
||||
open_websocket(etcd_watch_ws, url.addr, url.hostname, url.path+"/watch", { .timeout = etcd_slow_timeout, .ssl = url.ssl },
|
||||
[this, cur_addr = url.addr](http_message_t *msg)
|
||||
{
|
||||
if (msg->body.length())
|
||||
{
|
||||
@@ -368,11 +456,12 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
{
|
||||
if (watch_id == ETCD_CONFIG_WATCH_ID ||
|
||||
watch_id == ETCD_PG_STATE_WATCH_ID ||
|
||||
watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
watch_id == ETCD_OSD_STATE_WATCH_ID ||
|
||||
watch_id == ETCD_INDEX_WATCH_ID)
|
||||
{
|
||||
etcd_watches_initialised++;
|
||||
}
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && this->log_level > 0)
|
||||
if (etcd_watches_initialised == etcd_total_watches && this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Successfully subscribed to etcd at %s, revision %ju/%ju/%ju\n", cur_addr.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
@@ -393,8 +482,7 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = etcd_watch_revision_index = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
return;
|
||||
@@ -414,7 +502,10 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
}
|
||||
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
|
||||
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && !data["result"]["header"]["revision"].is_null())
|
||||
// Also don't save revision from the initial created: true messages because they always contain the latest revision
|
||||
if (etcd_watches_initialised == etcd_total_watches &&
|
||||
!data["result"]["header"]["revision"].is_null() &&
|
||||
!data["result"]["created"].bool_value())
|
||||
{
|
||||
// Restart watchers from the same revision number as in the last received message,
|
||||
// not from the next one to protect against revision being split into multiple messages,
|
||||
@@ -435,7 +526,9 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
addresses_to_try.clear();
|
||||
else if (watch_id == ETCD_INDEX_WATCH_ID)
|
||||
etcd_watch_revision_index = watch_rev;
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
@@ -465,13 +558,8 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (etcd_watches_initialised == 0)
|
||||
{
|
||||
// Connection not established, retry in <etcd_quick_timeout>
|
||||
@@ -515,6 +603,18 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
if (use_image_index_for_auth)
|
||||
{
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/index/image/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/index/image0") },
|
||||
{ "start_revision", etcd_watch_revision_index },
|
||||
{ "watch_id", ETCD_INDEX_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
}
|
||||
// FIXME: Do not watch /pg/history/ at all in client code (not in OSD)
|
||||
if (on_start_watcher_hook)
|
||||
{
|
||||
@@ -538,7 +638,7 @@ void etcd_state_client_t::start_ws_keepalive()
|
||||
{
|
||||
ws_keepalive_timer = tfd->set_timer(etcd_ws_keepalive_interval*1000, true, [this](int)
|
||||
{
|
||||
if (!etcd_watch_ws || etcd_watches_initialised < ETCD_TOTAL_WATCHES)
|
||||
if (!etcd_watch_ws || etcd_watches_initialised < etcd_total_watches)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
@@ -546,12 +646,7 @@ void etcd_state_client_t::start_ws_keepalive()
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_address.c_str());
|
||||
}
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
@@ -689,6 +784,15 @@ void etcd_state_client_t::load_pgs()
|
||||
} }
|
||||
},
|
||||
};
|
||||
if (use_image_index_for_auth)
|
||||
{
|
||||
txn.push_back(json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/index/image/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/index/image0") },
|
||||
} }
|
||||
});
|
||||
}
|
||||
json11::Json::object req = { { "success", txn } };
|
||||
json11::Json checks = load_pgs_checks_hook != NULL ? load_pgs_checks_hook() : json11::Json();
|
||||
if (checks.array_items().size() > 0)
|
||||
@@ -1262,33 +1366,40 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
}
|
||||
else
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
|
||||
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/index/image/")
|
||||
{
|
||||
// <etcd_prefix>/index/image/%s
|
||||
// Used for environments based on etcd authentication to grant client image access
|
||||
// by names - granting by ID is inconvenient because of snapshots
|
||||
std::string inode_name = key.substr(etcd_prefix.length()+13);
|
||||
if (!value.is_object())
|
||||
{
|
||||
auto n_it = this->inode_by_name.find(inode_name);
|
||||
if (n_it != this->inode_by_name.end())
|
||||
{
|
||||
uint64_t inode_num = n_it->second;
|
||||
if (on_inode_change_hook != NULL)
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num |= pool_id << (64-POOL_ID_BITS);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %ju/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
inode_num >> (64-POOL_ID_BITS), inode_num & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
on_inode_change_hook(inode_num, true);
|
||||
}
|
||||
insert_inode_config((inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = kv.mod_revision,
|
||||
});
|
||||
this->inode_config.erase(inode_num);
|
||||
this->inode_by_name.erase(n_it);
|
||||
}
|
||||
}
|
||||
else if (value["name"] == inode_name)
|
||||
{
|
||||
uint64_t inode_num = INODE_WITH_POOL(value["pool_id"].uint64_value(), value["id"].uint64_value());
|
||||
if (!INODE_POOL(inode_num) || !INODE_NO_POOL(inode_num))
|
||||
{
|
||||
fprintf(stderr, "Invalid inode ID in key %s: %ju\n", key.c_str(), inode_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
insert_inode_config(deserialize_inode_cfg(inode_num, value, kv.mod_revision));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1377,6 +1488,10 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
new_cfg["parent_pool"] = (uint64_t)INODE_POOL(cfg->parent_id);
|
||||
new_cfg["parent_id"] = (uint64_t)INODE_NO_POOL(cfg->parent_id);
|
||||
}
|
||||
if (!cfg->enc_key.empty())
|
||||
{
|
||||
new_cfg["enc_key"] = cfg->enc_key;
|
||||
}
|
||||
if (cfg->readonly)
|
||||
{
|
||||
new_cfg["readonly"] = true;
|
||||
@@ -1392,6 +1507,50 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
return new_cfg;
|
||||
}
|
||||
|
||||
inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision)
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !INODE_POOL(parent_inode_num))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num = INODE_WITH_POOL(INODE_POOL(inode_num), parent_inode_num);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %u/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
std::string enc_key;
|
||||
if (!value["enc_key"].is_null())
|
||||
{
|
||||
enc_key = value["enc_key"].string_value();
|
||||
if (enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) != VAULT_KEY_PREFIX &&
|
||||
(enc_key.size() != 2*AES_256_XTS_KEY_SIZE || !ishexstr(enc_key)))
|
||||
{
|
||||
enc_key = "";
|
||||
fprintf(stderr, "Inode %u/%ju has invalid enc_key, should be %u bit hex string or Vault key reference\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num), AES_256_XTS_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
return (inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.enc_key = std::move(enc_key),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = mod_revision,
|
||||
};
|
||||
}
|
||||
|
||||
int etcd_state_client_t::address_count()
|
||||
{
|
||||
return etcd_addresses.size() + etcd_local.size();
|
||||
|
||||
@@ -4,21 +4,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "json11/json11.hpp"
|
||||
#include "osd_id.h"
|
||||
#include "object_id.h"
|
||||
#include "timerfd_manager.h"
|
||||
|
||||
#define ETCD_CONFIG_WATCH_ID 1
|
||||
#define ETCD_OSD_STATE_WATCH_ID 2
|
||||
#define ETCD_PG_STATE_WATCH_ID 3
|
||||
#define ETCD_TOTAL_WATCHES 3
|
||||
#define ETCD_INDEX_WATCH_ID 4
|
||||
|
||||
#define DEFAULT_BLOCK_SIZE 128*1024
|
||||
#define MIN_DATA_BLOCK_SIZE 4*1024
|
||||
#define MAX_DATA_BLOCK_SIZE 128*1024*1024
|
||||
#define DEFAULT_BITMAP_GRANULARITY 4096
|
||||
|
||||
#define VAULT_KEY_PREFIX "vault:"
|
||||
|
||||
#ifndef IMMEDIATE_NONE
|
||||
#define IMMEDIATE_NONE 0
|
||||
#define IMMEDIATE_SMALL 1
|
||||
@@ -83,6 +86,7 @@ struct inode_config_t
|
||||
inode_t parent_id = 0;
|
||||
bool readonly = false;
|
||||
bool deleted = false;
|
||||
std::string enc_key;
|
||||
// Arbitrary metadata
|
||||
json11::Json meta;
|
||||
// Change revision of the metadata in etcd
|
||||
@@ -95,23 +99,42 @@ struct inode_watch_t
|
||||
inode_config_t cfg = {};
|
||||
};
|
||||
|
||||
struct http_url_t
|
||||
{
|
||||
bool ssl;
|
||||
std::string addr;
|
||||
std::string hostname;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct http_co_t;
|
||||
struct http_context_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
std::vector<std::string> local_ips;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::set<std::string> local_ips;
|
||||
std::vector<std::string> etcd_local;
|
||||
std::string selected_etcd_address;
|
||||
std::vector<std::string> addresses_to_try;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::vector<http_url_t> etcd_local_addr_urls;
|
||||
std::vector<http_url_t> etcd_nonlocal_addr_urls;
|
||||
std::vector<http_url_t> etcd_name_urls;
|
||||
size_t local_to_try = 0;
|
||||
std::vector<http_url_t> etcd_urls_to_try;
|
||||
http_url_t selected_etcd_url;
|
||||
size_t resolve_count = 0;
|
||||
std::vector<inode_watch_t*> watches;
|
||||
std::vector<std::function<void()>> on_resolve_queue;
|
||||
bool new_pg_config = false;
|
||||
bool use_image_index_for_auth = false;
|
||||
int ws_keepalive_timer = -1;
|
||||
int ws_alive = 0;
|
||||
bool rand_initialized = false;
|
||||
void add_etcd_url(std::string);
|
||||
void pick_next_etcd();
|
||||
void pick_next_etcd(std::function<void()> cb);
|
||||
void pick_next_etcd_on_resolve();
|
||||
void etcd_call_selected(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void start_etcd_watcher_selected();
|
||||
public:
|
||||
int etcd_keepalive_timeout = 30;
|
||||
int etcd_ws_keepalive_interval = 5;
|
||||
@@ -124,15 +147,22 @@ public:
|
||||
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
||||
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
||||
|
||||
uint64_t osd_num = 0;
|
||||
std::string etcd_prefix;
|
||||
std::string etcd_client_cert;
|
||||
std::string etcd_client_key;
|
||||
std::string etcd_ca;
|
||||
int log_level = 0;
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
|
||||
http_context_t *http_ctx = NULL;
|
||||
http_co_t *etcd_watch_ws = NULL, *keepalive_client = NULL;
|
||||
int etcd_watches_initialised = 0;
|
||||
int etcd_total_watches = 0;
|
||||
uint64_t etcd_watch_revision_config = 0;
|
||||
uint64_t etcd_watch_revision_osd = 0;
|
||||
uint64_t etcd_watch_revision_pg = 0;
|
||||
uint64_t etcd_watch_revision_index = 0;
|
||||
timespec etcd_last_reload = {};
|
||||
int load_pgs_timer_id = -1;
|
||||
std::map<pool_id_t, pool_config_t> pool_config;
|
||||
@@ -158,10 +188,12 @@ public:
|
||||
std::function<void(http_co_t *)> on_start_watcher_hook;
|
||||
|
||||
json11::Json::object serialize_inode_cfg(inode_config_t *cfg);
|
||||
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
|
||||
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
|
||||
std::vector<std::string> get_addresses();
|
||||
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
http_context_t *get_http_ctx();
|
||||
void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_txn(json11::Json txn, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_txn_slow(json11::Json txn, std::function<void(std::string, json11::Json)> callback);
|
||||
void start_etcd_watcher();
|
||||
|
||||
+681
-87
File diff suppressed because it is too large
Load Diff
@@ -17,14 +17,19 @@
|
||||
|
||||
class timerfd_manager_t;
|
||||
|
||||
#pragma GCC visibility push(default)
|
||||
|
||||
struct http_options_t
|
||||
{
|
||||
int timeout;
|
||||
bool want_streaming;
|
||||
bool keepalive;
|
||||
bool ssl;
|
||||
};
|
||||
|
||||
struct http_response_t
|
||||
struct http_context_t;
|
||||
|
||||
struct http_message_t
|
||||
{
|
||||
std::string error;
|
||||
|
||||
@@ -41,10 +46,25 @@ struct http_response_t
|
||||
// Opened websocket or keepalive HTTP connection
|
||||
struct http_co_t;
|
||||
|
||||
http_co_t* http_init(timerfd_manager_t *tfd);
|
||||
http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, const std::string & path,
|
||||
int timeout, std::function<void(const http_response_t *msg)> on_message);
|
||||
http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key,
|
||||
const std::string & ssl_ca, bool verify_peer, std::string & error);
|
||||
void http_resolve(http_context_t *ctx, bool ssl, std::string host,
|
||||
std::function<void(const std::string & error, const std::vector<std::string> & addrs)> cb);
|
||||
void http_context_destroy(http_context_t *ctx);
|
||||
http_co_t* http_init(http_context_t *ctx = NULL);
|
||||
void open_websocket(http_co_t *handler, const std::string & addr, const std::string & hostname, const std::string & path,
|
||||
const http_options_t & options, std::function<void(http_message_t *msg)> on_message);
|
||||
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
|
||||
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_get(http_co_t *handler, const std::string & url, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_json_post(http_co_t *handler, const std::string & url, json11::Json body, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_post_message(http_co_t *handler, uint8_t type, const std::string & msg);
|
||||
void http_serve(http_co_t *handler, int peer_fd, const http_options_t & options,
|
||||
std::function<void(http_message_t *msg)> request_callback);
|
||||
void http_reply(http_co_t *handler, const std::string & reply);
|
||||
void http_close(http_co_t *co);
|
||||
void http_destroy(http_co_t *co);
|
||||
|
||||
#pragma GCC visibility pop
|
||||
|
||||
+14
-12
@@ -145,7 +145,7 @@ void osd_messenger_t::init()
|
||||
rdma_contexts = msgr_rdma_context_t::create_all(
|
||||
osd_num && osd_cluster_network_masks.size() ? osd_cluster_network_masks : osd_network_masks,
|
||||
rdma_device != "" ? rdma_device.c_str() : NULL,
|
||||
rdma_port_num, rdma_gid_index, rdma_mtu, rdma_odp, log_level
|
||||
rdma_port_num, rdma_gid_index, rdma_mtu, log_level
|
||||
);
|
||||
if (!rdma_contexts.size())
|
||||
{
|
||||
@@ -289,6 +289,14 @@ osd_messenger_t::~osd_messenger_t()
|
||||
rdmacm_evch = NULL;
|
||||
}
|
||||
#endif
|
||||
for (auto encrypt_ctx: encrypt_ctx_pool)
|
||||
{
|
||||
destroy_aes_xts_encrypt(encrypt_ctx);
|
||||
}
|
||||
for (auto decrypt_ctx: decrypt_ctx_pool)
|
||||
{
|
||||
destroy_aes_xts_decrypt(decrypt_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
@@ -322,8 +330,10 @@ void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
this->rdma_max_msg = config["rdma_max_msg"].uint64_value();
|
||||
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
|
||||
this->rdma_max_msg = 129*1024;
|
||||
this->rdma_odp = config["rdma_odp"].bool_value();
|
||||
#endif
|
||||
this->max_aes_xts_pool_size = config["max_aes_xts_pool_size"].uint64_value();
|
||||
if (!this->max_aes_xts_pool_size)
|
||||
this->max_aes_xts_pool_size = 256;
|
||||
if (!osd_num)
|
||||
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
|
||||
else
|
||||
@@ -503,7 +513,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
|
||||
clients[peer_fd]->peer_state = PEER_CONNECTING;
|
||||
clients[peer_fd]->connect_timeout_id = -1;
|
||||
clients[peer_fd]->osd_num = peer_osd;
|
||||
clients[peer_fd]->in_buf = malloc_or_die(receive_buffer_size);
|
||||
clients[peer_fd]->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
tfd->set_fd_handler(peer_fd, true, [this](int peer_fd, int epoll_events)
|
||||
{
|
||||
// Either OUT (connected) or HUP
|
||||
@@ -740,14 +750,6 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
fprintf(stderr, "Connected to OSD %ju using RDMA\n", cl->osd_num);
|
||||
}
|
||||
cl->peer_state = PEER_RDMA;
|
||||
tfd->set_fd_handler(cl->peer_fd, false, [this](int peer_fd, int epoll_events)
|
||||
{
|
||||
// Do not miss the disconnection!
|
||||
if (epoll_events & EPOLLRDHUP)
|
||||
{
|
||||
handle_peer_epoll(peer_fd, epoll_events);
|
||||
}
|
||||
});
|
||||
// Add the initial receive request
|
||||
init_recv_rdma(cl);
|
||||
}
|
||||
@@ -782,7 +784,7 @@ void osd_messenger_t::accept_connections(int listen_fd)
|
||||
cl->peer_port = ntohs(((sockaddr_in*)&addr)->sin_port);
|
||||
cl->peer_fd = peer_fd;
|
||||
cl->peer_state = PEER_CONNECTED;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
// Add FD to epoll
|
||||
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
|
||||
{
|
||||
|
||||
+43
-15
@@ -34,9 +34,6 @@
|
||||
|
||||
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
|
||||
|
||||
#define MSGR_SENDP_HDR 1
|
||||
#define MSGR_SENDP_FREE 2
|
||||
|
||||
struct msgr_sendp_t
|
||||
{
|
||||
osd_op_t *op;
|
||||
@@ -48,6 +45,11 @@ struct msgr_rdma_connection_t;
|
||||
struct msgr_rdma_context_t;
|
||||
#endif
|
||||
|
||||
struct op_aes_xts_encrypt_t;
|
||||
struct op_aes_xts_decrypt_t;
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
|
||||
|
||||
struct osd_client_t
|
||||
{
|
||||
int refs = 0;
|
||||
@@ -63,7 +65,7 @@ struct osd_client_t
|
||||
osd_num_t in_osd_num = 0;
|
||||
bool is_incoming = false;
|
||||
|
||||
void *in_buf = NULL;
|
||||
uint8_t *in_buf = NULL;
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
msgr_rdma_connection_t *rdma_conn = NULL;
|
||||
@@ -72,14 +74,18 @@ struct osd_client_t
|
||||
// Read state
|
||||
int read_ready = 0;
|
||||
osd_op_t *read_op = NULL;
|
||||
size_t read_op_size = 0;
|
||||
size_t read_op_pos = 0;
|
||||
iovec read_iov = { 0 };
|
||||
msghdr read_msg = { 0 };
|
||||
int read_remaining = 0;
|
||||
int read_state = 0;
|
||||
osd_op_buf_list_t recv_list;
|
||||
std::vector<iovec> recv_list;
|
||||
size_t recv_list_size = 0;
|
||||
uint64_t read_op_id = 1;
|
||||
bool check_sequencing = false;
|
||||
bool enable_pg_locks = false;
|
||||
op_aes_xts_decrypt_t *decrypt_ctx = NULL;
|
||||
size_t read_op_inline_decrypt_pos = 0;
|
||||
size_t read_op_inline_decrypt_in = 0;
|
||||
|
||||
// Incoming operations
|
||||
std::vector<osd_op_t*> received_ops;
|
||||
@@ -92,11 +98,16 @@ struct osd_client_t
|
||||
std::set<pool_pg_num_t> dirty_pgs;
|
||||
|
||||
// Write state
|
||||
std::deque<osd_op_t *> write_ops;
|
||||
osd_op_t *write_op = NULL;
|
||||
size_t write_op_pos = 0;
|
||||
msghdr write_msg = { 0 };
|
||||
int write_state = 0;
|
||||
std::vector<iovec> send_list, next_send_list;
|
||||
std::vector<msgr_sendp_t> outbox, next_outbox;
|
||||
std::vector<iovec> send_list;
|
||||
size_t send_list_size = 0;
|
||||
std::deque<osd_op_t*> send_free_ops;
|
||||
std::vector<osd_op_t*> zc_free_list;
|
||||
op_aes_xts_encrypt_t *encrypt_ctx = NULL;
|
||||
|
||||
~osd_client_t();
|
||||
void cancel_ops();
|
||||
@@ -188,6 +199,7 @@ protected:
|
||||
bool use_sync_send_recv = false;
|
||||
int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE;
|
||||
int iothread_count = 0;
|
||||
int max_aes_xts_pool_size = 256;
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
bool use_rdma = true;
|
||||
@@ -200,7 +212,6 @@ protected:
|
||||
std::vector<msgr_rdma_context_t *> rdma_contexts;
|
||||
uint64_t rdma_max_sge = 0, rdma_max_send = 0, rdma_max_recv = 0;
|
||||
uint64_t rdma_max_msg = 0;
|
||||
bool rdma_odp = false;
|
||||
rdma_event_channel *rdmacm_evch = NULL;
|
||||
std::map<rdma_cm_id*, osd_client_t*> rdmacm_connections;
|
||||
std::map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
|
||||
@@ -210,7 +221,10 @@ protected:
|
||||
std::vector<int> read_ready_clients;
|
||||
std::vector<int> write_ready_clients;
|
||||
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
|
||||
std::vector<osd_op_t*> set_immediate_ops;
|
||||
std::deque<osd_op_t*> set_immediate_ops;
|
||||
|
||||
std::vector<op_aes_xts_encrypt_t*> encrypt_ctx_pool;
|
||||
std::vector<op_aes_xts_decrypt_t*> decrypt_ctx_pool;
|
||||
|
||||
public:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
@@ -275,9 +289,24 @@ protected:
|
||||
|
||||
bool try_send(osd_client_t *cl);
|
||||
void handle_send(int result, bool prev, bool more, osd_client_t *cl);
|
||||
size_t op_copy_to(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
void op_get_write_buffers(osd_client_t *cl, std::vector<iovec> & lst);
|
||||
|
||||
void handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
|
||||
bool handle_hdr(osd_client_t *cl);
|
||||
bool allocate_op_buffers(osd_client_t *cl);
|
||||
bool allocate_reply_buffers(osd_client_t *cl, osd_op_t *op);
|
||||
size_t op_copy_from(osd_client_t *cl, uint8_t *src, size_t src_len, size_t & done);
|
||||
size_t op_get_read_buffers(osd_client_t *cl, std::vector<iovec> & lst);
|
||||
void handle_finished_op(osd_client_t *cl);
|
||||
|
||||
bool op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done);
|
||||
bool op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done);
|
||||
void op_decrypt_start(osd_client_t* cl);
|
||||
void op_decrypt_inline(osd_client_t* cl);
|
||||
void op_decrypt_free(osd_client_t* cl);
|
||||
|
||||
bool handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, void *curbuf, int remain);
|
||||
bool handle_finished_read(osd_client_t *cl);
|
||||
void handle_op_hdr(osd_client_t *cl);
|
||||
bool handle_reply_hdr(osd_client_t *cl);
|
||||
@@ -287,8 +316,7 @@ protected:
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
void try_send_rdma(osd_client_t *cl);
|
||||
void try_send_rdma_odp(osd_client_t *cl);
|
||||
void try_send_rdma_nodp(osd_client_t *cl);
|
||||
int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len);
|
||||
bool init_recv_rdma(osd_client_t *cl);
|
||||
void handle_rdma_events(msgr_rdma_context_t *rdma_context);
|
||||
msgr_rdma_context_t* choose_rdma_context(osd_client_t *cl);
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
#include "messenger.h"
|
||||
#include "msgr_encrypt.h"
|
||||
|
||||
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
if (!(ctx = EVP_CIPHER_CTX_new()))
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
|
||||
{
|
||||
assert(!encrypted);
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!encrypted);
|
||||
this->start_offset = start_offset;
|
||||
this->key = key;
|
||||
this->block_size = block_size;
|
||||
this->offset = 0;
|
||||
this->encrypted = false;
|
||||
this->tmp_pos = 0;
|
||||
if (tmp && tmp_size != block_size)
|
||||
{
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
tmp_size = 0;
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
int actual_out = 0;
|
||||
if (EVP_EncryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
// FIXME: Copy-paste
|
||||
void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
|
||||
{
|
||||
// Fucking AES-XTS implementations (all of them) don't have streaming support,
|
||||
// crafting IV to resume encryption is slow, so we have to accumulate a full block
|
||||
// and encrypt it at once :-(
|
||||
// And then we have to support consuming it in parts because it's simpler for the
|
||||
// higher layers.
|
||||
if (encrypted)
|
||||
{
|
||||
// Copy accumulated and encrypted output
|
||||
assert(tmp);
|
||||
if (max_out > block_size - tmp_pos)
|
||||
max_out = block_size - tmp_pos;
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
encrypted = false;
|
||||
}
|
||||
else if (max_in < block_size - offset%block_size)
|
||||
{
|
||||
// Just accumulate input
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
}
|
||||
else if (max_out < block_size)
|
||||
{
|
||||
// Accumulate and encrypt input in <tmp>, then copy part of it to <out>
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, tmp);
|
||||
encrypted = true;
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
encrypt_block(in, out);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
done_out += block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accumulate input and encrypt directly to <output>
|
||||
assert(tmp);
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, out);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
|
||||
{
|
||||
delete encrypt_ctx;
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
if (!(ctx = EVP_CIPHER_CTX_new()))
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
|
||||
{
|
||||
assert(!decrypted);
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, uint8_t *key_indexes, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!decrypted);
|
||||
this->start_offset = start_offset;
|
||||
this->key_chain = chain_size > 1 ? key_chain : 0;
|
||||
this->chain_size = chain_size > 1 ? chain_size : 0;
|
||||
this->key_indexes = chain_size > 1 ? key_indexes : NULL;
|
||||
assert(chain_size <= 1 || key_indexes != NULL);
|
||||
this->block_size = block_size;
|
||||
this->offset = 0;
|
||||
this->tmp_pos = 0;
|
||||
if (tmp && tmp_size != block_size)
|
||||
{
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
tmp_size = 0;
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
if (chain_size == 1 && key_chain[0] && EVP_DecryptInit_ex(ctx, NULL, NULL, key_chain[0], NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
if (chain_size)
|
||||
{
|
||||
assert(key_indexes[offset/block_size] < chain_size);
|
||||
key = key_chain[key_indexes[offset/block_size]];
|
||||
if (!key)
|
||||
{
|
||||
if (in != out)
|
||||
memcpy(out, in, block_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
int actual_out = 0;
|
||||
if (EVP_DecryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
|
||||
{
|
||||
// Fucking AES-XTS implementations (all of them) don't have streaming support,
|
||||
// crafting IV to resume decryption is slow, so we have to accumulate a full block
|
||||
// and decrypt it at once :-(
|
||||
// And then we have to support consuming it in parts because clients sometimes need
|
||||
// fragmented output.
|
||||
if (decrypted)
|
||||
{
|
||||
// Copy accumulated and decrypted output
|
||||
assert(tmp);
|
||||
if (max_out > block_size - tmp_pos)
|
||||
max_out = block_size - tmp_pos;
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
decrypted = false;
|
||||
}
|
||||
else if (max_in < block_size - offset%block_size)
|
||||
{
|
||||
// Just accumulate input
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
}
|
||||
else if (max_out < block_size)
|
||||
{
|
||||
// Accumulate and decrypt input in <tmp>, then copy part of it to <out>
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
decrypt_block(tmp, tmp);
|
||||
decrypted = true;
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
decrypt_block(in, out);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
done_out += block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accumulate input and decrypt directly to <output>
|
||||
assert(tmp);
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
decrypt_block(tmp, out);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
|
||||
{
|
||||
delete decrypt_ctx;
|
||||
}
|
||||
|
||||
bool osd_messenger_t::op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done)
|
||||
{
|
||||
auto op = cl->write_op;
|
||||
auto & op_pos = cl->write_op_pos;
|
||||
assert(op->req.hdr.opcode == OSD_OP_WRITE);
|
||||
if (!from)
|
||||
{
|
||||
if (!cl->encrypt_ctx)
|
||||
{
|
||||
if (encrypt_ctx_pool.size())
|
||||
{
|
||||
cl->encrypt_ctx = encrypt_ctx_pool.back();
|
||||
encrypt_ctx_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->encrypt_ctx = new op_aes_xts_encrypt_t();
|
||||
}
|
||||
assert(op->enc->key_chain[0]);
|
||||
cl->encrypt_ctx->start(op->enc->key_chain[0], op->req.rw.offset, op->enc->bitmap_granularity);
|
||||
}
|
||||
for (int i = 0; i < op->iov.count; i++)
|
||||
{
|
||||
uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base;
|
||||
size_t plain_len = op->iov.buf[i].iov_len;
|
||||
while (from < plain_len || cl->encrypt_ctx->has_buffered())
|
||||
{
|
||||
if (done >= enc_len)
|
||||
return false;
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->encrypt_ctx->update(plain+from, plain_len-from, enc_buf+done, enc_len-done, done_in, done_out);
|
||||
done += done_out;
|
||||
op_pos += done_in;
|
||||
from += done_in;
|
||||
}
|
||||
from -= plain_len;
|
||||
}
|
||||
if (cl->encrypt_ctx)
|
||||
{
|
||||
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
|
||||
delete cl->encrypt_ctx;
|
||||
else
|
||||
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
|
||||
cl->encrypt_ctx = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool osd_messenger_t::op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
auto op = cl->read_op;
|
||||
assert(op->req.hdr.opcode == OSD_OP_READ);
|
||||
for (int i = 0; i < op->iov.count; i++)
|
||||
{
|
||||
uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base;
|
||||
size_t plain_len = op->iov.buf[i].iov_len;
|
||||
while (from < plain_len)
|
||||
{
|
||||
if (done >= enc_len)
|
||||
return false;
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->decrypt_ctx->update(enc_buf+done, enc_len-done, plain+from, plain_len-from, done_in, done_out);
|
||||
done += done_in;
|
||||
cl->read_op_pos += done_out;
|
||||
cl->read_op_inline_decrypt_in += done_in;
|
||||
from += done_out;
|
||||
if (!done_out)
|
||||
return false;
|
||||
}
|
||||
from -= plain_len;
|
||||
}
|
||||
op_decrypt_free(cl);
|
||||
return true;
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
|
||||
{
|
||||
if (!cl->decrypt_ctx)
|
||||
{
|
||||
if (decrypt_ctx_pool.size())
|
||||
{
|
||||
cl->decrypt_ctx = decrypt_ctx_pool.back();
|
||||
decrypt_ctx_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->decrypt_ctx = new op_aes_xts_decrypt_t();
|
||||
auto & enc = cl->read_op->enc;
|
||||
cl->decrypt_ctx->start(enc->key_chain, enc->chain_size,
|
||||
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
|
||||
cl->read_op->req.rw.offset, enc->bitmap_granularity);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
osd_op_t *op = cl->read_op;
|
||||
size_t from_in = cl->read_op_inline_decrypt_in;
|
||||
int i = 0;
|
||||
while (i < op->iov.count && from_in >= op->iov.buf[i].iov_len)
|
||||
{
|
||||
from_in -= op->iov.buf[i].iov_len;
|
||||
i++;
|
||||
}
|
||||
size_t from_out = cl->read_op_inline_decrypt_pos - OSD_PACKET_SIZE - op->reply.rw.bitmap_len;
|
||||
int j = 0;
|
||||
while (j < op->iov.count && from_out >= op->iov.buf[j].iov_len)
|
||||
{
|
||||
from_out -= op->iov.buf[j].iov_len;
|
||||
j++;
|
||||
}
|
||||
while (i < op->iov.count && j < op->iov.count)
|
||||
{
|
||||
uint8_t *in = (uint8_t*)op->iov.buf[i].iov_base + from_in;
|
||||
size_t in_len = op->iov.buf[i].iov_len - from_in;
|
||||
uint8_t *out = (uint8_t*)op->iov.buf[j].iov_base + from_out;
|
||||
size_t out_len = op->iov.buf[j].iov_len - from_out;
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->decrypt_ctx->update(in, in_len, out, out_len, done_in, done_out);
|
||||
if (done_in >= in_len)
|
||||
{
|
||||
i++;
|
||||
from_in = 0;
|
||||
}
|
||||
else
|
||||
from_in += done_in;
|
||||
if (done_out >= out_len)
|
||||
{
|
||||
j++;
|
||||
from_out = 0;
|
||||
}
|
||||
else
|
||||
from_out += done_out;
|
||||
}
|
||||
assert(j >= op->iov.count);
|
||||
op_decrypt_free(cl);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->decrypt_ctx)
|
||||
{
|
||||
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
|
||||
delete cl->decrypt_ctx;
|
||||
else
|
||||
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
|
||||
cl->decrypt_ctx = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// WITH_OPENSSL is left to possibly support other crypto libraries
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
#endif
|
||||
|
||||
class op_aes_xts_encrypt_t
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t *key = NULL;
|
||||
size_t offset = 0;
|
||||
size_t block_size = 0;
|
||||
uint8_t *tmp = NULL;
|
||||
size_t tmp_size = 0;
|
||||
size_t tmp_pos = 0;
|
||||
bool encrypted = false;
|
||||
|
||||
void encrypt_block(uint8_t *in, uint8_t *out);
|
||||
|
||||
public:
|
||||
op_aes_xts_encrypt_t();
|
||||
~op_aes_xts_encrypt_t();
|
||||
|
||||
inline bool has_buffered() { return encrypted; };
|
||||
void start(uint8_t *key, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
|
||||
|
||||
class op_aes_xts_decrypt_t
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t **key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
uint8_t *key_indexes = NULL;
|
||||
size_t offset = 0;
|
||||
size_t block_size = 0;
|
||||
uint8_t *tmp = NULL;
|
||||
size_t tmp_size = 0;
|
||||
size_t tmp_pos = 0;
|
||||
bool decrypted = false;
|
||||
|
||||
void decrypt_block(uint8_t *in, uint8_t *out);
|
||||
|
||||
public:
|
||||
op_aes_xts_decrypt_t();
|
||||
~op_aes_xts_decrypt_t();
|
||||
|
||||
inline bool has_buffered() { return decrypted; };
|
||||
void start(uint8_t **key_chain, size_t chain_size, uint8_t *key_indexes, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user