Compare commits
144
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6af10a444 | ||
|
|
bc24936969 | ||
|
|
b6c854c860 | ||
|
|
0ce6a61fcb | ||
|
|
4498d0665a | ||
|
|
55cce76d36 | ||
|
|
70c0cd2d5f | ||
|
|
455dc9cb1c | ||
|
|
9243aed615 | ||
|
|
3dd0e4daca | ||
|
|
3779e2670d | ||
|
|
0d92e00e41 | ||
|
|
0d1eeb4310 | ||
|
|
b21c92cb7a | ||
|
|
9db0980fc1 | ||
|
|
4a8ee088c1 | ||
|
|
f2585bcd02 | ||
|
|
72d1447eec | ||
|
|
c67db91305 | ||
|
|
75dfafa0cf | ||
|
|
b1d9098904 | ||
|
|
f676d262ec | ||
|
|
954505b5fd | ||
|
|
c137860f18 | ||
|
|
f45fb987d1 | ||
|
|
d744bd43ae | ||
|
|
8da99ee24a | ||
|
|
3c3a58aa1e | ||
|
|
40d4162249 | ||
|
|
026aaf59e7 | ||
|
|
cb40ab3dfd | ||
|
|
459a412f91 | ||
|
|
2b52769d64 | ||
|
|
ff83e12b79 | ||
|
|
4340b2b7bc | ||
|
|
6be0be02fa | ||
|
|
a31a1c0ab1 | ||
|
|
1c18daa644 | ||
|
|
b367c81f44 | ||
|
|
197ef6c171 | ||
|
|
4a907f09df | ||
|
|
360ccbaa4d | ||
|
|
d960816a9b | ||
|
|
3dabffb1be | ||
|
|
5ba63a2fa5 | ||
|
|
26be916e3f | ||
|
|
eb1dbd7459 | ||
|
|
13842ecd90 | ||
|
|
27e2c08e38 | ||
|
|
f9975311ea | ||
|
|
652ca3f1c3 | ||
|
|
c81cbcf69e | ||
|
|
9e28c4c6e3 | ||
|
|
e14be3fdad | ||
|
|
2e5d9cb238 | ||
|
|
376226552a | ||
|
|
6bdd260b50 | ||
|
|
b72cfbad66 | ||
|
|
792efa673e | ||
|
|
f3c5c3b776 | ||
|
|
5e26376f1f | ||
|
|
55047c10fb | ||
|
|
e13edcdc2f | ||
|
|
aaf54f264d | ||
|
|
7e879bea9d | ||
|
|
af34be70ab | ||
|
|
00517e2bac | ||
|
|
e0a2615cbc | ||
|
|
63fe3c323a | ||
|
|
a88465df05 | ||
|
|
ad24be717a | ||
|
|
648e3b12f0 | ||
|
|
a675993c74 | ||
|
|
c9dfd0f67d | ||
|
|
84919a10a9 | ||
|
|
51ae4d6e24 | ||
|
|
572b20fedc | ||
|
|
4e2724b28f | ||
|
|
768b1675f8 | ||
|
|
38fa722725 | ||
|
|
e56d83fb7f | ||
|
|
ff95a85875 | ||
|
|
98203568a8 | ||
|
|
89df98ee08 | ||
|
|
0007a831b6 | ||
|
|
40517c335f | ||
|
|
c9f7308b6a | ||
|
|
85c7e3bde0 | ||
|
|
4fb55b3535 | ||
|
|
912aca11a3 | ||
|
|
7b454bd16c | ||
|
|
a0c8be46a4 | ||
|
|
53b4329fac | ||
|
|
a7f41c4a12 | ||
|
|
5d78057ac3 | ||
|
|
8efc5a353f | ||
|
|
603b26b896 | ||
|
|
a3b0fe0deb | ||
|
|
f504e356d5 | ||
|
|
4ed17b7070 | ||
|
|
1fd2819724 | ||
|
|
dcdabbc1ec | ||
|
|
625d5b7b9e | ||
|
|
9e507fd333 | ||
|
|
c2b5118127 | ||
|
|
4b926e2223 | ||
|
|
a5d9a6996a | ||
|
|
0ee03e7172 | ||
|
|
88b7d9afcd | ||
|
|
f271c8450c | ||
|
|
f78d7d4efc | ||
|
|
fdaf7c88ff | ||
|
|
2fb6eb0c30 | ||
|
|
36d2b56208 | ||
|
|
14b22f2ba9 | ||
|
|
fe8b1fe0cc | ||
|
|
1ec963e468 | ||
|
|
5100f822d8 | ||
|
|
7432494e88 | ||
|
|
d0c0f3ea39 | ||
|
|
f61190f31d | ||
|
|
3dc0ab5c33 | ||
|
|
de96efed2f | ||
|
|
87a5230798 | ||
|
|
0c5e6d4346 | ||
|
|
b278087410 | ||
|
|
a8e821b13b | ||
|
|
caa70317fa | ||
|
|
b8eaaabfe4 | ||
|
|
e4d80c415e | ||
|
|
553191c3ff | ||
|
|
ab385252b5 | ||
|
|
041185c673 | ||
|
|
b03ac80a57 | ||
|
|
2ba56074f9 | ||
|
|
4acfe149cb | ||
|
|
008ed5b269 | ||
|
|
4fffe0f032 | ||
|
|
a76d5ccc0d | ||
|
|
8ed1e180e0 | ||
|
|
8832fc3b14 | ||
|
|
0134934c99 | ||
|
|
2e36f292bd | ||
|
|
bcc6419760 |
@@ -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,42 @@ 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_snapshot_chain_enc_gcm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=enc_gcm ENCRYPTED=1 VITASTOR_CFG=',"proto_checksums":"gcm"' /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 +1296,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_heal_ec_rdma:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: TEST_NAME=ec_rdma RDMA=1 SCHEME=ec /root/vitastor/tests/test_heal.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:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1224,6 +1332,24 @@ jobs:
|
||||
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
|
||||
@@ -2052,3 +2178,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...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.9")
|
||||
set(VITASTOR_VERSION "3.0.12")
|
||||
|
||||
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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.9
|
||||
VITASTOR_VERSION ?= v3.0.12
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.9
|
||||
image: vitalif/vitastor-csi:v3.0.12
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.9
|
||||
image: vitalif/vitastor-csi:v3.0.12
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.9"
|
||||
vitastorCSIDriverVersion = "3.0.12"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.9-1) unstable; urgency=medium
|
||||
vitastor (3.0.12-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
+6
-8
@@ -12,20 +12,18 @@ ARG REL=
|
||||
WORKDIR /root
|
||||
|
||||
RUN set -e -x; \
|
||||
if [ "$REL" = "buster" ]; then \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
fi; \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
|
||||
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
|
||||
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
|
||||
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 libisal-crypto-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,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.9
|
||||
VITASTOR_VERSION ?= v3.0.12
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -12,12 +12,7 @@ EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
SyslogIdentifier=etcd
|
||||
ExecStart=bash -c 'docker run --rm -i -v /var/lib/vitastor/etcd:/data \
|
||||
--log-driver none --network host $CONTAINER_OPTIONS --name vitastor-etcd \
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --name "$ETCD_NAME" --data-dir /data \
|
||||
--snapshot-count 10000 --advertise-client-urls http://$ETCD_IP:2379 --listen-client-urls http://$ETCD_IP:2379 \
|
||||
--initial-advertise-peer-urls http://$ETCD_IP:2380 --listen-peer-urls http://$ETCD_IP:2380 \
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster "$ETCD_INITIAL_CLUSTER" \
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision'
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --data-dir /data
|
||||
ExecStop=docker stop vitastor-etcd
|
||||
Restart=always
|
||||
StartLimitInterval=0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.9
|
||||
VITASTOR_VERSION=v3.0.12
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
|
||||
ETCD_NAME=""
|
||||
ETCD_IP=""
|
||||
ETCD_INITIAL_CLUSTER=""
|
||||
|
||||
@@ -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 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -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_cipher_pool_size](#max_cipher_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_cipher_pool_size
|
||||
|
||||
- Type: integer
|
||||
- Default: 256
|
||||
|
||||
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
|
||||
for each cipher and for encryption/decryption. 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_cipher_pool_size](#max_cipher_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_cipher_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 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -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_cipher_pool_size
|
||||
type: int
|
||||
default: 256
|
||||
info: |
|
||||
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
|
||||
for each cipher and for encryption/decryption. Probably doesn't require modification.
|
||||
info_ru: |
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
@@ -26,9 +26,9 @@ 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.9`
|
||||
`docker pull vitalif/vitastor:v3.0.12`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
|
||||
3. Reload udev rules: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Enable the vitastor-host service: \
|
||||
|
||||
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
|
||||
Инструкция по установке максимально простая.
|
||||
|
||||
1. Скачайте Docker-образ желаемой версии: \
|
||||
`docker pull vitalif/vitastor:v3.0.9`
|
||||
`docker pull vitalif/vitastor:v3.0.12`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
|
||||
@@ -15,9 +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)
|
||||
- tcmalloc (google-perftools-dev)
|
||||
- jerasure, c-ares headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
|
||||
назначенных инициализаторов (designated initializers) из C++20
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm
|
||||
- tcmalloc (google-perftools-dev)
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
|
||||
@@ -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) для хранения ключей
|
||||
|
||||
## Драйверы и инструменты
|
||||
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
[Документация](../../README-ru.md#документация) → Безопасность
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Оглавление
|
||||
|
||||
⚠️ Предупреждение: детальное описание настроек безопасности достаточно длинное.
|
||||
|
||||
Если не боитесь - читайте [Подробное описание](#подробное-описание).
|
||||
|
||||
Если хотите просто быстро настроить Vitastor с шифрованием - читайте начало статьи.
|
||||
|
||||
- [Быстрая настройка](#быстрая-настройка)
|
||||
-
|
||||
|
||||
# Быстрая настройка
|
||||
|
||||
|
||||
|
||||
# Пользовательские сценарии
|
||||
|
||||
Зачем всё это нужно вам?
|
||||
|
||||
|
||||
|
||||
# Подробное описание
|
||||
|
||||
Начиная с версии 3.1.0, в Vitastor есть следующие функции:
|
||||
1. Шифрование соединений с etcd (TLS)
|
||||
2. Шифрование соединений с OSD (AES-GCM) - по выбору либо только заголовков, либо и заголовков, и данных
|
||||
3. Сквозное шифрование данных образов (AES-XTS)
|
||||
4. Хранения ключей шифрования AES-XTS во внешнем Vault
|
||||
5. Контрольных сумм данных на транспортном уровне с секретной "солью"
|
||||
6. Аутентификация с помощью TLS (X.509) сертификатов и закрытых ключей
|
||||
7. Разграничение прав доступа клиентов к данным etcd
|
||||
8. Разграничение прав доступа клиентов к данным самих образов (на стороне OSD)
|
||||
|
||||
По умолчанию шифрование, аутентификация и авторизация отключены, но, начиная с 3.1.0,
|
||||
используются контрольные суммы данных на транспортном уровне (`proto_checksums=payload`).
|
||||
|
||||
## Шифрование соединений с etcd (TLS)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования (http)
|
||||
- С шифрованием (https)
|
||||
- С клиентским сертификатом, но при выключенной авторизации (`use_auth=false`) - используется
|
||||
отдельный сертификат и ключ: `etcd_client_cert`, `etcd_client_key`
|
||||
- С клиентским сертификатом, при включённой аутентификации на уровне OSD - используется общий
|
||||
сертификат и ключ: для OSD - `osd_cert` и `osd_pkey`, для клиентов - `cert` и `pkey`
|
||||
|
||||
## Шифрование соединений с OSD (AES-GCM)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования и без контрольных сумм: `proto_checksums=none`.
|
||||
- Без шифрования, с контрольными суммами данных: `proto_checksums=payload` (можно не указывать,
|
||||
т.к. это значение по умолчанию). При этом контрольные суммы можно отключить на стороне
|
||||
клиента либо использовать более старые версии клиента, не поддерживающие контрольные суммы.
|
||||
Если нужно запретить подключение клиентов без контрольных сумм, можно использовать опцию
|
||||
`force_proto_checksums=payload`.
|
||||
- С шифрованием заголовков и контрольными суммами данных: активируется при установленных опциях
|
||||
`cert`, `pkey`, `osd_ca` на стороне клиента и `osd_cert`, `osd_pkey`, `osd_ca`, `client_ca`
|
||||
на стороне OSD, при `proto_checksums=payload`. При этом по умолчанию запрещается
|
||||
отключение контрольных сумм на уровне клиента, то есть используется `force_proto_checksums=payload`.
|
||||
- С полным шифрованием всего трафика: аналогично прошлому варианту, но с `proto_checksums=gcm`.
|
||||
Клиенту при этом по умолчанию разрешается понизить уровень защиты до контрольных сумм, но
|
||||
это тоже можно запретить через `force_proto_checksums=gcm`. Данный вариант не является рекомендуемым,
|
||||
так как добавлен в первую очередь для возможной поддержки небезопасных (публичных) сетей и
|
||||
больше всего снижает производительность. В частности, если одновременно использовать полное
|
||||
шифрование трафика и сквозное шифрование образов AES-XTS, то данные будут шифроваться дважды.
|
||||
|
||||
Для шифрования используется алгоритм AES-256-GCM и собственный упрощённый протокол согласования
|
||||
ключей, полностью аналогичный TLS 1.3 ECDHE.
|
||||
|
||||
## Сквозное шифрование данных образов (AES-XTS)
|
||||
|
||||
Клиент Vitastor поддерживает шифрование данных каждого образа своим ключом. В этом случае на OSD
|
||||
уходят уже зашифрованные данные и сами OSD не видят настоящее содержимое образов. Разные ключи
|
||||
в том числе могут иметь разные снимки или клоны одного и того же образа. Например, можно сделать
|
||||
базовый образ ВМ (условный Debian Linux) нешифрованным, но наследовать от него шифрованные образы
|
||||
клиентских ВМ.
|
||||
|
||||
Ключи шифрования образов могут храниться либо в etcd, либо во внешнем Vault. Во втором случае
|
||||
в etcd хранятся только ID ключей, а Vitastor вообще не имеет доступа к данным образов. Для
|
||||
использования Vault нужно создать образ с опцией `--enc_key vault:ID`, а в конфигурации указать
|
||||
опции:
|
||||
- vault_url
|
||||
- vault_ca
|
||||
- vault_client_cert
|
||||
- vault_client_key
|
||||
|
||||
Ещё раз повторимся, что если AES-XTS используется с полным шифрованием трафика (`proto_checksums=gcm`),
|
||||
то данные образов шифруются дважды - сначала AES-XTS, а потом AES-GCM. Можете использовать,
|
||||
только если вы совсем параноик :-).
|
||||
|
||||
## Производительность шифрования
|
||||
|
||||
У вас может возникнуть вопрос - а как быстро всё это прекрасное шифрование работает?
|
||||
|
||||
Ответ - скорость сильно зависит от процессора. Складывается она из нескольких вещей:
|
||||
|
||||
-
|
||||
|
||||
TODO: vitastor-cli bench.
|
||||
|
||||
## Аутентификация по сертификатам
|
||||
|
||||
При включённом шифровании клиенты, OSD и мониторы Vitastor аутентифицируются по сертификатам
|
||||
как при соединениях с etcd (Antietcd), так и с OSD.
|
||||
|
||||
Для OSD и мониторов должны использоваться отдельные сертификаты - либо самоподписанные, либо
|
||||
подписанные отдельными CA (`osd_ca` и `mon_ca`). При этом все OSD могут использовать один и
|
||||
тот же сертификат и все мониторы тоже могут использовать один и тот же сертификат, так как
|
||||
привилегии разных OSD или разных мониторов ничем не отличаются (теоретически можно было бы
|
||||
сделать разграничение сертификатов OSD по пулам, но пока что такой необходимости не было).
|
||||
|
||||
Также сертификат монитора может быть вообще не нужен, если Antietcd встраивается в сам монитор.
|
||||
В этом случае монитор и так имеет доступ ко всем данным etcd прямо в памяти.
|
||||
|
||||
Каждый клиент должен иметь свой сертификат, подписанный общим корневым сертификатом
|
||||
для клиентов (`client_ca`). Common Name сертификата должно равняться имени пользователя.
|
||||
|
||||
## Модель прав доступа
|
||||
|
||||
Привилегии пользователей хранятся в данных etcd в ключах `/vitastor/config/user/<имя>`.
|
||||
|
||||
У пользователя есть 2 свойства:
|
||||
- Тип:
|
||||
- Клиент (`type=client` или не указано) - может читать и модифицировать только явным образом
|
||||
разрешённые образы.
|
||||
- Администратор (`type=admin`) - может читать и модифицировать все образы, а также администрировать
|
||||
кластер: смотреть общую статистику и состояние, создавать и удалять OSD и так далее.
|
||||
- Список имён групп, членом которых пользователь является.
|
||||
|
||||
У образов есть 3 свойства:
|
||||
- Владелец (owner) - имя пользователя, которому разрешено и читать, и менять образ
|
||||
- Группа владельцев (owner_group) - имя группы владельцев
|
||||
- Группа читатетей (reader_group) - имя группы пользователей, которым разрешено читать образ
|
||||
|
||||
У пулов есть 1 свойство:
|
||||
- Группа создателей (creator_group) - имя группы пользователей, которым разрешено создавать образы в пуле
|
||||
|
||||
## Права доступа к данным etcd
|
||||
|
||||
Привилегии реализуются через Antietcd во всех режимах работы. Если используется etcd, то
|
||||
Antietcd выступает в роли фильтрующего прокси, при этом он может быть встроен в монитор
|
||||
Vitastor или запущен отдельно. В этом случае etcd должен разрешать входящие подключения
|
||||
только от Antietcd, а все остальные компоненты должны соединяться с Antietcd.
|
||||
|
||||
Если же используется Antietcd, то привилегии реализуются в нём самом.
|
||||
|
||||
Если используется встроенный в монитор Antietcd, то привилегии включаются либо параметром
|
||||
`use_auth: true`, либо, если этот параметр не указан - включается автоматически, если задан
|
||||
любой из параметров `client_ca`, `osd_ca`, `mon_ca`. При этом монитор требует указания
|
||||
параметров `client_ca` и `osd_ca`, а если не используется режим проксирования в etcd -
|
||||
также `antietcd_server_ca`, чтобы Antietcd мог отличать кластерные соединения от клиентских.
|
||||
|
||||
Если используется отдельно стоящий Antietcd, привилегии нужно включать явным образом.
|
||||
|
||||
Встроенные привилегии etcd не поддерживаются по причине их многочисленных недоработок:
|
||||
- Аутентификация по сертификатам не работает в REST интерфейсе etcd,
|
||||
- Привилегии хранятся отдельно от k/v и не могут участвовать в транзакциях,
|
||||
- Менять привилегии может только администратор (root)
|
||||
- Нет поддержки фильтрации ответов чтения по привилегиям.
|
||||
|
||||
Подробный список привилегий на ключи в etcd [смотрите ниже](#привилегии-etcd).
|
||||
|
||||
## Права доступа к данным OSD
|
||||
|
||||
Регулируется опцией `use_auth`, либо, если она не указана, включается автоматически,
|
||||
если используется шифрование, то есть, если заданы опции `osd_ca` и `client_ca`.
|
||||
|
||||
OSD аутентифицирует клиентов по сертификатам и разрешает каждому клиенту только
|
||||
то, что ему разрешено согласно модели прав доступа.
|
||||
|
||||
Подробный список разрешаемых OSD операций [смотрите ниже](#привилегии-osd).
|
||||
|
||||
## Права доступа к API
|
||||
|
||||
[vitastor-cli serve](../usage/cli.ru.md#serve) также поддерживает клиентскую
|
||||
аутентификацию по сертификатам. Принимаются только сертификаты, подписанные
|
||||
`client_ca`. В качестве серверного сертификата используется отдельный сертификат
|
||||
`server_cert` с ключом `server_key`.
|
||||
|
||||
При этом для корректной работы `vitastor-cli serve` он сам должен использовать
|
||||
для доступа в Vitastor сертификат (`cert`+`pkey`) пользователя с правами
|
||||
администратора (`type=admin`).
|
||||
|
||||
Обычным клиентам при доступе к API разрешаются только API-операции с образами,
|
||||
доступными им либо на чтение (для чтения), либо на запись (для модификации).
|
||||
Все остальные API-вызовы разрешаются только для администраторов.
|
||||
|
||||
Подробный список разрешаемых API операций [смотрите ниже](#привилегии-api).
|
||||
|
||||
## Привилегии etcd
|
||||
|
||||
Ниже все названия ключей приведены без общего префикса `/vitastor`.
|
||||
|
||||
Разрешённые операции с ключами в Antietcd для клиентов (`type=client`):
|
||||
- Только чтение:
|
||||
- Разрешено всегда:
|
||||
- `/config/global`
|
||||
- `/config/node_placement`
|
||||
- `/config/pools`
|
||||
- `/pg/config`
|
||||
- `/osd/state/*`
|
||||
- `/pg/state/*`
|
||||
- `/index/maxid/*`
|
||||
- Для образов, которые [может читать пользователь](#модель-прав-доступа):
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
- `/inode/stats/*`
|
||||
- Чтение и запись:
|
||||
- Для пулов, в которых может создавать образы пользователь:
|
||||
- `/index/maxid/*`
|
||||
- Для образов, которыми владеет пользователь:
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
|
||||
Разрешённые операции с ключами в Antietcd для администраторов (`type=admin`):
|
||||
- Чтение:
|
||||
- `/stats`
|
||||
- `/mon/*`
|
||||
- `/pg/*`
|
||||
- `/pgstats/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
- Чтение и запись:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/index/*`
|
||||
- `/pg/history/*`
|
||||
|
||||
Разрешённые операции с ключами в etcd для OSD:
|
||||
- Чтение:
|
||||
- `/pg/config`
|
||||
- `/config/*`
|
||||
- Чтение и запись:
|
||||
- `/osd/*`
|
||||
- `/pg/state/*`
|
||||
- `/pg/history/*`
|
||||
- `/pgstats/*`
|
||||
|
||||
Разрешённые операции с ключами в etcd для мониторов:
|
||||
- Чтение:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/pgstats/*`
|
||||
- Чтение и запись:
|
||||
- `/pg/config`
|
||||
- `/stats`
|
||||
- `/history/last_clean_pgs`
|
||||
- `/mon/*`
|
||||
- `/pg/history/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
|
||||
## Привилегии OSD
|
||||
|
||||
Клиентские операции:
|
||||
- READ - разрешено для образов, доступных пользователю на чтение.
|
||||
- WRITE, DELETE, SCRUB - разрешены для образов, доступных пользователю на запись.
|
||||
- SYNC - операция не связана с образом и разрешена всегда.
|
||||
- DESCRIBE - операция разрешена только для администраторов (используются командами
|
||||
`vitastor-cli describe` и `fix`).
|
||||
- PING - операция разрешена всегда.
|
||||
- SHOW_CONFIG - операция разрешена всегда, однако если в ней клиент представляется
|
||||
как OSD, то проверяется, что он использует сертификат, подписанный `osd_ca`.
|
||||
- SEC_LIST (листинг) - разрешена другим OSD и администраторам с любыми параметрами,
|
||||
а обычным клиентам разрешена только для запросов, ограниченных образом, доступным
|
||||
пользователю на чтение.
|
||||
|
||||
Кластерные операции - разрешаются только другим OSD:
|
||||
- SEC_READ
|
||||
- SEC_WRITE
|
||||
- SEC_WRITE_STABLE
|
||||
- SEC_SYNC
|
||||
- SEC_STABILIZE
|
||||
- SEC_ROLLBACK
|
||||
- SEC_DELETE
|
||||
- SEC_READ_BMP
|
||||
- SEC_LOCK
|
||||
|
||||
## Привилегии API
|
||||
|
||||
Клиентам (пользователям с `type=client`) разрешаются операции:
|
||||
- image/list - для образов, которые пользователь может читать.
|
||||
- image/create - для пулов, в которых пользователю разрешено создавать образы, либо
|
||||
для создания снимков образов, которыми пользователь владеет.
|
||||
- image/delete, image/flatten, image/modify - для образов, которыми пользователь владеет.
|
||||
|
||||
Все остальные операции разрешаются только администраторам (`type=admin`).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Таким образом, доступны следующие варианты настройки:
|
||||
|
||||
### Mon в роли Etcd proxy
|
||||
|
||||
Mon
|
||||
- use_antietcd: true
|
||||
- etcd_proxy = {
|
||||
urls: [],
|
||||
cert = <antietcd.pem>,
|
||||
key,
|
||||
ca = <etcd.pem>,
|
||||
}
|
||||
- antietcd_cert = antietcd.pem
|
||||
- antietcd_key
|
||||
|
||||
etcd
|
||||
--client-cert-auth --cert-file=etcd.pem --key-file=etcd.key --trusted-ca-file=antietcd.pem \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.pem --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.pem
|
||||
|
||||
### Mon с отдельным Antietcd Proxy
|
||||
|
||||
Mon
|
||||
- use_antietcd: false
|
||||
- etcd_ca = antietcd.pem
|
||||
|
||||
Antietcd
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
|
||||
--cert antietcd.pem --key antietcd.key --ca client_ca.pem --osd_ca osd_ca.pem \
|
||||
--etcd_cert antietcd.pem --etcd_key antietcd.key --etcd_ca etcd.pem
|
||||
|
||||
etcd
|
||||
--client-cert-auth --cert-file=etcd.pem --key-file=etcd.key --trusted-ca-file=antietcd.pem \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.pem --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.pem
|
||||
|
||||
### Mon со встроенным Antietcd
|
||||
|
||||
Mon
|
||||
- use_antietcd: true
|
||||
- use_auth: true
|
||||
- antietcd_cert = antietcd.pem
|
||||
- antietcd_key
|
||||
|
||||
### Отдельный Antietcd
|
||||
|
||||
Mon
|
||||
- use_antietcd: false
|
||||
- etcd_ca = antietcd.pem
|
||||
|
||||
Antietcd
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js
|
||||
|
||||
## Варианты настройки
|
||||
|
||||
### Настройка по умолчанию
|
||||
|
||||
Используются только контрольные суммы данных на транспортном уровне. Соединения с etcd не шифруются.
|
||||
Аутентификация и авторизация не используется, любой клиент имеет доступ ко всем данным кластера.
|
||||
|
||||
Аналог настройки:
|
||||
- proto_checksums: payload
|
||||
|
||||
### Полная защита
|
||||
|
||||
Везде
|
||||
- osd_ca
|
||||
- client_ca
|
||||
- etcd_ca = antietcd.pem
|
||||
|
||||
OSD
|
||||
- osd_cert
|
||||
- osd_pkey
|
||||
|
||||
Клиент
|
||||
- cert
|
||||
- pkey
|
||||
|
||||
### Только защита etcd
|
||||
|
||||
- etcd_ca
|
||||
- etcd_cert
|
||||
- etcd_key
|
||||
|
||||
### antietcd и только защита antietcd
|
||||
|
||||
- etcd_ca
|
||||
- etcd_cert
|
||||
- etcd_key
|
||||
- use_antietcd: true
|
||||
- antietcd_cert = etcd_ca
|
||||
- antietcd_key
|
||||
- antietcd_ca = etcd_cert
|
||||
|
||||
### Полное шифрование протокола, включая данные
|
||||
|
||||
Внимание: если включить этот вариант защиты и при этом
|
||||
|
||||
### Только контрольные суммы на транспортном уровне, без шифрования
|
||||
|
||||
## Настройка Vault/OpenBao
|
||||
|
||||
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
|
||||
-new -newkey rsa:4096 -nodes -keyout vault.key -out vault.crt
|
||||
|
||||
bao status -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
|
||||
|
||||
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
|
||||
|
||||
bao operator unseal -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
|
||||
|
||||
bao auth enable -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 cert
|
||||
|
||||
bao secrets enable -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 -path=secret kv-v1
|
||||
|
||||
bao kv put -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
|
||||
|
||||
cat >testimg3.policy <<EOF
|
||||
path "/secret/vitastor/testimg3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
bao policy write -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
|
||||
|
||||
bao write -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
|
||||
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key --json '{}' https://vault:8200/v1/auth/cert/login
|
||||
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key -H 'X-Vault-Token: s.Qkrm78BeK7Rqdz5MA3eJZNbu' https://vault:8200/v1/secret/vitastor/testimg3
|
||||
+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
|
||||
|
||||
|
||||
@@ -262,3 +262,4 @@ Options:
|
||||
| `--logfile <FILE>` | log to the specified file |
|
||||
| `--enforce 1` | enforce permissions at the server side (no by default) |
|
||||
| `--foreground 1` | stay in foreground, do not daemonize |
|
||||
| `--trace` | trace all NFS requests |
|
||||
|
||||
@@ -274,3 +274,4 @@ VitastorFS из GPUDirect.
|
||||
| `--logfile <FILE>` | записывать логи в заданный файл |
|
||||
| `--enforce 1` | проверять права доступа на стороне сервера (по умолчанию нет) |
|
||||
| `--foreground 1` | не уходить в фон после запуска |
|
||||
| `--trace` | логгировать все запросы NFS |
|
||||
|
||||
+50
-5
@@ -3,6 +3,7 @@
|
||||
|
||||
const AntiEtcd = require('antietcd');
|
||||
|
||||
const vitastor_auth_filter = require('./vitastor_auth_filter.js');
|
||||
const vitastor_persist_filter = require('./vitastor_persist_filter.js');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -18,7 +19,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,10 +27,16 @@ 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 cluster_local = cluster.map(s =>
|
||||
{
|
||||
const m = /^https?:\/\/(?:\[(.*)\]|([^\[\:]+))(?::(\d+))?$/.exec(s);
|
||||
return [ m[2] || m[1], m[3] || 2379 ];
|
||||
});
|
||||
const selected = cluster_local.filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
console.error('More than 1 etcd_address matches local IPs: '+(selected.join(', '))+', please specify port');
|
||||
process.exit(1);
|
||||
}
|
||||
else if (selected.length == 1)
|
||||
@@ -37,14 +44,52 @@ class AntiEtcdAdapter
|
||||
const antietcd_config = {
|
||||
ip: selected[0][0],
|
||||
port: selected[0][1],
|
||||
cert: config.antietcd_cert,
|
||||
key: config.antietcd_key,
|
||||
ca: config.antietcd_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.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; }, {})),
|
||||
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,
|
||||
};
|
||||
if (config.etcd_proxy)
|
||||
{
|
||||
// Monitor may use the builtin etcd_proxy mode
|
||||
if (!config.etcd_proxy.urls)
|
||||
{
|
||||
console.error('etcd_proxy.urls are empty');
|
||||
process.exit(1);
|
||||
}
|
||||
antietcd_config.etcd_proxy = config.etcd_proxy.urls;
|
||||
antietcd_config.etcd_cert = config.etcd_proxy.cert;
|
||||
antietcd_config.etcd_key = config.etcd_proxy.key;
|
||||
antietcd_config.etcd_ca = config.etcd_proxy.ca;
|
||||
delete antietcd_config.data;
|
||||
delete antietcd_config.persist_filter;
|
||||
delete antietcd_config.cluster;
|
||||
delete antietcd_config.cluster_key;
|
||||
}
|
||||
const use_auth = config.use_auth || config.use_auth == null && config.client_ca;
|
||||
if (use_auth)
|
||||
{
|
||||
antietcd_config.client_cert_auth = true;
|
||||
antietcd_config.auth_filter = vitastor_auth_filter;
|
||||
antietcd_config.ca = config.client_ca;
|
||||
antietcd_config.osd_ca = config.osd_ca;
|
||||
antietcd_config.mon_ca = config.mon_ca;
|
||||
if (!config.etcd_proxy)
|
||||
{
|
||||
antietcd_config.peer_ca = config.antietcd_server_ca;
|
||||
if (!config.antietcd_server_ca || config.antietcd_server_ca == config.client_ca)
|
||||
{
|
||||
console.error('Secure setup requires separate antietcd_server_ca (for signing antietcd server certificates) and client_ca (for signing client certificates)');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in config)
|
||||
{
|
||||
if (key.substr(0, 9) === 'antietcd_')
|
||||
@@ -169,7 +214,7 @@ class AntiEtcdAdapter
|
||||
await new Promise(ok => setTimeout(ok, timeout-(Date.now()-prev)));
|
||||
}
|
||||
prev = Date.now();
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body);
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { user_type: 'mon' });
|
||||
if (res.error)
|
||||
{
|
||||
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
|
||||
|
||||
+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)
|
||||
{
|
||||
|
||||
+22
-1
@@ -16,6 +16,7 @@ const etcd_allow = new RegExp('^'+[
|
||||
'config/pools',
|
||||
'config/osd/[1-9]\\d*',
|
||||
'config/pgs', // old name
|
||||
'config/user/.*',
|
||||
'pg/config',
|
||||
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
||||
'osd/state/[1-9]\\d*',
|
||||
@@ -45,7 +46,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
|
||||
@@ -201,6 +209,8 @@ const etcd_tree = {
|
||||
primary_affinity_tags?: 'nvme' | [ 'nvme', ... ],
|
||||
// scrub interval
|
||||
scrub_interval?: '30d',
|
||||
// users allowed to create images in this pool
|
||||
creator_group?: '',
|
||||
},
|
||||
...
|
||||
}, */
|
||||
@@ -217,10 +227,21 @@ const etcd_tree = {
|
||||
parent_id?: <inode_t>,
|
||||
readonly?: boolean,
|
||||
deleted?: boolean,
|
||||
enc_key?: string,
|
||||
owner?: string,
|
||||
owner_group?: string,
|
||||
reader_group?: string,
|
||||
}
|
||||
}
|
||||
}, */
|
||||
inode: {},
|
||||
/* user: {
|
||||
<username>: {
|
||||
type: 'osd'|'mon'|'admin'|'client',
|
||||
groups: string[],
|
||||
},
|
||||
}, */
|
||||
user: {},
|
||||
},
|
||||
osd: {
|
||||
state: {
|
||||
|
||||
+2
-2
@@ -627,7 +627,7 @@ class Mon
|
||||
if (this.state.pg.history[pool_id] &&
|
||||
this.state.pg.history[pool_id][pg])
|
||||
{
|
||||
pg_history[pg-1] = this.state.pg.history[pool_id][pg];
|
||||
pg_history[pg-1] = JSON.parse(JSON.stringify(this.state.pg.history[pool_id][pg]));
|
||||
}
|
||||
}
|
||||
const real_prev_pgs = [];
|
||||
@@ -719,7 +719,7 @@ class Mon
|
||||
this.next_recheck_timer = null;
|
||||
this.next_recheck_at = 0;
|
||||
this.schedule_recheck();
|
||||
}, now-this.next_recheck_at);
|
||||
}, (this.next_recheck_at-now)*1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.9",
|
||||
"version": "3.0.12",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ function scale_pg_history(prev_pg_history, prev_pgs, new_pgs)
|
||||
finish_pg_history(merged_history[1]);
|
||||
for (let i = 0; i < new_pg_count; i++)
|
||||
{
|
||||
new_pg_history[i] = { ...merged_history[1] };
|
||||
new_pg_history[i] = JSON.parse(JSON.stringify(merged_history[1]));
|
||||
}
|
||||
}
|
||||
// Mark history keys for removed PGs as removed
|
||||
@@ -102,7 +102,7 @@ function scale_pg_count(prev_pgs, new_pg_count)
|
||||
{
|
||||
for (let i = prev_pgs.length; i < new_pg_count; i++)
|
||||
{
|
||||
prev_pgs[i] = prev_pgs[i % prev_pgs.length];
|
||||
prev_pgs[i] = [ ...prev_pgs[i % prev_pgs.length] ];
|
||||
}
|
||||
}
|
||||
else if (prev_pgs.length > new_pg_count)
|
||||
|
||||
+327
-44
@@ -1,38 +1,278 @@
|
||||
#!/usr/bin/node
|
||||
// Simple systemd unit generator for etcd
|
||||
// Simple Vitastor etcd / antietcd / TLS configurator
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: MIT
|
||||
|
||||
// USAGE:
|
||||
// 1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes
|
||||
// 2) Run ./make-etcd.js. It will create the etcd service on one of specified IPs
|
||||
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const readline = require('readline');
|
||||
|
||||
run().catch(e => { console.error(e); process.exit(1); });
|
||||
|
||||
const help_text = `Initialize a Vitastor cluster (etcd, vitastor.conf and TLS certificates)
|
||||
(c) Vitaliy Filippov, 2019+ (MIT)
|
||||
|
||||
USAGE:
|
||||
1) Create a minimal vitastor.conf with etcd_address, osd_network and (optionally) use_auth.
|
||||
Example: {"etcd_address":["http://10.0.0.10:2379","http://10.0.0.11:2379","http://10.0.0.12:2379"],"use_auth":false,"osd_network":"10.0.0.0/24"}
|
||||
Or: {"etcd_address":["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],"use_auth":true,"osd_network":"10.0.0.0/24"}
|
||||
2) Run: ${process.argv[1]} [./vitastor.conf]
|
||||
You can run it on etcd/monitor nodes or on an external node.
|
||||
It configures etcd, generates TLS certificates (on the first or external node), copies them
|
||||
to other etcd/monitor nodes, and updates vitastor.conf with TLS options.
|
||||
3) If you have OSD-only nodes, run:
|
||||
${process.argv[1]} --copy-to-osd-node NODE_NAME ./vitastor.conf
|
||||
It copies vitastor.conf and required TLS certificates to that node.
|
||||
|
||||
OPTIONS:
|
||||
--gen-certs
|
||||
force certificate generation even if it's not the first node
|
||||
--no-certs
|
||||
disable certificate generation
|
||||
--no-copy
|
||||
do not copy initial certificates to other nodes
|
||||
--copy-to-osd-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates required for OSDs to NODES using scp
|
||||
--copy-to-mon-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates required for monitor and etcd to NODES using scp
|
||||
--copy-to-client-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates required for clients to NODES using scp
|
||||
`;
|
||||
|
||||
async function run()
|
||||
{
|
||||
const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
|
||||
if (config_path == '-h' || config_path == '--help')
|
||||
let config_path = '/etc/vitastor/vitastor.conf';
|
||||
let config_dir = '/etc/vitastor/';
|
||||
let gen_certs = 'auto';
|
||||
let copy_initial = true;
|
||||
let copy_to_osd =
|
||||
for (let i = 2; i < process.argv.length; i++)
|
||||
{
|
||||
console.log(
|
||||
'Initialize systemd etcd service for Vitastor\n'+
|
||||
'(c) Vitaliy Filippov, 2019+ (MIT)\n'+
|
||||
'\n'+
|
||||
'USAGE:\n'+
|
||||
'1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes\n'+
|
||||
'2) Run '+process.argv[1]+' [config_path]\n'
|
||||
);
|
||||
process.exit(0);
|
||||
if (arg == '-h' || arg == '--help')
|
||||
{
|
||||
console.log(help_text);
|
||||
process.exit(0);
|
||||
}
|
||||
else if (arg == '--only-certs')
|
||||
{
|
||||
i++;
|
||||
gen_certs =
|
||||
}
|
||||
else if (arg == '--copy')
|
||||
{
|
||||
i++;
|
||||
copy = process.argv[i];
|
||||
if (copy !== 'ask' && copy !== 'yes' && copy !== 'no')
|
||||
{
|
||||
console.error('--copy should be "ask", "yes" or "no"');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
else if (arg[0] == '-')
|
||||
{
|
||||
console.error('Unknown option: '+arg[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
config_path = arg;
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(config_path))
|
||||
{
|
||||
console.log(config_path+' is missing');
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => /^(https?):\/\/(\[[^\]]+\]|[^\[\]\:\/]+)(?::(\d+))?/.exec(s.toLowerCase()))
|
||||
.filter(s => s)
|
||||
.map(s => ({
|
||||
scheme: s[1],
|
||||
addr: s[2].indexOf(':') && s[2][0] != '[' ? '['+s[2]+']' : s[2],
|
||||
ip: s[2][0] == '[' ? s[2].substr(1, s[2].length-2) : s[2],
|
||||
port: s[3],
|
||||
}));
|
||||
const tls = etcds.filter(e => e.scheme === 'https').length > 0;
|
||||
const use_auth = tls && config.use_auth;
|
||||
const num = select_local_etcd(etcds);
|
||||
if (tls)
|
||||
{
|
||||
if (gen_certs === 'yes')
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('Certificate generation is requested explicitly, generating');
|
||||
}
|
||||
else if (gen_certs === 'no')
|
||||
{
|
||||
gen_certs = false;
|
||||
console.log('Certificate generation is disabled explicitly, skipping');
|
||||
}
|
||||
else if (num < 0)
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('No matching IPs in etcd_address from '+config_path+', only generating certificates');
|
||||
}
|
||||
else if (fs.existsSync("/etc/vitastor/etcd.crt"))
|
||||
{
|
||||
gen_certs = false;
|
||||
console.log('/etc/vitastor/etcd.crt already exists, assuming certificates are already generated');
|
||||
}
|
||||
else if (num === 0)
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('This is monitor node 1, generating certificates');
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('This is monitor node '+(num+1)+', /etc/vitastor/etcd.crt does not exist, please copy certificates to this node');
|
||||
process.exit(1);
|
||||
}
|
||||
if (gen_certs)
|
||||
{
|
||||
if (copy === 'ask')
|
||||
copy = await ask_copy('Copy certificates and vitastor.conf to other nodes after generation?');
|
||||
copy = (copy === 'y' || copy === 'yes');
|
||||
await make_certs(dir, copy);
|
||||
}
|
||||
await write_auth_config(config, config_path);
|
||||
}
|
||||
if (num < 0)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(tls && gen_certs ? 0 : 1);
|
||||
}
|
||||
await configure_etcd();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function ask_copy(question)
|
||||
{
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: '> ',
|
||||
});
|
||||
let copy;
|
||||
while (true)
|
||||
{
|
||||
copy = await new Promise(ok => rl.question(question, ok));
|
||||
if (copy != 'y' && copy != 'n' && copy != 'yes' && copy != 'no')
|
||||
console.log('Please type "yes" or "no"');
|
||||
else
|
||||
break;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
async function make_certs(dir, copy)
|
||||
{
|
||||
console.log(`-----
|
||||
Generating certificates in ${dir}
|
||||
-----
|
||||
`);
|
||||
await make_ca("/O=Vitastor etcd CA", dir+"etcd_ca");
|
||||
await make_signed("/CN=Vitastor etcd", dir+"etcd", dir+"etcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
|
||||
if (use_auth)
|
||||
{
|
||||
await make_ca("/O=Vitastor Antietcd CA", dir+"antietcd_ca");
|
||||
await make_signed("/CN=Vitastor Antietcd", dir+"antietcd", dir+"antietcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
|
||||
await make_ca("/CN=Vitastor OSD", dir+"osd");
|
||||
await make_ca("/O=Vitastor Client CA", dir+"client_ca");
|
||||
await make_signed("/CN=admin", dir+"admin", dir+"client_ca");
|
||||
}
|
||||
if (use_auth)
|
||||
{
|
||||
console.log(`-----
|
||||
Certificates generated, commands to copy them:
|
||||
- Monitor+OSD node:
|
||||
cd ${dir} && scp antietcd_ca.crt antietcd.crt antietcd.key osd.crt osd.key client_ca.crt etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
|
||||
- Monitor node:
|
||||
cd ${dir} && scp antietcd_ca.crt antietcd.crt antietcd.key osd.crt client_ca.crt etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
|
||||
- OSD node:
|
||||
cd ${dir} && scp antietcd_ca.crt osd.crt osd.key client_ca.crt root@NODE:/etc/vitastor/
|
||||
-----
|
||||
`);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`-----
|
||||
Certificates generated, commands to copy them:
|
||||
- Monitor node:
|
||||
cd ${dir} && scp etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
|
||||
-----
|
||||
`);
|
||||
}
|
||||
if (copy)
|
||||
{
|
||||
const to_copy = use_auth
|
||||
? [ "antietcd_ca.crt", "antietcd.crt", "antietcd.key", "osd.crt", "osd.key", "client_ca.crt", "etcd_ca.crt", "etcd.crt", "etcd.key" ]
|
||||
: [ "etcd_ca.crt", "etcd.crt", "etcd.key" ];
|
||||
for (const node of etcds)
|
||||
{
|
||||
await system("scp "+dir+to_copy.join(" "+dir)+" root@"+node.ip+"/etc/vitastor/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn('Certificates generated in /etc/vitastor, please copy them to other nodes');
|
||||
}
|
||||
}
|
||||
|
||||
async function write_auth_config(config, config_path)
|
||||
{
|
||||
const auth = {};
|
||||
if (use_auth)
|
||||
{
|
||||
auth["use_antietcd"] = true;
|
||||
auth["etcd_proxy"] = {
|
||||
urls: etcds.map(e => e.ip+':2381'),
|
||||
cert: "/etc/vitastor/antietcd.crt",
|
||||
key: "/etc/vitastor/antietcd.key",
|
||||
ca: "/etc/vitastor/etcd_ca.crt",
|
||||
};
|
||||
auth["antietcd_cert"] = "/etc/vitastor/antietcd.crt";
|
||||
auth["antietcd_key"] = "/etc/vitastor/antietcd.key";
|
||||
auth["etcd_ca"] = "/etc/vitastor/antietcd_ca.crt";
|
||||
auth["osd_cert"] = "/etc/vitastor/osd.crt";
|
||||
auth["osd_pkey"] = "/etc/vitastor/osd.key";
|
||||
auth["osd_ca"] = "/etc/vitastor/osd.crt";
|
||||
auth["client_ca"] = "/etc/vitastor/client_ca.crt";
|
||||
auth["cert"] = "/etc/vitastor/admin.crt";
|
||||
auth["pkey"] = "/etc/vitastor/admin.key";
|
||||
}
|
||||
else
|
||||
{
|
||||
auth["etcd_ca"] = "/etc/vitastor/etcd.crt";
|
||||
}
|
||||
for (const k in auth)
|
||||
{
|
||||
if ((k in config) && JSON.stringify(auth[k]) != JSON.stringify(config[k]))
|
||||
{
|
||||
// Auth options already overridden with non-default
|
||||
console.log(k+" is already overridden in "+config_path+", skipping config update");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const k in auth)
|
||||
{
|
||||
config[k] = auth[k];
|
||||
}
|
||||
console.log(`-----
|
||||
Updating ${config_path}
|
||||
-----
|
||||
`);
|
||||
fs.writeFileSync(config_path, JSON.stringify(config, 0, 4));
|
||||
}
|
||||
|
||||
async configure_etcd()
|
||||
{
|
||||
const in_docker = fs.existsSync("/etc/vitastor/etcd.conf") &&
|
||||
fs.existsSync("/etc/vitastor/docker.conf");
|
||||
if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service"))
|
||||
@@ -45,30 +285,49 @@ async function run()
|
||||
console.log("/etc/systemd/system/etcd.service already exists");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
const etcd_url = etcds[num].scheme + '://' + etcds[num].addr;
|
||||
const options = {
|
||||
name: 'etcd'+etcds[num].ip.replace(/[^0-9a-z_]/ig, '_'),
|
||||
advertise_client_urls: etcd_url+':'+(use_auth ? 2381 : 2379),
|
||||
listen_client_urls: etcd_url+':'+(use_auth ? 2381 : 2379),
|
||||
initial_advertise_peer_urls: etcd_url+':2380',
|
||||
listen_peer_urls: etcd_url+':2380',
|
||||
initial_cluster_token: 'vitastor-etcd-1',
|
||||
initial_cluster_state: 'new',
|
||||
initial_cluster: etcds.map(e => `etcd${e.ip.replace(/[^0-9a-z_]/ig, '_')}=${e.scheme}://${e.addr}:2380`).join(','),
|
||||
snapshot_count: 10000,
|
||||
max_txn_ops: 100000,
|
||||
max_request_bytes: 104857600,
|
||||
auto_compaction_retention: 10,
|
||||
auto_compaction_mode: 'revision',
|
||||
};
|
||||
if (tls)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
options['cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_auth)
|
||||
{
|
||||
options['client_cert_auth'] = '1';
|
||||
options['trusted_ca_file'] = '/etc/vitastor/antietcd.crt';
|
||||
}
|
||||
options['peer_cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['peer_key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_auth)
|
||||
{
|
||||
options['peer_client_cert_auth'] = '1';
|
||||
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd.crt';
|
||||
}
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => (''+s).replace(/^https?:\/\/|(:\d+)?(\/.*)?$/g, '').replace(/^\[(.*)\]$/, '$1').toLowerCase());
|
||||
const num = select_local_etcd(etcds);
|
||||
if (num < 0)
|
||||
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
|
||||
? fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' })
|
||||
: "";
|
||||
for (const k in options)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(0);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_'+k.toUpperCase().replace(/-/, '_'), options[k]);
|
||||
}
|
||||
const etcd_url = 'http://' + (etcds[num].indexOf(':') >= 0 ? '['+etcds[num]+']' : etcds[num]);
|
||||
const etcd_name = 'etcd'+etcds[num].replace(/[^0-9a-z_]/ig, '_');
|
||||
const etcd_cluster = etcds.map(e => `etcd${e.replace(/[^0-9a-z_]/ig, '_')}=http://${e.indexOf(':') >= 0 ? '['+e+']' : e}:2380`).join(',');
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
if (in_docker)
|
||||
{
|
||||
let etcd_conf = fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' });
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_NAME', etcd_name);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_IP', etcds[num]);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_INITIAL_CLUSTER', etcd_cluster);
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
console.log('etcd for Vitastor configured. Run `systemctl enable --now vitastor-etcd` to start etcd');
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -83,12 +342,8 @@ Wants=network-online.target local-fs.target time-sync.target
|
||||
[Service]
|
||||
Restart=always
|
||||
Environment=GOGC=50
|
||||
ExecStart=etcd --name ${etcd_name} --data-dir /var/lib/etcd/vitastor \\
|
||||
--snapshot-count 10000 --advertise-client-urls ${etcd_url}:2379 --listen-client-urls ${etcd_url}:2379 \\
|
||||
--initial-advertise-peer-urls ${etcd_url}:2380 --listen-peer-urls ${etcd_url}:2380 \\
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster ${etcd_cluster} \\
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \\
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision
|
||||
EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
ExecStart=etcd --data-dir /var/lib/etcd/vitastor
|
||||
WorkingDirectory=/var/lib/etcd/vitastor
|
||||
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
|
||||
User=etcd
|
||||
@@ -106,7 +361,6 @@ WantedBy=multi-user.target
|
||||
// Disable distribution etcd unit and enable our one
|
||||
await system(`systemctl disable --now etcd`);
|
||||
await system(`systemctl enable --now vitastor-etcd`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function replace_env(text, key, value)
|
||||
@@ -119,16 +373,29 @@ function replace_env(text, key, value)
|
||||
function select_local_etcd(etcds)
|
||||
{
|
||||
const ifaces = os.networkInterfaces();
|
||||
const local = {};
|
||||
for (const ifname in ifaces)
|
||||
{
|
||||
for (const iface of ifaces[ifname])
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
if (etcds[i] == iface.address.toLowerCase())
|
||||
return i;
|
||||
{
|
||||
const addr = iface.address;
|
||||
if (iface.family == 'IPv6')
|
||||
local[addr.toLowerCase()] = local['['+addr.toLowerCase()+']'] = true;
|
||||
else
|
||||
local[addr] = true;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
{
|
||||
if (local[etcds[i].addr])
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
async function system(cmd)
|
||||
{
|
||||
console.log('Running '+cmd);
|
||||
const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
|
||||
let finish_cb;
|
||||
cp.on('exit', () => finish_cb && finish_cb());
|
||||
@@ -136,3 +403,19 @@ async function system(cmd)
|
||||
await new Promise(ok => finish_cb = ok);
|
||||
return cp.exitCode;
|
||||
}
|
||||
|
||||
async function make_ca(subj, filename)
|
||||
{
|
||||
if (await system("openssl req -days 3650 -x509 -subj '"+subj+"' -addext basicConstraints=critical,CA:TRUE,pathlen:1"+
|
||||
" -new -newkey rsa:4096 -nodes -keyout "+filename+".key -out "+filename+".crt"))
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function make_signed(subj, f, ca, san)
|
||||
{
|
||||
if (await system(`openssl req -subj '${subj}' ${san ? "-addext 'subjectAltName="+san+"'" : ""} -nodes -new -keyout ${f}.key -out ${f}.csr`))
|
||||
process.exit(1);
|
||||
if (await system(`openssl x509 -req -days 3650 -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
|
||||
process.exit(1);
|
||||
fs.unlinkSync(f+".csr");
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ function derive_osd_stats(st, prev, prev_diff)
|
||||
const n = c.count - BigInt(pr && pr.count||0);
|
||||
diff.recovery_stats[op] = { ...c, bps: n > 0 ? b*1000n/timediff : 0n, iops: n > 0 ? n*1000n/timediff : 0n };
|
||||
}
|
||||
diff.inode_stats = {};
|
||||
for (const pool_id in st.inode_stats||{})
|
||||
{
|
||||
diff.inode_stats[pool_id] = {};
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
// AntiEtcd authentication filter for Vitastor
|
||||
// (c) Vitaliy Filippov, 2026
|
||||
// License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
|
||||
|
||||
// Permissions are based on:
|
||||
// 1. Users.
|
||||
// Stored in /vitastor/config/user/<username>.
|
||||
// Has 2 properties:
|
||||
// - type, one of: admin, client.
|
||||
// admin has full access to all images and also to cluster config.
|
||||
// client has r/w access to owned images and r/o access to images with reader_group.
|
||||
// - groups, a list of group names the user is included in.
|
||||
// 2. Images.
|
||||
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
|
||||
// - owner (user name)
|
||||
// - owner_group (group name)
|
||||
// - reader_group
|
||||
// 3. Certificates.
|
||||
// - osd, mon use their own trusted certificates.
|
||||
|
||||
const { X509Certificate } = require('node:crypto');
|
||||
|
||||
const static_perms = {
|
||||
invalid: {
|
||||
keys: {},
|
||||
prefixes: {},
|
||||
},
|
||||
osd: {
|
||||
keys: { '/pg/config': false },
|
||||
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
|
||||
},
|
||||
mon: {
|
||||
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
|
||||
prefixes: {
|
||||
'/config/': false, '/osd/': false, '/mon/': true, '/pg/history/': true,
|
||||
'/pgstats/': false, '/inode/stats/': true, '/pool/stats/': true,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
keys: { '/stats': false },
|
||||
prefixes: {
|
||||
'/config/': true, '/osd/': true, '/index/': true, '/pg/history/': true,
|
||||
'/mon/': false, '/pg/': false, '/pgstats/': false, '/inode/stats/': false, '/pool/stats/': false,
|
||||
},
|
||||
},
|
||||
client: {
|
||||
keys: { '/config/global': false, '/config/node_placement': false, '/config/pools': false, '/pg/config': false },
|
||||
prefixes: { '/osd/state/': false, '/pg/state/': false, '/index/maxid/': false },
|
||||
},
|
||||
};
|
||||
|
||||
const api_perms = {
|
||||
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
admin: { maintenance_status: true },
|
||||
client: { maintenance_status: true },
|
||||
};
|
||||
|
||||
class VitastorAuthFilter
|
||||
{
|
||||
constructor(antietcd)
|
||||
{
|
||||
this.cfg = antietcd.cfg;
|
||||
this.antietcd = antietcd;
|
||||
this.prefix = this.cfg.vitastor_prefix || '/vitastor';
|
||||
this.prefix_parts = this.prefix.split('/');
|
||||
}
|
||||
|
||||
async init()
|
||||
{
|
||||
if (!this.cfg.cert || !this.cfg.key || !this.cfg.osd_ca || !this.cfg.etcd_proxy && !this.cfg.peer_ca || !this.cfg.client_cert_auth)
|
||||
{
|
||||
throw new Error('Authenticated Vitastor setups require enabled client_cert_auth, cert, key'+
|
||||
' and separate ca (client CA), osd_ca'+(this.cfg.etcd_proxy ? '' : ', peer_ca')+' and optionally mon_ca');
|
||||
}
|
||||
this.osd_ca = await this.antietcd.readPEM(this.cfg.osd_ca);
|
||||
this.osd_ca_obj = new X509Certificate(this.osd_ca);
|
||||
this.antietcd.tls.ca.push(this.osd_ca);
|
||||
if (this.cfg.mon_ca)
|
||||
{
|
||||
this.mon_ca = await this.antietcd.readPEM(this.cfg.mon_ca);
|
||||
this.mon_ca_obj = new X509Certificate(this.mon_ca_obj);
|
||||
this.antietcd.tls.ca.push(this.mon_ca);
|
||||
}
|
||||
}
|
||||
|
||||
init_context(context, clientCert)
|
||||
{
|
||||
let cert = clientCert;
|
||||
while (cert)
|
||||
{
|
||||
if (cert.fingerprint256 == this.osd_ca_obj.fingerprint256)
|
||||
{
|
||||
context.user_type = 'osd';
|
||||
break;
|
||||
}
|
||||
if (this.mon_ca_obj && cert.fingerprint256 == this.mon_ca_obj.fingerprint256)
|
||||
{
|
||||
context.user_type = 'mon';
|
||||
break;
|
||||
}
|
||||
cert = cert.issuerCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
_get(path, decode)
|
||||
{
|
||||
let cur = this.antietcd.etctree.state;
|
||||
path = path instanceof Array ? path : path.split('/');
|
||||
for (const p of path)
|
||||
{
|
||||
if (!cur.children)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
cur = cur.children[p];
|
||||
if (!cur)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (decode)
|
||||
{
|
||||
return this._decode(path, cur.value);
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
_decode(path, cur)
|
||||
{
|
||||
if (!cur)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (cur)
|
||||
{
|
||||
try
|
||||
{
|
||||
cur = JSON.parse(cur);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.warn('Invalid JSON in '+(path instanceof Array ? path.join('/') : path)+': '+e);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
// userInfo: { name: string, type: string, perms: static_perms[type], groups: { [string]: true } }
|
||||
_check_compare(check, userInfo, checked)
|
||||
{
|
||||
let key = String(check.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (key in userInfo.perms.keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userInfo.type == 'client')
|
||||
{
|
||||
// Image permissions
|
||||
if (key.substr(0, 14) == '/config/inode/')
|
||||
{
|
||||
// Allowed to check that a key does not exist
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
const data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
const inode = this._decode(check.key, data.value);
|
||||
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/image/')
|
||||
{
|
||||
// Allowed to check that a key does not exist
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
let data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
data = this._decode(check.key, data.value);
|
||||
if (data)
|
||||
{
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
|
||||
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/maxid/')
|
||||
{
|
||||
const pool_id = key.substr(13);
|
||||
const pool_cfg = this._get([ ...this.prefix_parts, 'config', 'pools' ], true);
|
||||
if (!pool_cfg || !pool_cfg[pool_id] || !pool_cfg[pool_id].creator_group || !userInfo.groups[pool_cfg[pool_id].creator_group])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['I'+parseInt(key.substr(13))+'_0'] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
const data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
checked['I'+parseInt(key.substr(13))+'_'+data.value] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_read(kv, userInfo)
|
||||
{
|
||||
let key = String(kv.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (key in userInfo.perms.keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userInfo.type == 'client')
|
||||
{
|
||||
// Image permissions
|
||||
if (key.substr(0, 14) == '/config/inode/')
|
||||
{
|
||||
const inode = this._decode(kv.key, kv.value);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/image/')
|
||||
{
|
||||
const data = this._decode(kv.key, kv.value);
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/inode/stats/')
|
||||
{
|
||||
const [ pool_id, id ] = key.substr(13).split('/');
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', pool_id, id ], true);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_write(put, userInfo, checked)
|
||||
{
|
||||
let key = String(put.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (userInfo.perms.keys[key])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (checked && userInfo.type == 'client')
|
||||
{
|
||||
if (key.substr(0, 13) == '/index/maxid/' &&
|
||||
checked['I'+parseInt(key.substr(13))+'_'+(put.value-1)])
|
||||
{
|
||||
// Allowed to increment maxid
|
||||
return true;
|
||||
}
|
||||
if (checked['M'+key])
|
||||
{
|
||||
// Allowed to modify known images with CAS checks
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_req(req, userInfo, checked)
|
||||
{
|
||||
let r;
|
||||
if ((r = (req.request_range || req.requestRange)))
|
||||
{
|
||||
// All range queries are allowed, but responses are filtered - it's simpler
|
||||
}
|
||||
else if ((r = (req.request_put || req.requestPut)))
|
||||
{
|
||||
if (!this._check_write(r, userInfo, checked))
|
||||
return false;
|
||||
}
|
||||
else if ((r = (req.request_delete_range || req.requestDeleteRange)))
|
||||
{
|
||||
if (!r.range_end || r.range_end === r.key)
|
||||
{
|
||||
if (!this._check_write({ key: r.key }, userInfo))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All keys in range must satisfy prefix
|
||||
r.range_end = String(r.range_end);
|
||||
if (r.key.length != r.range_end.length ||
|
||||
r.key[r.key.length-1] != '/' ||
|
||||
r.range_end[r.range_end.length-1] != '0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let key = r.key.substr(this.prefix.length);
|
||||
let found = false;
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_get_user(context)
|
||||
{
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
{
|
||||
return {
|
||||
name: context.user_type,
|
||||
type: context.user_type,
|
||||
perms: static_perms[context.user_type],
|
||||
};
|
||||
}
|
||||
if (!context.username)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
if (!userInfo)
|
||||
{
|
||||
userInfo = { type: 'client' };
|
||||
}
|
||||
else if (userInfo.type !== 'client' && userInfo.type !== 'admin')
|
||||
{
|
||||
userInfo.type = 'client';
|
||||
}
|
||||
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
|
||||
userInfo.name = context.username;
|
||||
if (userInfo.groups instanceof Array)
|
||||
{
|
||||
userInfo.groups = userInfo.groups.reduce((a, c) => { a[c] = true; return a; }, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
userInfo.groups = {};
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
filter_api(context, api/*, data*/)
|
||||
{
|
||||
let type = 'client';
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
{
|
||||
type = context.user_type;
|
||||
}
|
||||
else if (context.username)
|
||||
{
|
||||
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
if (userInfo && userInfo.type === 'admin')
|
||||
{
|
||||
type = 'admin';
|
||||
}
|
||||
}
|
||||
return api_perms[type] && api_perms[type][api];
|
||||
}
|
||||
|
||||
filter_txn(context, txn)
|
||||
{
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
const checked = {};
|
||||
if (txn.compare)
|
||||
{
|
||||
for (const check of txn.compare)
|
||||
{
|
||||
if (!this._check_compare(check, userInfo, checked))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Special transactions:
|
||||
// 1. create image: create config/inode and index/image, increment index/maxid/<pool> (with CAS)
|
||||
// 2. create snapshot: same as create image but also rename previous to @snap
|
||||
if (txn.success)
|
||||
{
|
||||
for (const req of txn.success)
|
||||
{
|
||||
if (!this._check_req(req, userInfo, checked))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (txn.failure)
|
||||
{
|
||||
for (const req of txn.failure)
|
||||
{
|
||||
if (!this._check_req(req, userInfo, null))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return txn;
|
||||
}
|
||||
|
||||
filter_txn_response(context, txn, res)
|
||||
{
|
||||
if (!res.responses)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
for (const resp of res.responses)
|
||||
{
|
||||
if (resp.response_range && resp.response_range.kvs)
|
||||
{
|
||||
resp.response_range.kvs = [];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const resp of res.responses)
|
||||
{
|
||||
if (resp.response_range && resp.response_range.kvs)
|
||||
{
|
||||
resp.response_range.kvs = resp.response_range.kvs.filter(kv => this._check_read(kv, userInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter_watch_message(context, msg)
|
||||
{
|
||||
if (!msg.result || !msg.result.events)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
msg.result.events = [];
|
||||
return;
|
||||
}
|
||||
msg.result.events = msg.result.events.filter(ev => this._check_read(ev.kv, userInfo));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VitastorAuthFilter;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor",
|
||||
"version": "3.0.9",
|
||||
"version": "3.0.12",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.9'
|
||||
VITASTOR_VERSION = '3.0.12'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -11,7 +11,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/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
|
||||
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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.9
|
||||
Version: 3.0.12
|
||||
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.9.el10.tar.gz
|
||||
Source0: vitastor-3.0.12.el10.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}
|
||||
|
||||
@@ -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.9
|
||||
Version: 3.0.12
|
||||
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.9.el7.tar.gz
|
||||
Source0: vitastor-3.0.12.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.9
|
||||
Version: 3.0.12
|
||||
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.9.el8.tar.gz
|
||||
Source0: vitastor-3.0.12.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.9
|
||||
Version: 3.0.12
|
||||
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.9.el9.tar.gz
|
||||
Source0: vitastor-3.0.12.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}
|
||||
|
||||
+11
-1
@@ -20,7 +20,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
|
||||
endif()
|
||||
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
|
||||
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.9")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.12")
|
||||
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})
|
||||
@@ -69,11 +69,21 @@ pkg_check_modules(ISAL libisal)
|
||||
if (ISAL_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL)
|
||||
endif (ISAL_LIBRARIES)
|
||||
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
|
||||
if (ISAL_CRYPTO_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL_CRYPTO)
|
||||
endif (ISAL_CRYPTO_LIBRARIES)
|
||||
pkg_check_modules(RDMACM librdmacm)
|
||||
if (RDMACM_LIBRARIES)
|
||||
add_definitions(-DWITH_RDMACM)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(-DWITH_OPENSSL)
|
||||
|
||||
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})
|
||||
|
||||
@@ -83,19 +83,24 @@ 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() &&
|
||||
(config["discard_on_start"] == "true" || config["discard_on_start"] == "1" || config["discard_on_start"] == "yes");
|
||||
gc_on_start = config.find("gc_on_start") == config.end() ||
|
||||
(config["gc_on_start"] == "true" || config["gc_on_start"] == "1" || config["gc_on_start"] == "yes");
|
||||
skip_double_claim = (config["skip_double_claim"] == "true" || config["skip_double_claim"] == "1" || config["skip_double_claim"] == "yes");
|
||||
min_discard_size = parse_size(config["min_discard_size"]);
|
||||
if (!min_discard_size)
|
||||
min_discard_size = 1024*1024;
|
||||
|
||||
@@ -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
|
||||
@@ -59,6 +60,8 @@ struct blockstore_disk_t
|
||||
bool discard_on_start = false;
|
||||
// GC on start (new store)
|
||||
bool gc_on_start = true;
|
||||
// Skip double claim conflicts on start (new store, temporary until the bug is found)
|
||||
bool skip_double_claim = false;
|
||||
uint64_t min_discard_size = 1024*1024;
|
||||
uint64_t discard_granularity = 0;
|
||||
|
||||
|
||||
@@ -174,14 +174,18 @@ bool journal_flusher_co::loop()
|
||||
else if (wait_state == 19) goto resume_19;
|
||||
else if (wait_state == 20) goto resume_20;
|
||||
else if (wait_state == 21) goto resume_21;
|
||||
else if (wait_state == 22) goto resume_22;
|
||||
else if (wait_state == 23) goto resume_23;
|
||||
else if (wait_state == 24) goto resume_24;
|
||||
else if (wait_state == 25) goto resume_25;
|
||||
resume_0:
|
||||
wait_state = 0;
|
||||
wait_count = 0;
|
||||
cur_oid = {};
|
||||
res = bs->heap->get_next_compact(cur_oid);
|
||||
// Advance fsynced_lsn every <journal_trim_interval> intent writes
|
||||
if ((bs->intent_write_counter >= bs->journal_trim_interval) && co_id == 0)
|
||||
{
|
||||
// Advance fsynced_lsn every <journal_trim_interval> intent writes
|
||||
bs->intent_write_counter = 0;
|
||||
resume_17:
|
||||
resume_18:
|
||||
@@ -196,6 +200,7 @@ resume_21:
|
||||
if (res == ENOENT && flusher->force_start > 0 && co_id == 0 &&
|
||||
(!bs->dsk.disable_journal_fsync || !bs->dsk.disable_meta_fsync || !bs->dsk.disable_data_fsync))
|
||||
{
|
||||
// When under pressure, do an additional fsync to force entries to be marked compactable
|
||||
flusher->active_flushers++;
|
||||
resume_14:
|
||||
resume_15:
|
||||
@@ -259,11 +264,9 @@ resume_1:
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE ||
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity)
|
||||
{
|
||||
auto res = bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity && !bs->perfect_csum_update
|
||||
? COPY_BUF_SKIP_CSUM : 0);
|
||||
if (res > 0)
|
||||
copy_count++;
|
||||
}
|
||||
});
|
||||
if (!compact_info.compact_lsn)
|
||||
@@ -273,6 +276,25 @@ resume_1:
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
flusher->active_flushers++;
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
{
|
||||
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
|
||||
{
|
||||
copy_count++;
|
||||
}
|
||||
}
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
init_fsync_data();
|
||||
}
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
@@ -291,13 +313,6 @@ resume_1:
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
flusher->active_flushers++;
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
overwrite_start = overwrite_end = 0;
|
||||
if (read_vec.size() > 0)
|
||||
{
|
||||
@@ -336,6 +351,13 @@ resume_3:
|
||||
if (res == ENOENT || res == EDOM)
|
||||
{
|
||||
// Abort compaction
|
||||
abort_compact:
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
cur_sync->member_count--;
|
||||
if (cur_sync->member_count > 0)
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
@@ -349,10 +371,7 @@ resume_4:
|
||||
if (res == ENOENT)
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
goto resume_0;
|
||||
goto abort_compact;
|
||||
}
|
||||
if (res == EAGAIN)
|
||||
{
|
||||
@@ -381,14 +400,14 @@ resume_9:
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
{
|
||||
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED) ||
|
||||
(read_vec[i].copy_flags & COPY_BUF_PADDED)) // FIXME Shit, simplify these flags
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
|
||||
{
|
||||
assert(read_vec[i].buf);
|
||||
await_sqe(10);
|
||||
data->iov = (struct iovec){ read_vec[i].buf + (read_vec[i].copy_flags & COPY_BUF_PADDED
|
||||
? read_vec[i].offset - read_vec[i].disk_offset : 0), (size_t)read_vec[i].len };
|
||||
data->callback = simple_callback_w;
|
||||
assert(clean_loc + read_vec[i].offset + data->iov.iov_len <= bs->dsk.block_count*bs->dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + read_vec[i].offset);
|
||||
wait_count++;
|
||||
}
|
||||
@@ -399,6 +418,17 @@ resume_11:
|
||||
wait_state = 11;
|
||||
return false;
|
||||
}
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
resume_22:
|
||||
resume_23:
|
||||
resume_24:
|
||||
resume_25:
|
||||
if (!fsync_data(22))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Lock is only needed to prevent freeing the big_write because we overwrite it...
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
// Mark the object compacted, but don't free and remove small_writes
|
||||
@@ -408,12 +438,14 @@ resume_11:
|
||||
if (!cur_obj)
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
if (!calc_block_checksums())
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -422,6 +454,7 @@ resume_11:
|
||||
if (res == EBUSY)
|
||||
{
|
||||
// Abort compaction, object is already overwritten by something else
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -586,7 +619,7 @@ int journal_flusher_co::check_and_punch_checksums()
|
||||
bs->heap->calc_block_checksums((uint32_t*)(new_csums+csum_off), vec.buf, punch_bmp, vec.offset, vec.offset+vec.len, true, NULL);
|
||||
}
|
||||
}
|
||||
// Modified, we should add_punch_holes and then write the block to disk
|
||||
// Modified, we should punch_holes and then write the block to disk
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
@@ -699,6 +732,67 @@ resume_1:
|
||||
return true;
|
||||
}
|
||||
|
||||
void journal_flusher_co::init_fsync_data()
|
||||
{
|
||||
cur_sync = flusher->data_syncs.begin();
|
||||
if (cur_sync == flusher->data_syncs.end() || cur_sync->ready_count > 0)
|
||||
{
|
||||
cur_sync = flusher->data_syncs.emplace(cur_sync);
|
||||
}
|
||||
cur_sync->member_count++;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::fsync_data(int wait_base)
|
||||
{
|
||||
if (wait_state == wait_base)
|
||||
goto resume_0;
|
||||
else if (wait_state == wait_base+1)
|
||||
goto resume_1;
|
||||
else if (wait_state == wait_base+2)
|
||||
goto resume_2;
|
||||
else if (wait_state == wait_base+3)
|
||||
goto resume_3;
|
||||
cur_sync->ready_count++;
|
||||
resume_0:
|
||||
if (cur_sync->ready_count < cur_sync->member_count)
|
||||
{
|
||||
wait_state = wait_base;
|
||||
return false;
|
||||
}
|
||||
if (!cur_sync->sent)
|
||||
{
|
||||
// Sync batch is ready. Do it.
|
||||
await_sqe(1);
|
||||
data->iov = { 0 };
|
||||
data->callback = simple_callback_w;
|
||||
io_uring_prep_fsync(sqe, bs->dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
cur_sync->sent = true;
|
||||
wait_count++;
|
||||
resume_2:
|
||||
if (wait_count > 0)
|
||||
{
|
||||
wait_state = wait_base+2;
|
||||
return false;
|
||||
}
|
||||
cur_sync->done = true;
|
||||
// Wake up other flushers
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
resume_3:
|
||||
if (!cur_sync->done)
|
||||
{
|
||||
wait_state = wait_base+3;
|
||||
return false;
|
||||
}
|
||||
cur_sync->done_count++;
|
||||
if (cur_sync->done_count >= cur_sync->member_count)
|
||||
{
|
||||
flusher->data_syncs.erase(cur_sync);
|
||||
cur_sync = flusher->data_syncs.end();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::fsync_meta(int wait_base)
|
||||
{
|
||||
if (wait_state == wait_base) goto resume_0;
|
||||
|
||||
@@ -25,6 +25,15 @@ struct flusher_meta_write_t
|
||||
std::map<uint64_t, meta_sector_t>::iterator it;
|
||||
};
|
||||
|
||||
struct flusher_data_sync_t
|
||||
{
|
||||
int member_count = 0;
|
||||
int ready_count = 0;
|
||||
int done_count = 0;
|
||||
bool sent = false;
|
||||
bool done = false;
|
||||
};
|
||||
|
||||
class journal_flusher_t;
|
||||
|
||||
// Journal flusher coroutine
|
||||
@@ -58,6 +67,7 @@ class journal_flusher_co
|
||||
int i, res;
|
||||
bool read_to_fill_incomplete;
|
||||
int copy_count;
|
||||
std::list<flusher_data_sync_t>::iterator cur_sync;
|
||||
|
||||
friend class journal_flusher_t;
|
||||
|
||||
@@ -68,6 +78,8 @@ class journal_flusher_co
|
||||
bool calc_block_checksums();
|
||||
bool write_meta_block(int wait_base);
|
||||
bool read_buffered(int wait_base);
|
||||
void init_fsync_data();
|
||||
bool fsync_data(int wait_base);
|
||||
bool fsync_meta(int wait_base);
|
||||
bool fsync_buffer(int wait_base);
|
||||
bool trim_lsn(int wait_base);
|
||||
@@ -88,6 +100,7 @@ class journal_flusher_t
|
||||
|
||||
robin_hood::unordered_flat_set<object_id> flushing;
|
||||
int active_flushers = 0;
|
||||
std::list<flusher_data_sync_t> data_syncs;
|
||||
int wanting_meta_fsync = 0;
|
||||
bool fsyncing_meta = false;
|
||||
int syncing_buffer = 0;
|
||||
|
||||
+441
-217
@@ -12,6 +12,7 @@
|
||||
#include "blockstore_heap.h"
|
||||
#include "../util/allocator.h"
|
||||
#include "../util/crc32c.h"
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/malloc_or_die.h"
|
||||
|
||||
#define BS_HEAP_FREE_MVCC 1
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
#define IMAP_MALLOC_LOW_BITS ((size_t)0x0F)
|
||||
#define IMAP_MAX_LOW 16
|
||||
#define POSTPONE_INSERT_COUNT 10
|
||||
|
||||
#define list_item_overhead(a) (((a) + sizeof(heap_list_item_t) - sizeof(heap_entry_t) + sizeof(void*) + 15) & ~15)
|
||||
|
||||
@@ -64,19 +66,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));
|
||||
}
|
||||
|
||||
@@ -91,7 +93,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;
|
||||
}
|
||||
@@ -126,26 +128,26 @@ uint32_t heap_entry_t::get_size(blockstore_heap_t *heap)
|
||||
return heap->get_simple_entry_size();
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_overwrite()
|
||||
bool heap_entry_t::is_overwrite() const
|
||||
{
|
||||
return ((entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_BIG_WRITE|BS_HEAP_STABLE) ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_BIG_INTENT|BS_HEAP_STABLE) ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_compactable()
|
||||
bool heap_entry_t::is_compactable() const
|
||||
{
|
||||
return !is_overwrite() && (entry_type & BS_HEAP_STABLE) ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == BS_HEAP_COMMIT ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == BS_HEAP_ROLLBACK;
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_before(heap_entry_t *other)
|
||||
bool heap_entry_t::is_before(const heap_entry_t *other) const
|
||||
{
|
||||
return lsn < other->lsn || lsn == other->lsn && !is_overwrite() && other->is_overwrite();
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_garbage()
|
||||
bool heap_entry_t::is_garbage() const
|
||||
{
|
||||
return (entry_type & BS_HEAP_GARBAGE);
|
||||
}
|
||||
@@ -214,15 +216,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;
|
||||
@@ -334,19 +345,12 @@ corrupted_block:
|
||||
block_num, block_offset, wr->size, sizeof(heap_entry_t));
|
||||
goto corrupted_block;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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
|
||||
// At the same time, further modifications _after_ putting new entries into the block,
|
||||
// but _before_ writing it, may mark some entries in it as garbage. That's why garbage
|
||||
// entries may still be present on disk.
|
||||
wr->entry_type &= ~BS_HEAP_GARBAGE;
|
||||
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)) ||
|
||||
@@ -387,12 +391,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 l%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). ",
|
||||
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_crc32c, wr->crc32c);
|
||||
block_num, block_offset, expected_checksum, wr->checksum);
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify offset & len
|
||||
@@ -415,6 +419,13 @@ corrupted_object:
|
||||
wr->inode, wr->stripe, wr->version, wr->big_intent().offset, wr->big_intent().len);
|
||||
goto corrupted_object;
|
||||
}
|
||||
if ((wr->type() == BS_HEAP_BIG_INTENT || wr->type() == BS_HEAP_BIG_WRITE) &&
|
||||
wr->big().block_num >= dsk->block_count)
|
||||
{
|
||||
fprintf(stderr, "Error: big_write or big_intent entry %jx:%jx v%ju block_num is too large: %u > %lu. Metadata is incompatible with current parameters. ",
|
||||
wr->inode, wr->stripe, wr->version, wr->big_intent().block_num, dsk->block_count);
|
||||
goto corrupted_object;
|
||||
}
|
||||
handle_write(block_num, wr);
|
||||
block_offset += wr->size;
|
||||
}
|
||||
@@ -441,7 +452,7 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
|
||||
next_lsn = wr->lsn;
|
||||
}
|
||||
entries_loaded++;
|
||||
loaded_list_items.push_back(li);
|
||||
insert_list_items(&li, 1, true);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
if (!inf.entries.size())
|
||||
@@ -547,18 +558,26 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
|
||||
|
||||
void blockstore_heap_t::finish_load()
|
||||
{
|
||||
if (loaded_list_items.size())
|
||||
if (postponed_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)
|
||||
// Sort "postponed" items and load in batches
|
||||
std::sort(postponed_items.begin(), postponed_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
|
||||
{
|
||||
return a->entry.lsn < b->entry.lsn;
|
||||
return a->entry.inode < b->entry.inode || a->entry.inode == b->entry.inode &&
|
||||
(a->entry.stripe < b->entry.stripe || a->entry.stripe == b->entry.stripe &&
|
||||
!a->entry.is_before(&b->entry)); // object ASC, lsn DESC
|
||||
});
|
||||
for (auto & li: loaded_list_items)
|
||||
size_t s = 0, e, n = postponed_items.size();
|
||||
for (e = 1; e <= n; e++)
|
||||
{
|
||||
insert_list_item(li);
|
||||
if (e >= n || postponed_items[e]->entry.inode != postponed_items[s]->entry.inode &&
|
||||
postponed_items[e]->entry.stripe != postponed_items[s]->entry.stripe)
|
||||
{
|
||||
insert_list_items(postponed_items.data()+s, e-s, false);
|
||||
s = e;
|
||||
}
|
||||
}
|
||||
loaded_list_items.clear();
|
||||
postponed_items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,26 +590,11 @@ void blockstore_heap_t::fill_recheck_queue()
|
||||
inode_map_iterate(ip.second, [&](heap_list_item_t *li)
|
||||
{
|
||||
auto obj = &li->entry;
|
||||
// Add object to recheck queue
|
||||
if (obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
|
||||
// Recheck only the latest intent_write (if after completed_lsn) or a series of small_writes
|
||||
if ((obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
|
||||
&& obj->lsn > completed_lsn || obj->type() == BS_HEAP_SMALL_WRITE)
|
||||
{
|
||||
// Recheck only the latest intent_write
|
||||
if (obj->lsn > completed_lsn)
|
||||
{
|
||||
// Do not recheck if it's already marked as completed in the superblock
|
||||
recheck_queue.push_back(obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Or recheck a series of small_writes
|
||||
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
{
|
||||
if (wr->small().len > 0)
|
||||
{
|
||||
recheck_queue.push_back(wr);
|
||||
}
|
||||
}
|
||||
recheck_queue.push_back(obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -600,6 +604,11 @@ void blockstore_heap_t::fill_recheck_queue()
|
||||
int blockstore_heap_t::mark_used_blocks()
|
||||
{
|
||||
int res = 0;
|
||||
std::vector<heap_list_item_t*> used_by;
|
||||
if (dsk->skip_double_claim)
|
||||
{
|
||||
used_by.resize(dsk->block_count);
|
||||
}
|
||||
for (auto & pgp: block_index)
|
||||
{
|
||||
for (auto & ip: pgp.second)
|
||||
@@ -653,10 +662,34 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
{
|
||||
if (is_data_used(wr->big_location(this)))
|
||||
{
|
||||
fprintf(stderr, "Error: double-claimed data block %u, second time by %jx:%jx l%ju\n",
|
||||
wr->big().block_num, wr->inode, wr->stripe, wr->lsn);
|
||||
res = EDOM;
|
||||
return;
|
||||
if (dsk->skip_double_claim)
|
||||
{
|
||||
// There is a BUG currently:
|
||||
// Sometimes (under unknown conditions) deletion entries are removed from the disk
|
||||
// earlier than previous big_writes.
|
||||
// Until it's fixed, we provide a way to ignore such objects on start.
|
||||
auto prev_li = used_by[wr->big().block_num];
|
||||
assert(prev_li);
|
||||
// Newer LSN must be trusted. Remove the older object.
|
||||
fprintf(stderr, "Block %u is double-claimed by entries %jx:%jx l%ju and %jx:%jx l%ju\n",
|
||||
wr->big().block_num, prev_li->entry.inode, prev_li->entry.stripe, prev_li->entry.lsn, wr->inode, wr->stripe, wr->lsn);
|
||||
if (init_erase_double_claim(prev_li, li))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: double-claimed data block %u, second time by %jx:%jx l%ju\n",
|
||||
wr->big().block_num, wr->inode, wr->stripe, wr->lsn);
|
||||
res = EDOM;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dsk->skip_double_claim)
|
||||
{
|
||||
// Record the object which uses the data block
|
||||
used_by[wr->big().block_num] = li;
|
||||
}
|
||||
use_data(wr->inode, wr->big_location(this));
|
||||
}
|
||||
@@ -680,6 +713,116 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
return res;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::init_free_bad_entry(heap_entry_t *wr)
|
||||
{
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE)
|
||||
{
|
||||
free_buffer_area(wr->inode, wr->small().location, wr->small().len);
|
||||
}
|
||||
else if (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
free_data(wr->inode, wr->big_location(this));
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::init_erase_bad_entry(heap_list_item_t *li)
|
||||
{
|
||||
modify_alloc(li->block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
for (size_t i = 0; i < inf.entries.size(); i++)
|
||||
{
|
||||
if (inf.entries[i] == li)
|
||||
{
|
||||
inf.entries.erase(inf.entries.begin()+i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
inf.used_space -= li->entry.size;
|
||||
inf.garbage_space -= (li->entry.is_garbage() ? li->entry.size : 0);
|
||||
});
|
||||
recheck_modified_blocks.insert(li->block_num);
|
||||
unlink_list_item(li);
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li)
|
||||
{
|
||||
bool erase_prev = false;
|
||||
bool erase_cur = false;
|
||||
if (prev_li->entry.lsn < cur_li->entry.lsn)
|
||||
{
|
||||
erase_prev = true;
|
||||
auto latest_li = prev_li;
|
||||
while (latest_li->next)
|
||||
{
|
||||
latest_li = latest_li->next;
|
||||
}
|
||||
if (latest_li->entry.lsn >= cur_li->entry.lsn)
|
||||
{
|
||||
// LSN ranges intersect, erase both
|
||||
erase_cur = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
erase_cur = true;
|
||||
auto latest_li = cur_li;
|
||||
while (latest_li->next)
|
||||
{
|
||||
latest_li = latest_li->next;
|
||||
}
|
||||
if ((latest_li->entry.inode != prev_li->entry.inode ||
|
||||
latest_li->entry.stripe != prev_li->entry.stripe) &&
|
||||
latest_li->entry.lsn >= prev_li->entry.lsn)
|
||||
{
|
||||
// LSN ranges intersect, erase both
|
||||
erase_prev = true;
|
||||
}
|
||||
}
|
||||
if (erase_prev)
|
||||
{
|
||||
fprintf(stderr, "Erasing object %jx:%jx due to double-claim\n", prev_li->entry.inode, prev_li->entry.stripe);
|
||||
auto erase_li = prev_li;
|
||||
while (erase_li->next)
|
||||
{
|
||||
erase_li = erase_li->next;
|
||||
}
|
||||
bool overwritten = false;
|
||||
while (erase_li)
|
||||
{
|
||||
auto prev_erase_li = erase_li->prev;
|
||||
if (!overwritten)
|
||||
{
|
||||
init_free_bad_entry(&erase_li->entry);
|
||||
overwritten = erase_li->entry.is_overwrite();
|
||||
}
|
||||
init_erase_bad_entry(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
if (erase_cur)
|
||||
{
|
||||
fprintf(stderr, "Erasing object %jx:%jx due to double-claim\n", cur_li->entry.inode, cur_li->entry.stripe);
|
||||
auto erase_li = cur_li->next;
|
||||
while (erase_li)
|
||||
{
|
||||
// Only newer entries are marked as used
|
||||
auto next_erase_li = erase_li->next;
|
||||
init_free_bad_entry(&erase_li->entry);
|
||||
init_erase_bad_entry(erase_li);
|
||||
erase_li = next_erase_li;
|
||||
}
|
||||
erase_li = cur_li;
|
||||
// Older ones are not
|
||||
while (erase_li)
|
||||
{
|
||||
auto prev_erase_li = erase_li->prev;
|
||||
init_erase_bad_entry(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
return erase_cur;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_full_gc()
|
||||
{
|
||||
uint32_t block_num = 0;
|
||||
@@ -720,80 +863,98 @@ void blockstore_heap_t::recheck_full_gc()
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_buffer(heap_entry_t *cwr, uint8_t *buf)
|
||||
void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr)
|
||||
{
|
||||
auto free_entry = [&](heap_list_item_t *li)
|
||||
// write entry is invalid, erase it and all newer entries
|
||||
int bad_count = 1;
|
||||
for (auto wr = obj; wr && wr != bad_wr; wr = prev(wr))
|
||||
{
|
||||
uint32_t block_num = li->block_num;
|
||||
auto wr_size = li->entry.size;
|
||||
if (li->entry.is_garbage())
|
||||
{
|
||||
garbage_entries--;
|
||||
garbage_memory -= list_item_overhead(wr_size);
|
||||
}
|
||||
live_entries--;
|
||||
live_memory -= list_item_overhead(wr_size);
|
||||
free(li);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
inf.used_space -= wr_size;
|
||||
bool found = false;
|
||||
for (auto it = inf.entries.begin(); it != inf.entries.end(); it++)
|
||||
{
|
||||
if (*it == li)
|
||||
{
|
||||
found = true;
|
||||
inf.entries.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(found);
|
||||
});
|
||||
recheck_modified_blocks.insert(block_num);
|
||||
};
|
||||
if (cwr->is_garbage())
|
||||
{
|
||||
// already freed after rechecking one of the previous small_write entries
|
||||
free_entry(list_item(cwr));
|
||||
bad_count++;
|
||||
}
|
||||
else if (!calc_checksums(cwr, buf, false))
|
||||
auto prev_wr = prev(bad_wr);
|
||||
if (prev_wr)
|
||||
{
|
||||
// write entry is invalid, erase it and mark newer entries with garbage bit
|
||||
auto & pg_idx = block_index[get_pg_id(cwr->inode, cwr->stripe)];
|
||||
auto & inode_idx = pg_idx[cwr->inode];
|
||||
heap_inode_map_t::iterator li_it;
|
||||
heap_list_item_t *li = NULL;
|
||||
inode_map_get(inode_idx, li_it, li, cwr->stripe);
|
||||
int rolled_back = 1;
|
||||
while (li && cwr != &li->entry)
|
||||
fprintf(stderr, "Notice: %u unfinished %s to %jx:%jx v%ju since good lsn %ju, rolling back\n",
|
||||
bad_count, bad_count > 1 ? "writes" : "write", obj->inode, obj->stripe, obj->version, prev_wr->lsn);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Notice: the whole object %jx:%jx only has unfinished writes, rolling back\n", obj->inode, obj->stripe);
|
||||
}
|
||||
auto li = list_item(obj);
|
||||
while (li && prev_wr != &li->entry)
|
||||
{
|
||||
auto prev = li->prev;
|
||||
assert(li->entry.type() == bad_wr->type());
|
||||
init_erase_bad_entry(li);
|
||||
li = prev;
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_start_reads(heap_recheck_state_t *st)
|
||||
{
|
||||
if (st->sent_reads >= st->total_reads)
|
||||
return;
|
||||
while (recheck_in_progress < recheck_queue_depth)
|
||||
{
|
||||
auto wr = st->next_wr;
|
||||
st->next_wr = prev(st->next_wr);
|
||||
uint64_t loc = 0, len = 0;
|
||||
bool from_data = false;
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE)
|
||||
{
|
||||
assert(li->entry.entry_type == cwr->entry_type);
|
||||
auto prev = li->prev;
|
||||
li->next = li->prev = NULL;
|
||||
if (!li->entry.is_garbage())
|
||||
{
|
||||
garbage_entries++;
|
||||
garbage_memory += list_item_overhead(li->entry.size);
|
||||
li->entry.set_garbage();
|
||||
}
|
||||
li = prev;
|
||||
rolled_back++;
|
||||
loc = wr->small().location;
|
||||
len = wr->small().len;
|
||||
}
|
||||
assert(li);
|
||||
if (li->prev)
|
||||
else if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
fprintf(stderr, "Notice: %u unfinished %s to %jx:%jx v%ju since lsn %ju, rolling back\n",
|
||||
rolled_back, rolled_back > 1 ? "writes" : "write", cwr->inode, cwr->stripe, li->prev->entry.version, li->entry.lsn);
|
||||
inode_map_replace(inode_idx, li_it, li->prev);
|
||||
li->prev->next = NULL;
|
||||
auto & bi = wr->big_intent();
|
||||
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
|
||||
len = bi.len;
|
||||
from_data = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Notice: the whole object %jx:%jx only has unfinished writes, rolling back\n",
|
||||
cwr->inode, cwr->stripe);
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, li);
|
||||
assert(wr->type() == BS_HEAP_INTENT_WRITE);
|
||||
auto prev_wr = prev(wr);
|
||||
while (prev_wr && prev_wr->entry_type == wr->entry_type)
|
||||
{
|
||||
// Skip other intent_writes
|
||||
prev_wr = prev(prev_wr);
|
||||
}
|
||||
if (!prev_wr || prev_wr->entry_type != (BS_HEAP_BIG_WRITE | (wr->entry_type & BS_HEAP_STABLE)) &&
|
||||
prev_wr->entry_type != (BS_HEAP_BIG_INTENT | (wr->entry_type & BS_HEAP_STABLE)))
|
||||
{
|
||||
fprintf(stderr, "Error: intent_write entry %jx:%jx v%ju l%ju is not written over a big_write\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn);
|
||||
exit(1);
|
||||
}
|
||||
loc = wr->small().offset + prev_wr->big_location(this);
|
||||
len = wr->small().len;
|
||||
from_data = true;
|
||||
}
|
||||
free_entry(li);
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
|
||||
st->sent_reads++;
|
||||
recheck_in_progress++;
|
||||
recheck_pending_reads--;
|
||||
bool is_last = st->sent_reads >= st->total_reads;
|
||||
recheck_cb(from_data, loc, len, buf, [this, st, wr, buf]()
|
||||
{
|
||||
st->checked_reads++;
|
||||
if (!calc_checksums(wr, buf, false))
|
||||
st->bad_wr = !st->bad_wr || st->bad_wr->lsn > wr->lsn ? wr : st->bad_wr;
|
||||
if (st->checked_reads >= st->total_reads)
|
||||
{
|
||||
if (st->bad_wr)
|
||||
recheck_drop_entries(st->obj, st->bad_wr);
|
||||
recheck_states.erase(st->obj);
|
||||
}
|
||||
free(buf);
|
||||
recheck_in_progress--;
|
||||
recheck_small_writes(NULL, 0);
|
||||
});
|
||||
if (is_last)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,70 +977,47 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
|
||||
recheck_queue_depth = queue_depth;
|
||||
}
|
||||
in_recheck = true;
|
||||
while (recheck_pending_reads > 0 && recheck_in_progress < recheck_queue_depth)
|
||||
{
|
||||
for (auto & sp: recheck_states)
|
||||
recheck_start_reads(&sp.second);
|
||||
}
|
||||
while (recheck_queue.size() > 0 && recheck_in_progress < recheck_queue_depth)
|
||||
{
|
||||
heap_entry_t *wr = recheck_queue.front();
|
||||
heap_entry_t *obj = recheck_queue.front();
|
||||
recheck_queue.pop_front();
|
||||
bool from_data = false;
|
||||
uint64_t loc = 0;
|
||||
uint32_t len = 0;
|
||||
if (wr->type() == BS_HEAP_INTENT_WRITE)
|
||||
if (obj->type() == BS_HEAP_SMALL_WRITE && buffer_area)
|
||||
{
|
||||
auto prev_wr = prev(wr);
|
||||
while (prev_wr && prev_wr->entry_type == wr->entry_type)
|
||||
// Check this object synchronously
|
||||
heap_entry_t *bad_wr = NULL;
|
||||
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
{
|
||||
// Skip other intent_writes
|
||||
prev_wr = prev(prev_wr);
|
||||
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in buffer area\n",
|
||||
wr->inode, wr->stripe, wr->lsn, wr->small().len, wr->small().location);
|
||||
if (!calc_checksums(wr, buffer_area + wr->small().location, false))
|
||||
bad_wr = wr;
|
||||
}
|
||||
if (!prev_wr || prev_wr->entry_type != (BS_HEAP_BIG_WRITE | (wr->entry_type & BS_HEAP_STABLE)) &&
|
||||
prev_wr->entry_type != (BS_HEAP_BIG_INTENT | (wr->entry_type & BS_HEAP_STABLE)))
|
||||
{
|
||||
fprintf(stderr, "Error: intent_write entry %jx:%jx v%ju l%ju is not written over a big_write\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn);
|
||||
exit(1);
|
||||
}
|
||||
loc = wr->small().offset + prev_wr->big_location(this);
|
||||
len = wr->small().len;
|
||||
from_data = true;
|
||||
}
|
||||
else if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
auto & bi = wr->big_intent();
|
||||
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
|
||||
len = bi.len;
|
||||
from_data = true;
|
||||
if (bad_wr)
|
||||
recheck_drop_entries(obj, bad_wr);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(wr->type() == BS_HEAP_SMALL_WRITE);
|
||||
loc = wr->small().location;
|
||||
len = wr->small().len;
|
||||
}
|
||||
if (log_level > 5)
|
||||
{
|
||||
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in %s area\n",
|
||||
wr->inode, wr->stripe, wr->lsn, len, loc, from_data ? "data" : "buffer");
|
||||
}
|
||||
if (!from_data && buffer_area)
|
||||
{
|
||||
recheck_buffer(wr, buffer_area+loc);
|
||||
}
|
||||
else
|
||||
{
|
||||
recheck_in_progress++;
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
|
||||
recheck_cb(from_data, loc, len, buf, [this, wr, buf]()
|
||||
{
|
||||
recheck_buffer(wr, buf);
|
||||
free(buf);
|
||||
recheck_in_progress--;
|
||||
recheck_small_writes(NULL, 0);
|
||||
});
|
||||
// Recheck will be asynchronous. Create state and start it
|
||||
auto & st = recheck_states[obj];
|
||||
st.obj = obj;
|
||||
st.next_wr = obj;
|
||||
st.total_reads = 1;
|
||||
if (obj->type() == BS_HEAP_SMALL_WRITE)
|
||||
for (auto wr = prev(obj); wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
st.total_reads++;
|
||||
recheck_pending_reads += st.total_reads;
|
||||
recheck_start_reads(&st);
|
||||
}
|
||||
}
|
||||
in_recheck = false;
|
||||
if (!recheck_queue.size() && !recheck_in_progress)
|
||||
{
|
||||
assert(!recheck_states.size());
|
||||
auto cb = std::move(recheck_cb);
|
||||
recheck_queue_depth = 0;
|
||||
if (cb)
|
||||
@@ -941,7 +1079,11 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
|
||||
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;
|
||||
@@ -991,11 +1133,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;
|
||||
@@ -1012,42 +1169,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;
|
||||
}
|
||||
|
||||
@@ -1335,43 +1539,51 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::insert_list_item(heap_list_item_t *li)
|
||||
void blockstore_heap_t::insert_list_items(heap_list_item_t** v, size_t count, bool postpone)
|
||||
{
|
||||
auto & inode_idx = block_index[get_pg_id(li->entry.inode, li->entry.stripe)][li->entry.inode];
|
||||
auto wr = &v[0]->entry;
|
||||
auto & inode_idx = block_index[get_pg_id(wr->inode, wr->stripe)][wr->inode];
|
||||
heap_inode_map_t::iterator li_it;
|
||||
heap_list_item_t *old_head = NULL;
|
||||
if (inode_idx)
|
||||
inode_map_get(inode_idx, li_it, old_head, li->entry.stripe);
|
||||
if (old_head && !old_head->entry.is_before(&li->entry))
|
||||
inode_map_get(inode_idx, li_it, old_head, wr->stripe);
|
||||
heap_list_item_t *next_li = NULL;
|
||||
heap_list_item_t *prev_li = old_head;
|
||||
int skips = 0;
|
||||
// Merge entry array and inode_idx linked list (both sorted in newest first order)
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
// BIG_WRITE may be inserted into the middle of the sequence during compaction
|
||||
// and it overrides SMALL_WRITEs and COMMITs with the same LSN
|
||||
// However, all entries of other types (say DELETE) override previous ones
|
||||
auto next_li = old_head;
|
||||
auto prev_li = old_head->prev;
|
||||
auto li = v[i];
|
||||
while (prev_li && !prev_li->entry.is_before(&li->entry))
|
||||
{
|
||||
next_li = prev_li;
|
||||
prev_li = prev_li->prev;
|
||||
skips++;
|
||||
}
|
||||
if (postpone && skips > POSTPONE_INSERT_COUNT)
|
||||
{
|
||||
postponed_items.push_back(li);
|
||||
return;
|
||||
}
|
||||
if (next_li == NULL)
|
||||
{
|
||||
// Replace the latest entry pointer
|
||||
if (old_head)
|
||||
inode_map_replace(inode_idx, li_it, li);
|
||||
else
|
||||
inode_map_put(inode_idx, li);
|
||||
}
|
||||
// Insert <li> between <next_li> and <prev_li>
|
||||
li->next = next_li;
|
||||
if (next_li)
|
||||
next_li->prev = li;
|
||||
li->prev = prev_li;
|
||||
if (prev_li)
|
||||
prev_li->next = li;
|
||||
next_li->prev = li;
|
||||
li->next = next_li;
|
||||
}
|
||||
else
|
||||
{
|
||||
li->prev = old_head;
|
||||
li->next = NULL;
|
||||
if (old_head)
|
||||
{
|
||||
old_head->next = li;
|
||||
inode_map_replace(inode_idx, li_it, li);
|
||||
}
|
||||
else
|
||||
inode_map_put(inode_idx, li);
|
||||
next_li = li;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1403,10 +1615,10 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
|
||||
(explicit_complete ? HEAP_INFLIGHT_EXPLICIT : 0) |
|
||||
(new_wr->is_overwrite() ? HEAP_INFLIGHT_COMPACTED : 0) |
|
||||
(new_wr->is_compactable() ? HEAP_INFLIGHT_COMPACTABLE : 0));
|
||||
insert_list_item(li);
|
||||
insert_list_items(&li, 1, false);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1466,7 +1678,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);
|
||||
@@ -1495,7 +1707,7 @@ 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);
|
||||
*obj_ptr = wr;
|
||||
@@ -1533,7 +1745,7 @@ 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, get_csum_size(wr));
|
||||
@@ -1580,7 +1792,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));
|
||||
});
|
||||
}
|
||||
@@ -1599,6 +1811,7 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
|
||||
*modified_block = block_num;
|
||||
memcpy(wr->get_int_bitmap(this), new_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
wr->checksum = wr->calc_checksum(dsk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2397,6 +2610,22 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
|
||||
}
|
||||
|
||||
void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
|
||||
{
|
||||
if (!li->next)
|
||||
{
|
||||
// The last freed entry must be a deletion
|
||||
assert(!li->prev);
|
||||
assert((li->entry.entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
|
||||
}
|
||||
else if (!li->prev && li->next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
|
||||
{
|
||||
// free BS_HEAP_DELETEs when all previous entries are also freed
|
||||
mark_garbage(li->next->block_num, &li->next->entry, UINT32_MAX);
|
||||
}
|
||||
unlink_list_item(li);
|
||||
}
|
||||
|
||||
void blockstore_heap_t::unlink_list_item(heap_list_item_t *li)
|
||||
{
|
||||
auto prev = li->prev;
|
||||
auto next = li->next;
|
||||
@@ -2406,25 +2635,20 @@ void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
// The last freed entry must be a deletion
|
||||
assert(!prev);
|
||||
auto wr = &li->entry;
|
||||
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;
|
||||
heap_list_item_t *old_li = NULL;
|
||||
inode_map_get(inode_idx, li_it, old_li, wr->stripe);
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
|
||||
if (!prev)
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
|
||||
else
|
||||
inode_map_replace(inode_idx, li_it, prev);
|
||||
}
|
||||
else
|
||||
{
|
||||
next->prev = prev;
|
||||
if (!prev && next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
|
||||
{
|
||||
// free BS_HEAP_DELETEs when all previous entries are also freed
|
||||
mark_garbage(next->block_num, &next->entry, UINT32_MAX);
|
||||
}
|
||||
}
|
||||
if (li->entry.is_garbage())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
@@ -57,11 +57,11 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
inline heap_small_write_t& small() { return *(heap_small_write_t*)this; }
|
||||
inline heap_big_write_t& big() { return *(heap_big_write_t*)this; }
|
||||
inline heap_big_intent_t& big_intent() { return *(heap_big_intent_t*)this; }
|
||||
bool is_garbage();
|
||||
bool is_garbage() const;
|
||||
void set_garbage();
|
||||
bool is_overwrite();
|
||||
bool is_compactable();
|
||||
bool is_before(heap_entry_t *other);
|
||||
bool is_overwrite() const;
|
||||
bool is_compactable() const;
|
||||
bool is_before(const heap_entry_t *other) const;
|
||||
uint32_t get_size(blockstore_heap_t *heap);
|
||||
uint8_t *get_ext_bitmap(blockstore_heap_t *heap);
|
||||
uint8_t *get_int_bitmap(blockstore_heap_t *heap);
|
||||
@@ -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
|
||||
@@ -158,6 +159,16 @@ struct heap_li_equal
|
||||
}
|
||||
};
|
||||
|
||||
struct heap_recheck_state_t
|
||||
{
|
||||
heap_entry_t *obj = NULL;
|
||||
heap_entry_t *next_wr = NULL;
|
||||
size_t total_reads = 0;
|
||||
size_t sent_reads = 0;
|
||||
size_t checked_reads = 0;
|
||||
heap_entry_t *bad_wr = NULL;
|
||||
};
|
||||
|
||||
using i64hash_t = robin_hood::hash<uint64_t>;
|
||||
using heap_inode_map_t = robin_hood::unordered_flat_set<heap_list_item_t*, heap_li_hash, heap_li_equal, 88>;
|
||||
using heap_block_index_t = robin_hood::unordered_flat_map<uint64_t,
|
||||
@@ -208,9 +219,11 @@ class blockstore_heap_t
|
||||
|
||||
bool marked_used_blocks = false;
|
||||
bool recheck_queue_filled = false;
|
||||
std::vector<heap_list_item_t*> loaded_list_items;
|
||||
std::vector<heap_list_item_t*> postponed_items;
|
||||
std::set<uint32_t> recheck_modified_blocks;
|
||||
std::deque<heap_entry_t*> recheck_queue;
|
||||
std::map<heap_entry_t*, heap_recheck_state_t> recheck_states;
|
||||
size_t recheck_pending_reads = 0;
|
||||
int recheck_in_progress = 0;
|
||||
bool in_recheck = false;
|
||||
std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> recheck_cb;
|
||||
@@ -219,7 +232,12 @@ class blockstore_heap_t
|
||||
uint64_t get_pg_id(inode_t inode, uint64_t stripe);
|
||||
bool validate_object(heap_entry_t *obj);
|
||||
void fill_recheck_queue();
|
||||
void recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr);
|
||||
void recheck_start_reads(heap_recheck_state_t *st);
|
||||
int mark_used_blocks();
|
||||
void init_free_bad_entry(heap_entry_t *wr);
|
||||
void init_erase_bad_entry(heap_list_item_t *li);
|
||||
bool init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li);
|
||||
void recheck_full_gc();
|
||||
void recheck_buffer(heap_entry_t *cwr, uint8_t *buf);
|
||||
void defragment_block(uint32_t block_num);
|
||||
@@ -227,8 +245,9 @@ class blockstore_heap_t
|
||||
|
||||
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);
|
||||
void insert_list_items(heap_list_item_t** v, size_t count, bool postpone);
|
||||
void remove_list_item(heap_list_item_t *li);
|
||||
void unlink_list_item(heap_list_item_t *li);
|
||||
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
|
||||
bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry);
|
||||
int add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type);
|
||||
|
||||
@@ -101,6 +101,7 @@ void blockstore_impl_t::loop()
|
||||
unsigned initial_ring_space = ringloop->space_left();
|
||||
int op_idx = 0, new_idx = 0;
|
||||
bool has_unfinished_writes = false;
|
||||
bool has_unfinished_sync = false;
|
||||
for (; op_idx < submit_queue.size(); op_idx++, new_idx++)
|
||||
{
|
||||
auto op = submit_queue[op_idx];
|
||||
@@ -138,7 +139,13 @@ void blockstore_impl_t::loop()
|
||||
else if (op->opcode == BS_OP_SYNC)
|
||||
{
|
||||
// syncs only completed writes, so doesn't have to be blocked by anything
|
||||
wr_st = continue_sync(op);
|
||||
if (!has_unfinished_sync)
|
||||
{
|
||||
wr_st = continue_sync(op);
|
||||
has_unfinished_sync = (wr_st != 2);
|
||||
}
|
||||
else
|
||||
wr_st = 0;
|
||||
}
|
||||
else if (op->opcode == BS_OP_STABLE || op->opcode == BS_OP_ROLLBACK)
|
||||
{
|
||||
@@ -154,9 +161,7 @@ void blockstore_impl_t::loop()
|
||||
wr_st = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
wr_st = 0;
|
||||
}
|
||||
}
|
||||
if (wr_st == 2)
|
||||
{
|
||||
|
||||
@@ -153,6 +153,14 @@ resume_1:
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
uint32_t csum = hdr->header_csum;
|
||||
hdr->header_csum = 0;
|
||||
if (crc32c(0, hdr, sizeof(*hdr)) != csum)
|
||||
{
|
||||
printf("Metadata header is corrupt (checksum mismatch).\n");
|
||||
exit(1);
|
||||
}
|
||||
hdr->header_csum = csum;
|
||||
}
|
||||
bs->heap->start_load(((blockstore_meta_header_v3_t *)bs->meta_superblock)->completed_lsn);
|
||||
if (bs->dsk.inmemory_journal)
|
||||
@@ -241,22 +249,6 @@ resume_4:
|
||||
// metadata read finished
|
||||
bs->heap->finish_load();
|
||||
printf("Metadata entries loaded: %ju, rechecking unfinished writes and garbage entries\n", entries_loaded);
|
||||
if (zero_on_init && !bs->dsk.disable_meta_fsync)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// asynchronous recheck
|
||||
resume_6:
|
||||
wait_state = 6;
|
||||
@@ -311,7 +303,7 @@ resume_8:
|
||||
uint32_t block_num = recheck_mod[i];
|
||||
uint64_t block_offset = bs->dsk.meta_offset + (uint64_t)(block_num+1) * bs->dsk.meta_block_size;
|
||||
data = ((ring_data_t*)sqe->user_data);
|
||||
uint8_t *buf = (uint8_t*)malloc_or_die(bs->dsk.meta_block_size);
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, bs->dsk.meta_block_size);
|
||||
bs->heap->get_meta_block(block_num, buf);
|
||||
data->iov = { buf, bs->dsk.meta_block_size };
|
||||
data->callback = [this, buf, block_offset](ring_data_t *data)
|
||||
@@ -337,6 +329,22 @@ resume_9:
|
||||
}
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
if (!bs->dsk.disable_meta_fsync && !bs->readonly)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("Loading finished. Data used: %ju / %ju bytes (%s / %s)\n",
|
||||
bs->heap->get_data_used_space(), bs->dsk.block_count * bs->dsk.data_block_size,
|
||||
format_size(bs->heap->get_data_used_space()).c_str(),
|
||||
|
||||
@@ -16,6 +16,7 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
else if (priv->op_state == 5) goto resume_5;
|
||||
assert(!priv->op_state);
|
||||
op->retval = 0;
|
||||
PRIV(op)->lsn = 0;
|
||||
priv->modified_block = priv->modified_block2 = UINT32_MAX;
|
||||
for (priv->stab_pos = 0; priv->stab_pos < op->len; priv->stab_pos++)
|
||||
{
|
||||
@@ -36,6 +37,12 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (res == ENOENT)
|
||||
{
|
||||
op->retval = -ENOENT;
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (res == ENOSPC)
|
||||
{
|
||||
if (!heap->get_to_compact_count())
|
||||
|
||||
@@ -9,6 +9,7 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
|
||||
if (!PRIV(op)->op_state)
|
||||
{
|
||||
op->retval = 0;
|
||||
PRIV(op)->lsn = 0;
|
||||
}
|
||||
int res = do_sync(op, 0);
|
||||
if (res == 2)
|
||||
@@ -104,7 +105,8 @@ int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
|
||||
unsynced_data_write_count = unsynced_buffer_write_count = unsynced_meta_write_count = 0;
|
||||
return 2;
|
||||
}
|
||||
PRIV(op)->modified_block = heap->get_completed_lsn();
|
||||
assert(!PRIV(op)->lsn);
|
||||
PRIV(op)->lsn = heap->get_completed_lsn();
|
||||
if (!submit_fsyncs(PRIV(op)->pending_ops))
|
||||
{
|
||||
PRIV(op)->wait_detail = 1;
|
||||
@@ -118,6 +120,6 @@ resume_1:
|
||||
return 1;
|
||||
}
|
||||
resume_2:
|
||||
heap->mark_lsn_fsynced(PRIV(op)->modified_block);
|
||||
heap->mark_lsn_fsynced(PRIV(op)->lsn);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
|
||||
heap->complete_block_write(modified_block);
|
||||
ringloop->wakeup();
|
||||
};
|
||||
assert(((uint64_t)modified_block+2)*dsk.meta_block_size <= dsk.meta_area_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
|
||||
);
|
||||
@@ -177,6 +178,7 @@ enospc:
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->offset+op->len <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
|
||||
PRIV(op)->pending_ops++;
|
||||
write_iodepth++;
|
||||
@@ -264,6 +266,7 @@ enospc:
|
||||
BS_SUBMIT_GET_SQE(sqe2, data2);
|
||||
data2->iov = (struct iovec){ op->buf, op->len };
|
||||
data2->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->len <= dsk.journal_len);
|
||||
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, dsk.journal_offset + loc);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
@@ -453,6 +456,7 @@ resume_10:
|
||||
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); };
|
||||
assert(PRIV(op)->location + op->offset <= dsk.block_count*dsk.data_block_size);
|
||||
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;
|
||||
|
||||
@@ -141,7 +141,7 @@ struct __attribute__((__packed__)) journal_entry
|
||||
inline uint32_t je_crc32(journal_entry *je)
|
||||
{
|
||||
// 0x48674bc7 = crc32(4 zero bytes)
|
||||
return crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
return je->size < 4 ? 0 : crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
}
|
||||
|
||||
// "VITAstor"
|
||||
|
||||
@@ -520,6 +520,7 @@ resume_2:
|
||||
await_sqe(15);
|
||||
data->iov = (struct iovec){ it->buf, (size_t)it->len };
|
||||
data->callback = simple_callback_w;
|
||||
assert(clean_loc+it->offset+it->len <= bs->dsk.block_count*bs->dsk.data_block_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + it->offset
|
||||
);
|
||||
@@ -749,6 +750,7 @@ bool journal_flusher_co::write_meta_block(flusher_meta_write_t & meta_block, int
|
||||
await_sqe(0);
|
||||
data->iov = (struct iovec){ meta_block.buf, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = simple_callback_w;
|
||||
assert(bs->dsk.meta_block_size + meta_block.sector + bs->dsk.meta_block_size <= bs->dsk.meta_area_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bs->dsk.meta_block_size + meta_block.sector
|
||||
);
|
||||
|
||||
@@ -193,6 +193,7 @@ void blockstore_impl_t::prepare_journal_sector_write(int cur_sector, blockstore_
|
||||
(size_t)journal.block_size
|
||||
};
|
||||
data->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
|
||||
assert(journal.sector_info[cur_sector].offset+journal.block_size <= dsk.journal_len);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.journal_fd, &data->iov, 1, journal.offset + journal.sector_info[cur_sector].offset
|
||||
);
|
||||
|
||||
@@ -368,9 +368,9 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
}
|
||||
data->iov.iov_len = op->len + stripe_offset + stripe_end; // to check it in the callback
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + (loc * dsk.data_block_size) + op->offset - stripe_offset
|
||||
);
|
||||
const uint64_t write_offset = (loc * dsk.data_block_size) + op->offset - stripe_offset;
|
||||
assert(write_offset+op->len+stripe_offset+stripe_end <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + write_offset);
|
||||
PRIV(op)->pending_ops = 1;
|
||||
if (!(dirty_it->second.state & BS_ST_INSTANT))
|
||||
{
|
||||
@@ -495,9 +495,8 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
.op = op,
|
||||
});
|
||||
data2->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
|
||||
io_uring_prep_writev(
|
||||
sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free
|
||||
);
|
||||
assert(journal.next_free+op->len <= dsk.journal_len);
|
||||
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -12,11 +12,11 @@ if (RDMACM_LIBRARIES)
|
||||
set(MSGR_RDMACM "msgr_rdmacm.cpp")
|
||||
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
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp msgr_iothread.cpp ../util/addr_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp
|
||||
msgr_encrypt.cpp msgr_handshake.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} ${ISAL_CRYPTO_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,8 @@ target_link_libraries(vitastor_client
|
||||
${LIBURING_LIBRARIES}
|
||||
${IBVERBS_LIBRARIES}
|
||||
${RDMACM_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
${ISAL_CRYPTO_LIBRARIES}
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
|
||||
configure_file(vitastor.pc.in vitastor.pc @ONLY)
|
||||
@@ -52,9 +55,6 @@ if (${WITH_FIO})
|
||||
../util/rw_blocking.cpp
|
||||
../util/addr_util.cpp
|
||||
)
|
||||
target_link_libraries(fio_vitastor_sec
|
||||
tcmalloc_minimal
|
||||
)
|
||||
endif (${WITH_FIO})
|
||||
|
||||
# vitastor-nbd
|
||||
@@ -98,9 +98,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
|
||||
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.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 ../util/xxh_x86dispatch.c ../util/openssl_util.cpp ../../json11/json11.cpp
|
||||
)
|
||||
target_link_libraries(test_cluster_client ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_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)
|
||||
|
||||
+125
-79
@@ -51,7 +51,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
msgr.stop_client(op->client_id);
|
||||
delete op;
|
||||
};
|
||||
msgr.parse_config(config);
|
||||
msgr.parse_config(config, true);
|
||||
|
||||
st_cli.tfd = tfd;
|
||||
st_cli.on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
@@ -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;
|
||||
@@ -70,13 +71,11 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli.infinite_start = config["client_infinite_start"].bool_value();
|
||||
}
|
||||
st_cli.load_global_config();
|
||||
|
||||
scrap_buffer_size = SCRAP_BUFFER_SIZE;
|
||||
scrap_buffer = malloc_or_die(scrap_buffer_size);
|
||||
}
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
vault_destroy();
|
||||
if (retry_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(retry_timeout_id);
|
||||
@@ -94,7 +93,6 @@ cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
ringloop->unregister_consumer(&consumer);
|
||||
}
|
||||
free(scrap_buffer);
|
||||
delete wb;
|
||||
wb = NULL;
|
||||
}
|
||||
@@ -481,7 +479,9 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
self_tree_metrics.clear();
|
||||
client_hostname = new_hostname;
|
||||
}
|
||||
msgr.parse_config(config);
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config, false);
|
||||
st_cli.parse_config(config);
|
||||
st_cli.load_pgs();
|
||||
}
|
||||
@@ -590,7 +590,7 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
{
|
||||
if (log_level > 2 && pg_counts[pool_item.first])
|
||||
{
|
||||
printf("Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
|
||||
fprintf(stderr, "Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
|
||||
pg_counts[pool_item.first], pool_item.second.real_pg_count);
|
||||
}
|
||||
// At this point, all pool operations should have been suspended
|
||||
@@ -607,6 +607,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 +676,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 +702,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 +965,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 +1009,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;
|
||||
}
|
||||
|
||||
@@ -1119,33 +1162,42 @@ resume_2:
|
||||
// Finished successfully
|
||||
// Even if the PG count has changed in meanwhile we treat it as success
|
||||
// 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_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Copy part bitmaps only after finishing all part reads
|
||||
for (auto & part: op->parts)
|
||||
if ((part.flags & (PART_SENT|PART_DONE|PART_VALID)) == (PART_SENT|PART_DONE|PART_VALID))
|
||||
copy_part_bitmap(op, &part);
|
||||
}
|
||||
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;
|
||||
@@ -1164,7 +1216,7 @@ resume_2:
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
else if (op->retval != 0 && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
else if (op->retval != 0 && op->opcode != OSD_OP_SYNC && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
op->retval != -EPIPE && (op->retval != -EIO || !client_eio_retry_interval) && (op->retval != -ENOSPC || !client_retry_enospc))
|
||||
{
|
||||
// Fatal error (neither -EPIPE, -EIO nor -ENOSPC)
|
||||
@@ -1196,7 +1248,7 @@ resume_2:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov, void *scrap, int scrap_len)
|
||||
static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov)
|
||||
{
|
||||
int left = size;
|
||||
while (left > 0 && iov_idx < op->iov.count)
|
||||
@@ -1204,7 +1256,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
int cur_left = op->iov.buf[iov_idx].iov_len - iov_pos;
|
||||
if (cur_left < left)
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, cur_left);
|
||||
}
|
||||
@@ -1214,7 +1266,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, left);
|
||||
}
|
||||
@@ -1223,16 +1275,10 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
}
|
||||
assert(left == 0);
|
||||
if (skip && scrap_len > 0)
|
||||
if (skip == 1)
|
||||
{
|
||||
// All skipped ranges are read into the same useless buffer
|
||||
left = size;
|
||||
while (left > 0)
|
||||
{
|
||||
int cur_left = scrap_len < left ? scrap_len : left;
|
||||
iov.push_back(scrap, cur_left);
|
||||
left -= cur_left;
|
||||
}
|
||||
// data read into a NULL buffer will be discarded by messenger
|
||||
iov.push_back(NULL, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1252,7 +1298,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 - max 4 bytes per block
|
||||
+ (op->enc ? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*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);
|
||||
@@ -1294,10 +1344,10 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
begin = cur;
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(cur-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(cur-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
skip_prev = skip;
|
||||
prev = cur;
|
||||
@@ -1308,11 +1358,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
if (skip_prev)
|
||||
{
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(end-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
end = prev;
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
if (end == begin)
|
||||
{
|
||||
op->done_count++;
|
||||
@@ -1321,7 +1371,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
}
|
||||
else if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_DELETE)
|
||||
{
|
||||
add_iov(end-begin, false, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-begin, 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
op->parts[i].parent = op;
|
||||
op->parts[i].offset = begin;
|
||||
@@ -1407,9 +1457,12 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
osd_client_t *cl = 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
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->opcode == OSD_OP_READ && op->enc
|
||||
? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*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);
|
||||
@@ -1552,6 +1607,9 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
|
||||
}
|
||||
}
|
||||
|
||||
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
|
||||
|
||||
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
cluster_op_t *op = part->parent;
|
||||
@@ -1560,15 +1618,10 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
// Operation failed, retry
|
||||
part->flags |= PART_ERROR;
|
||||
if (!op->retval || op->retval == -EPIPE ||
|
||||
part->op.reply.hdr.retval == -ENOSPC && op->retval == -ETIMEDOUT ||
|
||||
part->op.reply.hdr.retval == -EIO)
|
||||
{
|
||||
// Error priority: EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
|
||||
op->retval = part->op.reply.hdr.retval;
|
||||
}
|
||||
uint64_t stop_client_id = 0;
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
|
||||
{
|
||||
stop_client_id = part->op.client_id;
|
||||
if (op->retval != -EPIPE || log_level > 0)
|
||||
@@ -1630,13 +1683,6 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
}
|
||||
if (op->inflight_count == 0 && !op->retry_after)
|
||||
{
|
||||
// Copy part bitmaps only after finishing all part reads
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
for (auto & part: op->parts)
|
||||
if ((part.flags & (PART_SENT|PART_VALID|PART_DONE)) == (PART_SENT|PART_VALID|PART_DONE))
|
||||
copy_part_bitmap(op, &part);
|
||||
}
|
||||
if (op->opcode == OSD_OP_SYNC)
|
||||
continue_sync(op);
|
||||
else
|
||||
|
||||
+59
-13
@@ -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,17 +82,33 @@ struct inode_list_osd_t;
|
||||
struct inode_list_pg_t;
|
||||
class writeback_cache_t;
|
||||
|
||||
struct inode_cache_t
|
||||
{
|
||||
std::vector<inode_t> chain; // only parents from the same pool
|
||||
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
|
||||
{
|
||||
#ifdef __MOCK__
|
||||
public:
|
||||
#endif
|
||||
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 +120,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,15 +144,28 @@ 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;
|
||||
uint64_t dirty_bytes = 0, dirty_ops = 0;
|
||||
|
||||
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;
|
||||
@@ -155,15 +197,16 @@ public:
|
||||
void list_inode(inode_t inode, uint64_t min_offset, uint64_t max_offset, int max_parallel_pgs, std::function<void(
|
||||
int status, int pgs_left, pg_num_t pg_num, std::set<object_id>&& objects)> pg_callback);
|
||||
|
||||
//inline uint32_t get_bs_bitmap_granularity() { return st_cli.global_bitmap_granularity; }
|
||||
//inline uint64_t get_bs_block_size() { return st_cli.global_block_size; }
|
||||
|
||||
#ifndef __MOCK__
|
||||
protected:
|
||||
#endif
|
||||
void continue_ops(int time_passed = 0);
|
||||
|
||||
protected:
|
||||
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);
|
||||
|
||||
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 +216,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 +233,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);
|
||||
@@ -204,4 +249,5 @@ protected:
|
||||
osd_num_t select_nearest_osd(const std::vector<osd_num_t> & osds);
|
||||
|
||||
friend class writeback_cache_t;
|
||||
friend class cluster_client_test_t;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "cluster_client.h"
|
||||
|
||||
#define SCRAP_BUFFER_SIZE 4*1024*1024
|
||||
#define PART_SENT 1
|
||||
#define PART_DONE 2
|
||||
#define PART_ERROR 4
|
||||
|
||||
@@ -88,6 +88,11 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
// ...or just save it for writeback if write buffering is enabled
|
||||
if (op->len == 0)
|
||||
{
|
||||
// FIXME: OSD_OP_DELETEs are currently only sent by vitastor-cli rm/rm-data and
|
||||
// actually have len=0, because delete is actually a delete of the full object
|
||||
// containing the requested offset, not a "punch hole" operation. But here, writeback
|
||||
// cache assumes it IS a "punch hole" operation. I should select one of these
|
||||
// approaches and fix everything accordingly when I decide to implement TRIM.
|
||||
return;
|
||||
}
|
||||
auto dirty_it = find_dirty(op->inode, op->offset);
|
||||
@@ -244,12 +249,13 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
writeback_queue_size--;
|
||||
}
|
||||
}
|
||||
if (!is_del)
|
||||
if (!is_del && op->len > 0)
|
||||
{
|
||||
uint64_t pos = 0, len = op->len, iov_idx = 0;
|
||||
while (len > 0 && iov_idx < op->iov.count)
|
||||
while (iov_idx < op->iov.count)
|
||||
{
|
||||
auto & iov = op->iov.buf[iov_idx];
|
||||
assert(pos + iov.iov_len <= len);
|
||||
memcpy(buf + pos, iov.iov_base, iov.iov_len);
|
||||
pos += iov.iov_len;
|
||||
iov_idx++;
|
||||
@@ -443,7 +449,7 @@ void writeback_cache_t::start_writebacks(cluster_client_t *cli, int count)
|
||||
started++;
|
||||
assert(writeback_queue_size > 0);
|
||||
writeback_queue_size--;
|
||||
writeback_bytes -= off - from_it->first.stripe;
|
||||
writeback_bytes -= (is_del ? 0 : off - from_it->first.stripe);
|
||||
assert(writeback_queue_size > 0 || !writeback_bytes);
|
||||
flush_buffers(cli, from_it, to_it);
|
||||
}
|
||||
|
||||
+369
-180
@@ -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__
|
||||
@@ -9,6 +12,7 @@
|
||||
#include "http_client.h"
|
||||
#endif
|
||||
#include "str_util.h"
|
||||
#include "json_util.h"
|
||||
|
||||
etcd_state_client_t::~etcd_state_client_t()
|
||||
{
|
||||
@@ -22,14 +26,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 +81,91 @@ 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,
|
||||
std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & username)
|
||||
{
|
||||
auto user_it = user_info.find(username);
|
||||
if (user_it != user_info.end())
|
||||
{
|
||||
return user_it->second;
|
||||
}
|
||||
auto inf = std::make_shared<user_info_t>();
|
||||
inf->name = username;
|
||||
return inf;
|
||||
}
|
||||
|
||||
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
|
||||
{
|
||||
if (user_info->type == user_type_t::ADMIN)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto cache_it = user_info->perm_cache.find(inode_num);
|
||||
if (cache_it != user_info->perm_cache.end() &&
|
||||
cache_it->second.mod_revision == user_perm_cache_revision)
|
||||
{
|
||||
return write ? (cache_it->second.perm == user_perm_t::OWNER) : (cache_it->second.perm != user_perm_t::DENY);
|
||||
}
|
||||
auto inode_it = inode_config.find(inode_num);
|
||||
if (inode_it == inode_config.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// FIXME Implement cache reset after reworking etcd interaction to not keep everything in memory
|
||||
auto & perm_item = user_info->perm_cache[inode_num];
|
||||
perm_item.mod_revision = user_perm_cache_revision;
|
||||
perm_item.perm = (user_info->name == inode_it->second.owner || inode_it->second.owner_group != "" &&
|
||||
user_info->groups.find(inode_it->second.owner_group) != user_info->groups.end()
|
||||
? user_perm_t::OWNER : (inode_it->second.reader_group != "" &&
|
||||
user_info->groups.find(inode_it->second.reader_group) != user_info->groups.end()
|
||||
? user_perm_t::READER : user_perm_t::DENY));
|
||||
return write ? (perm_item.perm == user_perm_t::OWNER) : (perm_item.perm != user_perm_t::DENY);
|
||||
}
|
||||
|
||||
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 +173,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 +209,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 +268,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 +291,22 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
add_etcd_url(ea.string_value());
|
||||
}
|
||||
}
|
||||
if (this->etcd_client_cert != "")
|
||||
{
|
||||
this->etcd_client_cert = config["etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["etcd_client_key"].string_value();
|
||||
}
|
||||
else if (this->osd_num)
|
||||
{
|
||||
this->etcd_client_cert = config["osd_cert"].string_value();
|
||||
this->etcd_client_key = config["osd_pkey"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->etcd_client_cert = config["cert"].string_value();
|
||||
this->etcd_client_key = config["pkey"].string_value();
|
||||
}
|
||||
this->etcd_ca = config["etcd_ca"].string_value();
|
||||
this->etcd_prefix = config["etcd_prefix"].string_value();
|
||||
if (this->etcd_prefix == "")
|
||||
{
|
||||
@@ -291,66 +359,130 @@ 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);
|
||||
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())
|
||||
{
|
||||
@@ -393,7 +525,6 @@ 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;
|
||||
on_reload_hook();
|
||||
}
|
||||
@@ -438,7 +569,7 @@ 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();
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
@@ -468,13 +599,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>
|
||||
@@ -549,12 +675,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();
|
||||
}
|
||||
@@ -654,12 +775,8 @@ void etcd_state_client_t::load_pgs()
|
||||
json11::Json::array txn = {
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pools") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pgs") },
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
@@ -667,12 +784,6 @@ void etcd_state_client_t::load_pgs()
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/config") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/inode/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config/inode0") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/history/") },
|
||||
@@ -942,6 +1053,8 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
pc.used_for_app = "fs:"+pc.used_for_app;
|
||||
else
|
||||
pc.used_for_app = pool_item.second["used_for_app"].as_string();
|
||||
// Create group permission
|
||||
pc.creator_group = pool_item.second["creator_group"].string_value();
|
||||
// Local Read Configuration
|
||||
std::string local_reads = pool_item.second["local_reads"].string_value();
|
||||
if (local_reads == "nearest")
|
||||
@@ -1185,7 +1298,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (i >= pg_state_bit_count)
|
||||
{
|
||||
fprintf(stderr, "Unexpected pool %u PG %u state keyword in etcd: %s\n", pool_id, pg_num, e.dump().c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!cur_primary || !value["state"].is_array() || !state ||
|
||||
@@ -1194,7 +1306,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
(state & PG_INCOMPLETE) && state != PG_INCOMPLETE && state != (PG_INCOMPLETE|PG_HAS_INVALID))
|
||||
{
|
||||
fprintf(stderr, "Unexpected pool %u PG %u state in etcd: primary=%ju, state=%s\n", pool_id, pg_num, cur_primary, value["state"].dump().c_str());
|
||||
return;
|
||||
}
|
||||
pg_cfg.cur_primary = cur_primary;
|
||||
pg_cfg.cur_state = state;
|
||||
@@ -1261,37 +1372,15 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
{
|
||||
on_inode_change_hook(inode_num, true);
|
||||
}
|
||||
if (this->inode_config.find(inode_num) != this->inode_config.end())
|
||||
{
|
||||
user_perm_cache_revision = kv.mod_revision;
|
||||
}
|
||||
this->inode_config.erase(inode_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
|
||||
{
|
||||
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);
|
||||
}
|
||||
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,
|
||||
});
|
||||
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1302,6 +1391,38 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (on_change_node_placement_hook)
|
||||
on_change_node_placement_hook();
|
||||
}
|
||||
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
|
||||
{
|
||||
// <etcd_prefix>/config/user/<username>
|
||||
auto name = key.substr(etcd_prefix.length()+13);
|
||||
auto & inf = user_info[name];
|
||||
if (!value.is_object())
|
||||
{
|
||||
if (inf)
|
||||
{
|
||||
inf->type = user_type_t::CLIENT;
|
||||
inf->groups.clear();
|
||||
inf->perm_cache.clear();
|
||||
}
|
||||
user_info.erase(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inf)
|
||||
{
|
||||
inf = std::make_shared<user_info_t>();
|
||||
inf->name = name;
|
||||
}
|
||||
inf->type = value["type"] == "admin" ? user_type_t::ADMIN : user_type_t::CLIENT;
|
||||
inf->groups.clear();
|
||||
for (auto & group: value["groups"].array_items())
|
||||
{
|
||||
if (group.string_value() != "")
|
||||
inf->groups.insert(group.string_value());
|
||||
}
|
||||
inf->perm_cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
|
||||
@@ -1324,7 +1445,12 @@ uint32_t etcd_state_client_t::parse_scheme(const std::string & scheme)
|
||||
|
||||
void etcd_state_client_t::insert_inode_config(const inode_config_t & cfg)
|
||||
{
|
||||
this->inode_config[cfg.num] = cfg;
|
||||
auto & cfg_ref = this->inode_config[cfg.num];
|
||||
if (cfg_ref.mod_revision != cfg.mod_revision)
|
||||
{
|
||||
user_perm_cache_revision = cfg.mod_revision;
|
||||
}
|
||||
cfg_ref = cfg;
|
||||
if (cfg.name != "")
|
||||
{
|
||||
this->inode_by_name[cfg.name] = cfg.num;
|
||||
@@ -1380,6 +1506,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;
|
||||
@@ -1388,6 +1518,18 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
{
|
||||
new_cfg["deleted"] = true;
|
||||
}
|
||||
if (!cfg->owner.empty())
|
||||
{
|
||||
new_cfg["owner"] = cfg->owner;
|
||||
}
|
||||
if (!cfg->owner_group.empty())
|
||||
{
|
||||
new_cfg["owner_group"] = cfg->owner_group;
|
||||
}
|
||||
if (!cfg->reader_group.empty())
|
||||
{
|
||||
new_cfg["reader_group"] = cfg->reader_group;
|
||||
}
|
||||
if (cfg->meta.is_object())
|
||||
{
|
||||
new_cfg["meta"] = cfg->meta;
|
||||
@@ -1395,6 +1537,53 @@ 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),
|
||||
.owner = value["owner"].string_value(),
|
||||
.owner_group = value["owner_group"].string_value(),
|
||||
.reader_group = value["reader_group"].string_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = mod_revision,
|
||||
};
|
||||
}
|
||||
|
||||
int etcd_state_client_t::address_count()
|
||||
{
|
||||
return etcd_addresses.size() + etcd_local.size();
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "json11/json11.hpp"
|
||||
#include "object_id.h"
|
||||
#include "timerfd_manager.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define ETCD_CONFIG_WATCH_ID 1
|
||||
#define ETCD_OSD_STATE_WATCH_ID 2
|
||||
@@ -19,6 +21,8 @@
|
||||
#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
|
||||
@@ -66,6 +70,7 @@ struct pool_config_t
|
||||
std::map<pg_num_t, pg_config_t> pg_config;
|
||||
uint64_t scrub_interval = 0;
|
||||
std::string used_for_app;
|
||||
std::string creator_group;
|
||||
int backfillfull = 0;
|
||||
int local_reads = 0;
|
||||
|
||||
@@ -83,6 +88,9 @@ struct inode_config_t
|
||||
inode_t parent_id = 0;
|
||||
bool readonly = false;
|
||||
bool deleted = false;
|
||||
std::string enc_key;
|
||||
// Permissions
|
||||
std::string owner, owner_group, reader_group;
|
||||
// Arbitrary metadata
|
||||
json11::Json meta;
|
||||
// Change revision of the metadata in etcd
|
||||
@@ -95,23 +103,65 @@ struct inode_watch_t
|
||||
inode_config_t cfg = {};
|
||||
};
|
||||
|
||||
struct http_url_t
|
||||
{
|
||||
bool ssl;
|
||||
std::string addr;
|
||||
std::string hostname;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
enum class user_type_t
|
||||
{
|
||||
CLIENT = 0,
|
||||
ADMIN = 1,
|
||||
};
|
||||
|
||||
struct user_perm_t
|
||||
{
|
||||
enum class perm_type_t: uint8_t;
|
||||
constexpr static perm_type_t DENY = (perm_type_t)0;
|
||||
constexpr static perm_type_t READER = (perm_type_t)1;
|
||||
constexpr static perm_type_t OWNER = (perm_type_t)2;
|
||||
uint64_t mod_revision = 0;
|
||||
perm_type_t perm = DENY;
|
||||
};
|
||||
|
||||
struct user_info_t
|
||||
{
|
||||
std::string name;
|
||||
user_type_t type;
|
||||
robin_hood::unordered_flat_set<std::string> groups;
|
||||
robin_hood::unordered_flat_map<inode_t, user_perm_t> perm_cache;
|
||||
};
|
||||
|
||||
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;
|
||||
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,10 +174,15 @@ 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;
|
||||
uint64_t etcd_watch_revision_config = 0;
|
||||
@@ -140,6 +195,8 @@ public:
|
||||
std::set<osd_num_t> seen_peers;
|
||||
std::map<inode_t, inode_config_t> inode_config;
|
||||
std::map<std::string, inode_t> inode_by_name;
|
||||
robin_hood::unordered_flat_map<std::string, std::shared_ptr<user_info_t>> user_info;
|
||||
uint64_t user_perm_cache_revision = 0;
|
||||
json11::Json node_placement;
|
||||
|
||||
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
||||
@@ -158,10 +215,14 @@ 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);
|
||||
std::shared_ptr<user_info_t> get_user(const std::string & username);
|
||||
bool check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write);
|
||||
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();
|
||||
|
||||
+629
-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,26 @@ 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);
|
||||
std::string http_context_get_ssl_cn(http_context_t *ctx);
|
||||
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
|
||||
|
||||
+115
-139
@@ -10,113 +10,15 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "addr_util.h"
|
||||
#include "str_util.h"
|
||||
#include "messenger.h"
|
||||
#ifdef WITH_RDMA
|
||||
#include "msgr_rdma.h"
|
||||
#endif
|
||||
|
||||
#include <sys/poll.h>
|
||||
|
||||
msgr_iothread_t::msgr_iothread_t():
|
||||
ring(RINGLOOP_DEFAULT_SIZE, true),
|
||||
thread(&msgr_iothread_t::run, this)
|
||||
{
|
||||
eventfd = ring.register_eventfd();
|
||||
if (eventfd < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("failed to register eventfd: ") + strerror(-eventfd));
|
||||
}
|
||||
}
|
||||
|
||||
msgr_iothread_t::~msgr_iothread_t()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_sqe(io_uring_sqe & sqe)
|
||||
{
|
||||
mu.lock();
|
||||
queue.push_back((iothread_sqe_t){ .sqe = sqe, .data = std::move(*(ring_data_t*)sqe.user_data) });
|
||||
if (queue.size() == 1)
|
||||
{
|
||||
cond.notify_all();
|
||||
}
|
||||
mu.unlock();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::stop()
|
||||
{
|
||||
mu.lock();
|
||||
if (stopped)
|
||||
{
|
||||
mu.unlock();
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
if (outer_loop_data)
|
||||
{
|
||||
outer_loop_data->callback = [](ring_data_t*){};
|
||||
}
|
||||
cond.notify_all();
|
||||
close(eventfd);
|
||||
mu.unlock();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_to_ringloop(ring_loop_t *outer_loop)
|
||||
{
|
||||
assert(!this->outer_loop || this->outer_loop == outer_loop);
|
||||
io_uring_sqe *sqe = outer_loop->get_sqe();
|
||||
assert(sqe != NULL);
|
||||
this->outer_loop = outer_loop;
|
||||
this->outer_loop_data = ((ring_data_t*)sqe->user_data);
|
||||
io_uring_prep_poll_add(sqe, eventfd, POLLIN);
|
||||
outer_loop_data->callback = [this](ring_data_t *data)
|
||||
{
|
||||
if (data->res < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("eventfd poll failed: ") + strerror(-data->res));
|
||||
}
|
||||
outer_loop_data = NULL;
|
||||
if (stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
add_to_ringloop(this->outer_loop);
|
||||
ring.loop();
|
||||
};
|
||||
}
|
||||
|
||||
void msgr_iothread_t::run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mu);
|
||||
while (!stopped && !queue.size())
|
||||
cond.wait(lk);
|
||||
if (stopped)
|
||||
return;
|
||||
int i = 0;
|
||||
for (; i < queue.size(); i++)
|
||||
{
|
||||
io_uring_sqe *sqe = ring.get_sqe();
|
||||
if (!sqe)
|
||||
break;
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
*data = std::move(queue[i].data);
|
||||
*sqe = queue[i].sqe;
|
||||
sqe->user_data = (uint64_t)data;
|
||||
}
|
||||
queue.erase(queue.begin(), queue.begin()+i);
|
||||
}
|
||||
// We only want to offload sendmsg/recvmsg. Callbacks will be called in main thread
|
||||
ring.submit();
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init()
|
||||
{
|
||||
init_tls();
|
||||
#ifdef WITH_RDMACM
|
||||
if (use_rdmacm)
|
||||
{
|
||||
@@ -173,12 +75,7 @@ void osd_messenger_t::init()
|
||||
}
|
||||
if (ringloop && iothread_count > 0)
|
||||
{
|
||||
for (int i = 0; i < iothread_count; i++)
|
||||
{
|
||||
auto iot = new msgr_iothread_t();
|
||||
iothreads.push_back(iot);
|
||||
iot->add_to_ringloop(ringloop);
|
||||
}
|
||||
init_iothreads();
|
||||
}
|
||||
keepalive_timer_id = tfd->set_timer(1000, true, [this](int)
|
||||
{
|
||||
@@ -220,7 +117,7 @@ void osd_messenger_t::init()
|
||||
.opcode = OSD_OP_PING,
|
||||
},
|
||||
};
|
||||
op->callback = [this, cl](osd_op_t *op)
|
||||
op->callback = [this](osd_op_t *op)
|
||||
{
|
||||
auto cl_it = clients.find(op->client_id);
|
||||
if (cl_it == clients.end())
|
||||
@@ -229,6 +126,7 @@ void osd_messenger_t::init()
|
||||
delete op;
|
||||
return;
|
||||
}
|
||||
auto cl = cl_it->second;
|
||||
uint64_t fail_client_id = (op->reply.hdr.retval != 0 ? op->client_id : 0);
|
||||
auto fail_osd_num = cl->in_osd_num ? cl->in_osd_num : cl->osd_num;
|
||||
cl->ping_time_remaining = 0;
|
||||
@@ -271,14 +169,7 @@ osd_messenger_t::~osd_messenger_t()
|
||||
{
|
||||
stop_client(clients.begin()->first, true);
|
||||
}
|
||||
if (iothreads.size())
|
||||
{
|
||||
for (auto iot: iothreads)
|
||||
{
|
||||
delete iot;
|
||||
}
|
||||
iothreads.clear();
|
||||
}
|
||||
destroy_iothreads();
|
||||
#ifdef WITH_RDMA
|
||||
for (auto rdma_context: rdma_contexts)
|
||||
{
|
||||
@@ -295,10 +186,67 @@ osd_messenger_t::~osd_messenger_t()
|
||||
rdmacm_evch = NULL;
|
||||
}
|
||||
#endif
|
||||
for (auto encrypt_ctx: encrypt_xts_pool)
|
||||
{
|
||||
destroy_aes_xts_encrypt(encrypt_ctx);
|
||||
}
|
||||
for (auto decrypt_ctx: decrypt_xts_pool)
|
||||
{
|
||||
destroy_aes_xts_decrypt(decrypt_ctx);
|
||||
}
|
||||
destroy_tls();
|
||||
}
|
||||
|
||||
void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
static int parse_proto_checksums(const json11::Json & val, int default_value)
|
||||
{
|
||||
if (val.is_string())
|
||||
{
|
||||
const auto & str = val.string_value();
|
||||
if (str == "full")
|
||||
return MSGR_CSUM_FULL;
|
||||
else if (str == "payload")
|
||||
return MSGR_CSUM_PAYLOAD;
|
||||
else if (str == "gcm")
|
||||
return MSGR_CSUM_GCM;
|
||||
else if (str == "none")
|
||||
return 0;
|
||||
else if (str == "")
|
||||
return default_value;
|
||||
}
|
||||
else if (val.is_null())
|
||||
return default_value;
|
||||
fprintf(stderr, "proto_checksums should be \"full\", \"payload\", \"gcm\", \"none\""
|
||||
", \"\" or null (default), but it is: %s\n", val.dump().c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void osd_messenger_t::parse_config(const json11::Json & config, bool init)
|
||||
{
|
||||
this->max_cipher_pool_size = config["max_cipher_pool_size"].uint64_value();
|
||||
if (!this->max_cipher_pool_size)
|
||||
this->max_cipher_pool_size = 256;
|
||||
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
|
||||
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
|
||||
this->receive_buffer_size = 65536;
|
||||
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
|
||||
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
|
||||
: (int)config["min_zerocopy_send_size"].int64_value();
|
||||
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
|
||||
if (!this->peer_connect_interval)
|
||||
this->peer_connect_interval = 5;
|
||||
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
|
||||
if (!this->peer_connect_timeout)
|
||||
this->peer_connect_timeout = 5;
|
||||
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
|
||||
if (!this->osd_idle_timeout)
|
||||
this->osd_idle_timeout = 5;
|
||||
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
|
||||
if (!this->osd_ping_timeout)
|
||||
this->osd_ping_timeout = 5;
|
||||
this->log_level = config["log_level"].uint64_value();
|
||||
// All other parameters are only set on init
|
||||
if (!init)
|
||||
return;
|
||||
#ifdef WITH_RDMA
|
||||
if (!config["use_rdma"].is_null())
|
||||
{
|
||||
@@ -329,31 +277,27 @@ void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
|
||||
this->rdma_max_msg = 129*1024;
|
||||
#endif
|
||||
if (!osd_num)
|
||||
{
|
||||
tls_cert = config["cert"].string_value();
|
||||
tls_key = config["pkey"].string_value();
|
||||
osd_tls_ca = config["osd_ca"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
tls_cert = config["osd_cert"].string_value();
|
||||
tls_key = config["osd_pkey"].string_value();
|
||||
osd_tls_ca = config["osd_ca"].string_value();
|
||||
client_tls_ca = config["client_ca"].string_value();
|
||||
}
|
||||
this->use_proto_checksums = parse_proto_checksums(config["proto_checksums"], MSGR_CSUM_PAYLOAD);
|
||||
this->force_proto_checksums = parse_proto_checksums(config["force_proto_checksums"], tls_cert != "" ? MSGR_CSUM_PAYLOAD : 0);
|
||||
if (!osd_num)
|
||||
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
|
||||
else
|
||||
this->iothread_count = (uint32_t)config["osd_iothread_count"].uint64_value();
|
||||
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
|
||||
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
|
||||
this->receive_buffer_size = 65536;
|
||||
this->use_sync_send_recv = config["use_sync_send_recv"].bool_value() ||
|
||||
config["use_sync_send_recv"].uint64_value();
|
||||
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
|
||||
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
|
||||
: (int)config["min_zerocopy_send_size"].int64_value();
|
||||
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
|
||||
if (!this->peer_connect_interval)
|
||||
this->peer_connect_interval = 5;
|
||||
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
|
||||
if (!this->peer_connect_timeout)
|
||||
this->peer_connect_timeout = 5;
|
||||
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
|
||||
if (!this->osd_idle_timeout)
|
||||
this->osd_idle_timeout = 5;
|
||||
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
|
||||
if (!this->osd_ping_timeout)
|
||||
this->osd_ping_timeout = 5;
|
||||
this->log_level = config["log_level"].uint64_value();
|
||||
config["use_sync_send_recv"].uint64_value() || !ringloop;
|
||||
// OSD public & cluster networks
|
||||
this->osd_networks.clear();
|
||||
if (config["osd_network"].is_string())
|
||||
@@ -510,7 +454,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
|
||||
cl->peer_state = PEER_CONNECTING;
|
||||
cl->connect_timeout_id = -1;
|
||||
cl->osd_num = peer_osd;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
clients[client_id] = cl;
|
||||
clients_by_fd[peer_fd] = cl;
|
||||
tfd->set_fd_handler(peer_fd, true, [this](int peer_fd, int epoll_events)
|
||||
@@ -559,6 +503,7 @@ void osd_messenger_t::handle_connect_epoll(int peer_fd)
|
||||
handle_peer_epoll(peer_fd, epoll_events);
|
||||
});
|
||||
// Check OSD number
|
||||
init_tls_client(cl);
|
||||
check_peer_config(cl);
|
||||
}
|
||||
|
||||
@@ -648,7 +593,12 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
// Inform that we're OSD <osd_num>
|
||||
payload["osd_num"] = osd_num;
|
||||
}
|
||||
payload["features"] = json11::Json::object{ { "check_sequencing", true } };
|
||||
auto features = json11::Json::object{ { "check_sequencing", true } };
|
||||
if (use_proto_checksums)
|
||||
{
|
||||
features["proto_checksums"] = use_proto_checksums;
|
||||
}
|
||||
payload["features"] = features;
|
||||
#ifdef WITH_RDMA
|
||||
if (!use_rdmacm && rdma_contexts.size())
|
||||
{
|
||||
@@ -688,6 +638,11 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
err = true;
|
||||
fprintf(stderr, "Failed to get config from OSD %ju (retval=%jd), disconnecting peer\n", cl->osd_num, op->reply.hdr.retval);
|
||||
}
|
||||
else if (cl->gcm_enabled && !cl->hs_result.peer_is_osd)
|
||||
{
|
||||
err = true;
|
||||
fprintf(stderr, "Client %ju is not authenticated as an OSD, disconnecting peer\n", cl->client_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
config = json11::Json::parse(std::string((char*)op->buf), json_err);
|
||||
@@ -715,6 +670,21 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
err = !check_config_hook(cl, config);
|
||||
}
|
||||
}
|
||||
if (!err && use_proto_checksums)
|
||||
{
|
||||
auto peer_csums = config["features"]["proto_checksums"].uint64_value();
|
||||
if (peer_csums == MSGR_CSUM_GCM && use_proto_checksums == MSGR_CSUM_GCM && cl->gcm_enabled)
|
||||
cl->proto_csum_status = MSGR_CSUM_GCM;
|
||||
else if (peer_csums == MSGR_CSUM_FULL && use_proto_checksums == MSGR_CSUM_FULL)
|
||||
cl->proto_csum_status = MSGR_CSUM_FULL;
|
||||
else if (peer_csums && use_proto_checksums)
|
||||
cl->proto_csum_status = MSGR_CSUM_PAYLOAD;
|
||||
if (cl->proto_csum_status < force_proto_checksums)
|
||||
{
|
||||
fprintf(stderr, "Error: OSD %ju use_proto_checksums security level is lower than force_proto_checksums\n", cl->osd_num);
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
if (err)
|
||||
{
|
||||
osd_num_t peer_osd = cl->osd_num;
|
||||
@@ -786,7 +756,8 @@ 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);
|
||||
init_tls_client(cl);
|
||||
// Add FD to epoll
|
||||
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
|
||||
{
|
||||
@@ -829,6 +800,11 @@ bool osd_messenger_t::is_use_rdmacm()
|
||||
}
|
||||
#endif
|
||||
|
||||
bool osd_messenger_t::is_encryption_enabled()
|
||||
{
|
||||
return tls_cert != "" || tls_key != "" || osd_tls_ca != "";
|
||||
}
|
||||
|
||||
json11::Json::object osd_messenger_t::read_config(const json11::Json & config)
|
||||
{
|
||||
json11::Json::object file_config;
|
||||
|
||||
+133
-54
@@ -12,12 +12,20 @@
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/types.h>
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
#include <isa-l_crypto/aes_gcm.h>
|
||||
#endif
|
||||
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/robin_hood.h"
|
||||
#include "malloc_or_die.h"
|
||||
#include "json11/json11.hpp"
|
||||
#include "msgr_op.h"
|
||||
#include "timerfd_manager.h"
|
||||
#include "addr_util.h"
|
||||
#include "msgr_handshake.h"
|
||||
#include <ringloop.h>
|
||||
|
||||
#define CL_READ_HDR 1
|
||||
@@ -31,12 +39,19 @@
|
||||
#define PEER_RDMA 4
|
||||
#define PEER_STOPPED 5
|
||||
|
||||
#define MSGR_CSUM_PAYLOAD 1
|
||||
#define MSGR_CSUM_FULL 2
|
||||
#define MSGR_CSUM_GCM 4
|
||||
#define MSGR_CSUM_NEG 8
|
||||
|
||||
#define VITASTOR_CONFIG_PATH "/etc/vitastor/vitastor.conf"
|
||||
|
||||
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
|
||||
|
||||
#define MSGR_SENDP_HDR 1
|
||||
#define MSGR_SENDP_FREE 2
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
|
||||
#define MAX_SIMPLE_PAYLOAD_SIZE 1048576
|
||||
|
||||
struct msgr_sendp_t
|
||||
{
|
||||
@@ -49,6 +64,13 @@ 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 user_info_t;
|
||||
|
||||
struct osd_client_t
|
||||
{
|
||||
uint64_t client_id = 0;
|
||||
@@ -65,23 +87,49 @@ 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;
|
||||
#endif
|
||||
|
||||
bool gcm_enabled = false;
|
||||
msgr_handshake_i *hs = NULL;
|
||||
msgr_handshake_result_t hs_result;
|
||||
std::shared_ptr<user_info_t> user_info;
|
||||
std::vector<uint8_t> my_key, peer_key;
|
||||
uint64_t my_iv_ctr = 0, peer_iv_ctr = 0;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
isal_gcm_key_data my_key_isal, peer_key_isal;
|
||||
isal_gcm_context_data *enc_ctx = NULL;
|
||||
isal_gcm_context_data *dec_ctx = NULL;
|
||||
#else
|
||||
EVP_CIPHER_CTX *enc_ctx = NULL;
|
||||
EVP_CIPHER_CTX *dec_ctx = NULL;
|
||||
#endif
|
||||
uint8_t enc_tag[16];
|
||||
size_t enc_tag_size = 0;
|
||||
uint8_t dec_tag[16];
|
||||
size_t dec_tag_size = 0;
|
||||
|
||||
// Read state
|
||||
bool io_error = false;
|
||||
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;
|
||||
std::vector<int> recv_flags;
|
||||
uint64_t read_op_id = 1;
|
||||
bool check_sequencing = false;
|
||||
bool enable_pg_locks = false;
|
||||
op_aes_xts_decrypt_t *xts_dec_ctx = NULL;
|
||||
size_t read_op_inline_decrypt_pos = 0;
|
||||
size_t read_op_inline_decrypt_in = 0;
|
||||
int proto_csum_status = 0;
|
||||
XXH3_state_t* read_csum_state = NULL;
|
||||
|
||||
// Incoming operations
|
||||
std::vector<osd_op_t*> received_ops;
|
||||
@@ -94,11 +142,17 @@ 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 *xts_enc_ctx = NULL;
|
||||
XXH3_state_t* write_csum_state = NULL;
|
||||
|
||||
~osd_client_t();
|
||||
void cancel_ops();
|
||||
@@ -129,43 +183,7 @@ struct osd_op_stats_t
|
||||
uint64_t subop_stat_count[OSD_OP_MAX+1] = { 0 };
|
||||
};
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
|
||||
#ifdef __MOCK__
|
||||
class msgr_iothread_t;
|
||||
#else
|
||||
struct iothread_sqe_t
|
||||
{
|
||||
io_uring_sqe sqe;
|
||||
ring_data_t data;
|
||||
};
|
||||
|
||||
class msgr_iothread_t
|
||||
{
|
||||
protected:
|
||||
ring_loop_t ring;
|
||||
ring_loop_t *outer_loop = NULL;
|
||||
ring_data_t *outer_loop_data = NULL;
|
||||
int eventfd = -1;
|
||||
bool stopped = false;
|
||||
std::mutex mu;
|
||||
std::condition_variable cond;
|
||||
std::vector<iothread_sqe_t> queue;
|
||||
std::thread thread;
|
||||
|
||||
void run();
|
||||
public:
|
||||
|
||||
msgr_iothread_t();
|
||||
~msgr_iothread_t();
|
||||
|
||||
void add_sqe(io_uring_sqe & sqe);
|
||||
void stop();
|
||||
void add_to_ringloop(ring_loop_t *outer_loop);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
struct rdma_event_channel;
|
||||
@@ -176,9 +194,19 @@ struct osd_messenger_t;
|
||||
struct rdmacm_connecting_t;
|
||||
#endif
|
||||
|
||||
class msgr_op_reader_t;
|
||||
class msgr_op_writer_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) osd_messenger_t
|
||||
{
|
||||
protected:
|
||||
friend class copy_op_reader_t;
|
||||
friend class gcm_op_reader_t;
|
||||
friend class get_op_reader_t;
|
||||
friend class copy_op_writer_t;
|
||||
friend class gcm_op_writer_t;
|
||||
friend class get_op_writer_t;
|
||||
|
||||
int keepalive_timer_id = -1;
|
||||
|
||||
uint32_t receive_buffer_size = 0;
|
||||
@@ -190,6 +218,12 @@ protected:
|
||||
bool use_sync_send_recv = false;
|
||||
int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE;
|
||||
int iothread_count = 0;
|
||||
int max_cipher_pool_size = 256;
|
||||
|
||||
std::string tls_cert;
|
||||
std::string tls_key;
|
||||
std::string osd_tls_ca;
|
||||
std::string client_tls_ca;
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
bool use_rdma = true;
|
||||
@@ -207,19 +241,42 @@ protected:
|
||||
robin_hood::unordered_flat_map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
|
||||
#endif
|
||||
|
||||
bool gcm_enabled = false;
|
||||
msgr_handshake_ctx_i *hs_ctx = NULL;
|
||||
|
||||
void init_tls();
|
||||
void destroy_tls();
|
||||
void init_tls_client(osd_client_t *cl);
|
||||
bool do_tls_handshake(osd_client_t *cl, bool from_recv = false);
|
||||
bool derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer);
|
||||
|
||||
std::vector<msgr_iothread_t*> iothreads;
|
||||
std::vector<uint64_t> read_ready_clients;
|
||||
std::vector<uint64_t> write_ready_clients;
|
||||
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
|
||||
std::deque<osd_op_t*> set_immediate_ops;
|
||||
|
||||
std::vector<op_aes_xts_encrypt_t*> encrypt_xts_pool;
|
||||
std::vector<op_aes_xts_decrypt_t*> decrypt_xts_pool;
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
std::vector<isal_gcm_context_data*> encrypt_gcm_pool;
|
||||
std::vector<isal_gcm_context_data*> decrypt_gcm_pool;
|
||||
#else
|
||||
std::vector<EVP_CIPHER_CTX*> encrypt_gcm_pool;
|
||||
std::vector<EVP_CIPHER_CTX*> decrypt_gcm_pool;
|
||||
#endif
|
||||
|
||||
public:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
ring_loop_i *ringloop = NULL;
|
||||
bool has_sendmsg_zc = false;
|
||||
// osd_num_t is only for logging and asserts
|
||||
uint64_t next_client_id = 1;
|
||||
osd_num_t osd_num;
|
||||
// osd_num = 0 for client messenger, osd_num > 0 for OSD messenger
|
||||
osd_num_t osd_num = 0;
|
||||
uint32_t clean_entry_bitmap_size = 0;
|
||||
uint32_t bs_block_size = 0;
|
||||
uint32_t max_write_request_size = 0;
|
||||
robin_hood::unordered_flat_map<uint64_t, osd_client_t*> clients;
|
||||
robin_hood::unordered_flat_map<uint64_t, osd_client_t*> osd_peers;
|
||||
robin_hood::unordered_flat_map<int, osd_client_t*> clients_by_fd;
|
||||
@@ -230,11 +287,14 @@ public:
|
||||
std::vector<addr_mask_t> osd_cluster_network_masks;
|
||||
std::vector<std::string> all_osd_networks;
|
||||
std::vector<addr_mask_t> all_osd_network_masks;
|
||||
int use_proto_checksums = 0;
|
||||
int force_proto_checksums = 0;
|
||||
// op statistics
|
||||
osd_op_stats_t stats, recovery_stats;
|
||||
|
||||
void init();
|
||||
void parse_config(const json11::Json & config);
|
||||
void init_iothreads();
|
||||
void parse_config(const json11::Json & config, bool init);
|
||||
void connect_peer(uint64_t osd_num, json11::Json peer_state);
|
||||
void stop_client(uint64_t client_id, bool force_delete = false);
|
||||
void destroy_client(osd_client_t *cl);
|
||||
@@ -243,9 +303,11 @@ public:
|
||||
std::function<void(osd_num_t)> repeer_pgs;
|
||||
std::function<void(osd_num_t)> break_pg_locks;
|
||||
std::function<bool(osd_client_t*, json11::Json)> check_config_hook;
|
||||
std::function<void(osd_client_t*)> handshake_hook;
|
||||
void read_requests();
|
||||
void send_replies();
|
||||
void accept_connections(int listen_fd);
|
||||
void destroy_iothreads();
|
||||
~osd_messenger_t();
|
||||
|
||||
static json11::Json::object read_config(const json11::Json & config);
|
||||
@@ -263,6 +325,7 @@ public:
|
||||
rdma_cm_id *rdmacm_listen(const std::string & bind_address, int rdmacm_port, int *bound_port, int log_level);
|
||||
void rdmacm_destroy_listener(rdma_cm_id *listener);
|
||||
#endif
|
||||
bool is_encryption_enabled();
|
||||
|
||||
void inc_op_stats(osd_op_stats_t & stats, uint64_t opcode, timespec & tv_begin, timespec & tv_end, uint64_t len);
|
||||
void measure_exec(osd_op_t *cur_op);
|
||||
@@ -279,20 +342,36 @@ protected:
|
||||
|
||||
bool try_send(osd_client_t *cl);
|
||||
void handle_send(int result, bool prev, bool more, osd_client_t *cl);
|
||||
bool op_write_to(osd_client_t *cl, msgr_op_writer_t & wr);
|
||||
void next_write_op(osd_client_t *cl);
|
||||
bool op_write_buf(osd_client_t *cl, uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len, bool skip_csum, size_t & from, size_t & done);
|
||||
bool op_copy_data_to(osd_client_t *cl, uint8_t *dst, size_t dst_len, size_t & from, size_t & done);
|
||||
size_t copy_ops_to(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
template<typename T> size_t copy_ops_to_with(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
|
||||
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);
|
||||
void handle_reply_ready(osd_op_t *op);
|
||||
void handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
|
||||
template<typename T> bool handle_buffer_with(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);
|
||||
bool op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr);
|
||||
bool handle_finished_op(osd_client_t *cl);
|
||||
void handle_immediate_ops();
|
||||
|
||||
void op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
|
||||
void op_encrypt_free(osd_client_t* cl);
|
||||
void op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
|
||||
void op_decrypt_start(osd_client_t* cl);
|
||||
void op_decrypt_inline(osd_client_t* cl);
|
||||
void op_decrypt_free(osd_client_t* cl);
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
void try_send_rdma(osd_client_t *cl);
|
||||
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);
|
||||
void destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn);
|
||||
#endif
|
||||
#ifdef WITH_RDMACM
|
||||
void handle_rdmacm_events();
|
||||
|
||||
@@ -0,0 +1,661 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
#include <isa-l_crypto/isal_crypto_api.h>
|
||||
#endif
|
||||
|
||||
#include "str_util.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "messenger.h"
|
||||
#include "msgr_encrypt.h"
|
||||
#include "http_client.h"
|
||||
#include "openssl_util.h"
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
|
||||
{
|
||||
assert(!encrypted);
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::start(osd_client_t *cl, uint8_t *key, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!encrypted);
|
||||
this->cl = cl;
|
||||
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;
|
||||
}
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
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)
|
||||
{
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_xts_enc_256(key+32, key, iv, block_size, in, out);
|
||||
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
|
||||
#else
|
||||
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
|
||||
}
|
||||
|
||||
static inline void copy_or_gcm(osd_client_t *cl, uint8_t *out, uint8_t *in, size_t n)
|
||||
{
|
||||
if (cl->proto_csum_status != MSGR_CSUM_GCM)
|
||||
memcpy(out, in, n);
|
||||
else
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_enc_256_update(&cl->my_key_isal, cl->enc_ctx, out, in, n);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out;
|
||||
if (EVP_EncryptUpdate(cl->enc_ctx, out, &actual_out, in, n) != 1)
|
||||
{
|
||||
fprintf(stderr, "EncryptUpdate error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == n);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
copy_or_gcm(cl, out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
{
|
||||
encrypted = false;
|
||||
done_in += 1;
|
||||
}
|
||||
}
|
||||
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;
|
||||
copy_or_gcm(cl, out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in-1;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
encrypt_block(in, out);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
copy_or_gcm(cl, out, out, block_size);
|
||||
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);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
copy_or_gcm(cl, out, out, block_size);
|
||||
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()
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
|
||||
{
|
||||
assert(!decrypted);
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::start(osd_client_t *cl, uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!decrypted);
|
||||
this->cl = cl;
|
||||
this->start_offset = start_offset;
|
||||
this->key_chain = key_chain;
|
||||
this->chain_size = chain_size;
|
||||
this->key_indexes = key_indexes;
|
||||
this->key_index_bytes = osd_op_rw_t::chain_info_bytes(chain_size);
|
||||
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;
|
||||
}
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
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 > 1)
|
||||
{
|
||||
uint32_t key_index = key_index_bytes == 1
|
||||
? ((uint8_t*)key_indexes)[offset/block_size]
|
||||
: (key_index_bytes == 2
|
||||
? ((uint16_t*)key_indexes)[offset/block_size]
|
||||
: (key_index_bytes == 4
|
||||
? ((uint32_t*)key_indexes)[offset/block_size]
|
||||
: UINT32_MAX));
|
||||
assert(key_index < chain_size);
|
||||
key = key_chain[key_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
key = key_chain[0];
|
||||
}
|
||||
if (!key)
|
||||
{
|
||||
if (in != out)
|
||||
memcpy(out, in, block_size);
|
||||
return;
|
||||
}
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_xts_dec_256(key+32, key, iv, block_size, in, out);
|
||||
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
|
||||
#else
|
||||
if (EVP_DecryptInit_ex(ctx, NULL, NULL, chain_size == 1 ? 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
|
||||
}
|
||||
|
||||
static inline void gcm_dec(osd_client_t *cl, uint8_t *out, uint8_t *in, size_t n)
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_dec_256_update(&cl->peer_key_isal, cl->dec_ctx, out, in, n);
|
||||
assert(!r);
|
||||
#else
|
||||
int actual_out;
|
||||
if (EVP_DecryptUpdate(cl->dec_ctx, out, &actual_out, in, n) != 1)
|
||||
{
|
||||
fprintf(stderr, "DecryptUpdate error: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == n);
|
||||
#endif
|
||||
}
|
||||
|
||||
// out may be NULL, in this case all input is still decrypted to calculate checksums,
|
||||
// but part of it is skipped and not copied to out
|
||||
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;
|
||||
if (out)
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
{
|
||||
decrypted = false;
|
||||
done_in += 1;
|
||||
}
|
||||
}
|
||||
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 || !out)
|
||||
{
|
||||
// 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);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
gcm_dec(cl, tmp, tmp, block_size);
|
||||
decrypt_block(tmp, tmp);
|
||||
decrypted = true;
|
||||
if (out)
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in-1;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
{
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
gcm_dec(cl, tmp, in, block_size);
|
||||
if (out)
|
||||
decrypt_block(tmp, out);
|
||||
}
|
||||
else
|
||||
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);
|
||||
assert(out);
|
||||
if (cl->proto_csum_status == MSGR_CSUM_GCM)
|
||||
gcm_dec(cl, tmp, tmp, block_size);
|
||||
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;
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
|
||||
{
|
||||
if (!cl->xts_enc_ctx)
|
||||
{
|
||||
if (encrypt_xts_pool.size())
|
||||
{
|
||||
cl->xts_enc_ctx = encrypt_xts_pool.back();
|
||||
encrypt_xts_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->xts_enc_ctx = new op_aes_xts_encrypt_t();
|
||||
assert(cl->write_op->enc->key_chain[0]);
|
||||
cl->xts_enc_ctx->start(cl, cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
|
||||
}
|
||||
while (done_plain < plain_len && done_enc < enc_len)
|
||||
{
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->xts_enc_ctx->update(plain+done_plain, plain_len-done_plain, enc_buf+done_enc, enc_len-done_enc, done_in, done_out);
|
||||
if (cl->write_csum_state && done_out > 0)
|
||||
XXH3_64bits_update(cl->write_csum_state, enc_buf+done_enc, done_out);
|
||||
done_enc += done_out;
|
||||
cl->write_op_pos += done_in;
|
||||
done_plain += done_in;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
while (done_plain < plain_len && done_enc < enc_len)
|
||||
{
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
// plain == NULL means skip output
|
||||
cl->xts_dec_ctx->update(enc_buf+done_enc, enc_len-done_enc, plain ? plain+done_plain : NULL, plain_len-done_plain, done_in, done_out);
|
||||
if (cl->read_csum_state && done_in > 0)
|
||||
XXH3_64bits_update(cl->read_csum_state, enc_buf+done_enc, done_in);
|
||||
done_enc += done_in;
|
||||
cl->read_op_pos += done_out;
|
||||
cl->read_op_inline_decrypt_in += done_in;
|
||||
done_plain += done_out;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
|
||||
{
|
||||
if (!cl->xts_dec_ctx)
|
||||
{
|
||||
if (decrypt_xts_pool.size())
|
||||
{
|
||||
cl->xts_dec_ctx = decrypt_xts_pool.back();
|
||||
decrypt_xts_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->xts_dec_ctx = new op_aes_xts_decrypt_t();
|
||||
auto & enc = cl->read_op->enc;
|
||||
assert(cl->read_op->req.hdr.opcode == OSD_OP_READ);
|
||||
cl->xts_dec_ctx->start(cl, 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->xts_dec_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);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->xts_dec_ctx)
|
||||
{
|
||||
if (decrypt_xts_pool.size() > max_cipher_pool_size)
|
||||
delete cl->xts_dec_ctx;
|
||||
else
|
||||
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
|
||||
cl->xts_dec_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_encrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->xts_enc_ctx)
|
||||
{
|
||||
if (encrypt_xts_pool.size() > max_cipher_pool_size)
|
||||
delete cl->xts_enc_ctx;
|
||||
else
|
||||
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
|
||||
cl->xts_enc_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer)
|
||||
{
|
||||
if (!cl->hs_result.shared_secret.size())
|
||||
{
|
||||
cl->hs_result = cl->hs->get_result();
|
||||
if (handshake_hook)
|
||||
{
|
||||
handshake_hook(cl);
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> old_my = cl->my_key, old_peer = cl->peer_key;
|
||||
// Both keys include AES key and iv + xxhash3 secret
|
||||
const auto len = AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE;
|
||||
cl->my_key.resize(len);
|
||||
cl->peer_key.resize(len);
|
||||
bool ok = true;
|
||||
if (update_my || !old_my.size())
|
||||
{
|
||||
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
|
||||
old_my.size() ? old_my.data() : NULL, old_my.size(),
|
||||
cl->is_incoming ? "server key" : "client key", cl->my_key.data(), len);
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
if (ok)
|
||||
isal_aes_gcm_pre_256(cl->my_key.data(), &cl->my_key_isal);
|
||||
#endif
|
||||
cl->my_iv_ctr = 0;
|
||||
}
|
||||
if (update_peer || !old_peer.size())
|
||||
{
|
||||
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
|
||||
old_peer.size() ? old_peer.data() : NULL, old_peer.size(),
|
||||
!cl->is_incoming ? "server key" : "client key", cl->peer_key.data(), len);
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
if (ok)
|
||||
isal_aes_gcm_pre_256(cl->peer_key.data(), &cl->peer_key_isal);
|
||||
#endif
|
||||
cl->peer_iv_ctr = 0;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_tls()
|
||||
{
|
||||
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
|
||||
{
|
||||
if (tls_cert.empty() || tls_key.empty() || osd_tls_ca.empty() || osd_num && client_tls_ca.empty())
|
||||
{
|
||||
if (osd_num)
|
||||
fprintf(stderr, "Vitastor transport encryption requires osd_cert, osd_pkey, osd_ca, client_ca options for OSDs\n");
|
||||
else
|
||||
fprintf(stderr, "Vitastor transport encryption requires cert, pkey and osd_ca options\n");
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
gcm_enabled = true;
|
||||
hs_ctx = msgr_handshake_ctx_i::create_ctx();
|
||||
if (!hs_ctx->init(tls_cert, tls_key, osd_tls_ca, client_tls_ca))
|
||||
{
|
||||
fprintf(stderr, "Error: %s\n", hs_ctx->get_error().c_str());
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_tls_client(osd_client_t *cl)
|
||||
{
|
||||
if (gcm_enabled)
|
||||
{
|
||||
cl->gcm_enabled = true;
|
||||
cl->hs = hs_ctx->create();
|
||||
cl->hs->init(cl->is_incoming);
|
||||
if (cl->hs->out_size())
|
||||
{
|
||||
if (cl->write_state == 0)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_tls()
|
||||
{
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
for (isal_gcm_context_data *ctx: encrypt_gcm_pool)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
||||
for (isal_gcm_context_data *ctx: decrypt_gcm_pool)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
||||
#else
|
||||
for (EVP_CIPHER_CTX *ctx: encrypt_gcm_pool)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
for (EVP_CIPHER_CTX *ctx: decrypt_gcm_pool)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
#endif
|
||||
if (hs_ctx)
|
||||
{
|
||||
delete hs_ctx;
|
||||
hs_ctx = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
#include <isa-l_crypto/aes_xts.h>
|
||||
#endif
|
||||
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
struct osd_client_t;
|
||||
|
||||
class op_aes_xts_encrypt_t
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
osd_client_t *cl = NULL;
|
||||
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();
|
||||
|
||||
void start(osd_client_t *cl, 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
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
osd_client_t *cl = NULL;
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t **key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
void *key_indexes = NULL;
|
||||
int key_index_bytes = 0;
|
||||
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();
|
||||
|
||||
void start(osd_client_t *cl, uint8_t **key_chain, size_t chain_size, void *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);
|
||||
@@ -0,0 +1,810 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/obj_mac.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/kdf.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "msgr_handshake.h"
|
||||
#include "malloc_or_die.h"
|
||||
#include "openssl_util.h"
|
||||
#include "str_util.h"
|
||||
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
|
||||
// TLS 1.3-like handshake
|
||||
|
||||
// 1. Client->server: EC public key
|
||||
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
|
||||
// 3. Client->server: encrypted certificate and digital signature of the handshake
|
||||
|
||||
// "vitaECDH" interleaved
|
||||
#define MSGR_HS_MAGIC 0x4861447443694576l
|
||||
#define MSGR_HS_MAX_LEN 131072
|
||||
|
||||
#define MSGR_HS_SERVER_INIT 0
|
||||
#define MSGR_HS_CLIENT_INIT 1
|
||||
#define MSGR_HS_SERVER_REPLY 2
|
||||
#define MSGR_HS_CLIENT_REPLY 3
|
||||
#define MSGR_HS_DONE 100
|
||||
#define MSGR_HS_ERROR 101
|
||||
|
||||
struct __attribute__((__packed__)) msgr_handshake_hdr_t
|
||||
{
|
||||
uint32_t msg_len;
|
||||
uint64_t magic;
|
||||
uint32_t type;
|
||||
};
|
||||
|
||||
class msgr_handshake_ctx_t: public msgr_handshake_ctx_i
|
||||
{
|
||||
friend class msgr_handshake_t;
|
||||
|
||||
EVP_PKEY_CTX *pctx = NULL;
|
||||
EVP_PKEY *params = NULL;
|
||||
X509_STORE *ca = NULL;
|
||||
X509 *osd_ca = NULL;
|
||||
X509 *client_ca = NULL;
|
||||
std::string my_cert_pem;
|
||||
X509 *my_cert = NULL;
|
||||
EVP_PKEY *my_pubkey = NULL;
|
||||
const EVP_MD *md = NULL;
|
||||
EVP_PKEY *my_privkey = NULL;
|
||||
EVP_KDF_CTX* kdf_ctx = NULL;
|
||||
std::string error;
|
||||
|
||||
bool on_error(const std::string & prefix);
|
||||
|
||||
public:
|
||||
~msgr_handshake_ctx_t();
|
||||
msgr_handshake_i* create() override;
|
||||
bool init(const std::string & pem_cert, const std::string & pem_key,
|
||||
const std::string & pem_osd_ca, const std::string & pem_client_ca) override;
|
||||
std::string get_error() override;
|
||||
bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) override;
|
||||
};
|
||||
|
||||
class msgr_handshake_t: public msgr_handshake_i
|
||||
{
|
||||
msgr_handshake_ctx_t *ctx = NULL;
|
||||
bool is_server = false;
|
||||
|
||||
std::vector<uint8_t> full_handshake;
|
||||
std::vector<uint8_t> in_buf;
|
||||
uint8_t *out_buf = NULL;
|
||||
size_t out_buf_size = 0;
|
||||
std::string error;
|
||||
|
||||
int state = 0;
|
||||
|
||||
EVP_PKEY *ec_key = NULL;
|
||||
X509 *peer_cert = NULL;
|
||||
bool peer_is_osd = false;
|
||||
std::vector<uint8_t> shared_secret;
|
||||
std::vector<uint8_t> hs_key, peer_hs_key;
|
||||
|
||||
msgr_handshake_hdr_t *cur_hdr = NULL;
|
||||
uint8_t *cur_buf = NULL;
|
||||
size_t cur_left = 0;
|
||||
|
||||
bool on_error(const std::string & prefix);
|
||||
bool derive_shared_secret(EVP_PKEY *peer_ec_key);
|
||||
bool derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len);
|
||||
bool sign(std::vector<uint8_t> & out);
|
||||
bool verify(const uint8_t *signature, size_t signature_len);
|
||||
bool encrypt(const uint8_t* src, size_t len, uint8_t* dest);
|
||||
bool decrypt(const uint8_t* src, size_t & len, uint8_t* dest);
|
||||
bool make_client_init();
|
||||
bool make_server_reply();
|
||||
bool make_client_reply();
|
||||
bool verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len);
|
||||
ssize_t start_msg(uint8_t* src, size_t len, uint32_t expected_type);
|
||||
bool read_with_len(const uint8_t* & dst, uint32_t & dst_len);
|
||||
bool handle_client_init();
|
||||
bool handle_server_reply();
|
||||
bool handle_client_reply();
|
||||
bool handle_peer_cert(const uint8_t *key, uint32_t key_len);
|
||||
void complete();
|
||||
|
||||
public:
|
||||
// Workflow: create -> init -> handle -> get_result/get_error -> destruct
|
||||
msgr_handshake_t(msgr_handshake_ctx_t *ctx): ctx(ctx) {}
|
||||
~msgr_handshake_t();
|
||||
bool init(bool server_mode) override;
|
||||
ssize_t handle(uint8_t* in_buf, size_t in_size) override;
|
||||
bool done() override;
|
||||
uint8_t *get_out() override;
|
||||
size_t out_size() override;
|
||||
void eat_out(size_t n) override;
|
||||
void reset_out() override;
|
||||
msgr_handshake_result_t get_result() override;
|
||||
std::string get_error() override;
|
||||
};
|
||||
|
||||
msgr_handshake_ctx_i* msgr_handshake_ctx_i::create_ctx()
|
||||
{
|
||||
return new msgr_handshake_ctx_t();
|
||||
}
|
||||
|
||||
msgr_handshake_i* msgr_handshake_ctx_t::create()
|
||||
{
|
||||
return new msgr_handshake_t(this);
|
||||
}
|
||||
|
||||
bool msgr_handshake_ctx_t::init(const std::string & pem_cert, const std::string & pem_key,
|
||||
const std::string & pem_osd_ca, const std::string & pem_client_ca)
|
||||
{
|
||||
if (pem_cert.substr(0, 5) == "-----")
|
||||
my_cert_pem = pem_cert;
|
||||
else
|
||||
{
|
||||
my_cert_pem = read_file(pem_cert);
|
||||
if (my_cert_pem.empty())
|
||||
{
|
||||
error = "Failed to load certificate file";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
{
|
||||
BIO *bio = BIO_new_mem_buf(my_cert_pem.data(), my_cert_pem.size());
|
||||
if (!bio)
|
||||
return on_error("BIO_new_mem_buf: ");
|
||||
my_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
|
||||
BIO_free(bio);
|
||||
if (!my_cert)
|
||||
return on_error("Failed to load certificate: ");
|
||||
}
|
||||
if (!(my_pubkey = X509_get0_pubkey(my_cert)))
|
||||
return on_error("X509_get0_pubkey: ");
|
||||
if (!(md = EVP_get_digestbynid(NID_sha384)))
|
||||
return on_error("EVP_get_digestbynid SHA384: ");
|
||||
if (!(my_privkey = openssl_load_key(pem_key)))
|
||||
return on_error("Failed to load private key: ");
|
||||
if (!(ca = X509_STORE_new()))
|
||||
return on_error("X509_STORE_CTX_new: ");
|
||||
if (!(osd_ca = openssl_load_cert(pem_osd_ca)))
|
||||
return on_error("Failed to load OSD CA certificate: ");
|
||||
if (X509_STORE_add_cert(ca, osd_ca) <= 0)
|
||||
return on_error("X509_STORE_add_cert OSD CA: ");
|
||||
if (!pem_client_ca.empty() && !(client_ca = openssl_load_cert(pem_client_ca)))
|
||||
return on_error("Failed to load client CA certificate: ");
|
||||
if (client_ca && X509_STORE_add_cert(ca, client_ca) <= 0)
|
||||
return on_error("X509_STORE_add_cert client CA: ");
|
||||
if (!(pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)))
|
||||
return on_error("EVP_PKEY_CTX_new_id: ");
|
||||
if (EVP_PKEY_paramgen_init(pctx) <= 0)
|
||||
return on_error("EVP_PKEY_paramgen_init: ");
|
||||
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, /*NID_X9_62_prime256v1*/NID_secp384r1) <= 0)
|
||||
return on_error("EVP_PKEY_CTX_set_ec_paramgen_curve_nid: ");
|
||||
if (EVP_PKEY_paramgen(pctx, ¶ms) <= 0)
|
||||
return on_error("EVP_PKEY_paramgen: ");
|
||||
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
|
||||
if (!kdf)
|
||||
return on_error("EVP_KDF_fetch: ");
|
||||
kdf_ctx = EVP_KDF_CTX_new(kdf);
|
||||
EVP_KDF_free(kdf);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string msgr_handshake_ctx_t::get_error()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
msgr_handshake_ctx_t::~msgr_handshake_ctx_t()
|
||||
{
|
||||
if (pctx)
|
||||
EVP_PKEY_CTX_free(pctx);
|
||||
if (params)
|
||||
EVP_PKEY_free(params);
|
||||
if (ca)
|
||||
X509_STORE_free(ca);
|
||||
if (osd_ca)
|
||||
X509_free(osd_ca);
|
||||
if (client_ca)
|
||||
X509_free(client_ca);
|
||||
my_pubkey = NULL;
|
||||
if (my_cert)
|
||||
X509_free(my_cert);
|
||||
if (my_privkey)
|
||||
EVP_PKEY_free(my_privkey);
|
||||
if (kdf_ctx)
|
||||
EVP_KDF_CTX_free(kdf_ctx);
|
||||
}
|
||||
|
||||
bool msgr_handshake_ctx_t::on_error(const std::string & prefix)
|
||||
{
|
||||
error = prefix+ERR_error_string(ERR_get_error(), NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool msgr_handshake_ctx_t::derive_kdf(const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize)
|
||||
{
|
||||
OSSL_PARAM params[5];
|
||||
int n = 0;
|
||||
params[n++] = OSSL_PARAM_construct_utf8_string("digest", (char*)"sha384", (size_t)7);
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("key", (void*)insecret, insecret_len);
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("info", (void*)label, strlen(label)+1);
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("salt", (salt ? (void*)salt : (void*)""), salt_len);
|
||||
params[n++] = OSSL_PARAM_construct_end();
|
||||
assert(n <= sizeof(params)/sizeof(OSSL_PARAM));
|
||||
if (EVP_KDF_CTX_set_params(kdf_ctx, params) <= 0)
|
||||
return false;
|
||||
if (EVP_KDF_derive(kdf_ctx, outsecret, outsize, NULL) <= 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
msgr_handshake_t::~msgr_handshake_t()
|
||||
{
|
||||
if (out_buf)
|
||||
free(out_buf);
|
||||
if (peer_cert)
|
||||
X509_free(peer_cert);
|
||||
if (ec_key)
|
||||
EVP_PKEY_free(ec_key);
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::on_error(const std::string & prefix)
|
||||
{
|
||||
error = prefix+ERR_error_string(ERR_get_error(), NULL);
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::init(bool server_mode)
|
||||
{
|
||||
this->ctx = ctx;
|
||||
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> kctx(EVP_PKEY_CTX_new(ctx->params, NULL), EVP_PKEY_CTX_free);
|
||||
if (!kctx)
|
||||
return on_error("EVP_PKEY_CTX_new with EC params: ");
|
||||
if (EVP_PKEY_keygen_init(kctx.get()) <= 0)
|
||||
return on_error("EVP_PKEY_keygen_init: ");
|
||||
if (EVP_PKEY_keygen(kctx.get(), &ec_key) <= 0)
|
||||
return on_error("EVP_PKEY_keygen: ");
|
||||
if (!server_mode)
|
||||
{
|
||||
// Send initial message - only the EC public key
|
||||
if (!make_client_init())
|
||||
return false;
|
||||
}
|
||||
this->is_server = server_mode;
|
||||
this->state = server_mode ? MSGR_HS_SERVER_INIT : MSGR_HS_CLIENT_INIT;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copy_to(std::vector<uint8_t> & buf, const void* src, uint32_t len)
|
||||
{
|
||||
size_t old_size = buf.size();
|
||||
buf.resize(buf.size() + len);
|
||||
memcpy(buf.data() + old_size, src, len);
|
||||
}
|
||||
|
||||
static void copy_to_raw(uint8_t* & buf, const void* src, size_t len)
|
||||
{
|
||||
memcpy(buf, src, len);
|
||||
buf += len;
|
||||
}
|
||||
|
||||
static void copy_to_with_len(std::vector<uint8_t> & buf, const void* src, uint32_t len)
|
||||
{
|
||||
copy_to(buf, &len, sizeof(len));
|
||||
copy_to(buf, src, len);
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::derive_shared_secret(EVP_PKEY *peer_ec_key)
|
||||
{
|
||||
EVP_PKEY_CTX *dh_ctx = NULL;
|
||||
if (!(dh_ctx = EVP_PKEY_CTX_new(ec_key, NULL)))
|
||||
return on_error("EVP_PKEY_CTX_new for ECDH: ");
|
||||
if (!EVP_PKEY_derive_init(dh_ctx))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive_init: ");
|
||||
}
|
||||
if (!EVP_PKEY_derive_set_peer(dh_ctx, peer_ec_key))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive_set_peer: ");
|
||||
}
|
||||
size_t len = 0;
|
||||
if (!EVP_PKEY_derive(dh_ctx, NULL, &len))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive get length: ");
|
||||
}
|
||||
shared_secret.resize(len);
|
||||
assert(len == 48);
|
||||
if (!EVP_PKEY_derive(dh_ctx, shared_secret.data(), &len))
|
||||
{
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return on_error("EVP_PKEY_derive: ");
|
||||
}
|
||||
assert(len == shared_secret.size());
|
||||
shared_secret.resize(len);
|
||||
EVP_PKEY_CTX_free(dh_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len)
|
||||
{
|
||||
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> peer_ec_key(EVP_PKEY_new(), EVP_PKEY_free);
|
||||
if (!peer_ec_key)
|
||||
return on_error("EVP_PKEY_new: ");
|
||||
if (EVP_PKEY_copy_parameters(peer_ec_key.get(), ec_key) <= 0)
|
||||
return on_error("EVP_PKEY_copy_parameters: ");
|
||||
if (EVP_PKEY_set1_encoded_public_key(peer_ec_key.get(), encoded_peer_key, encoded_key_len) <= 0)
|
||||
return on_error("Invalid handshake peer key: ");
|
||||
if (!derive_shared_secret(peer_ec_key.get()))
|
||||
return false;
|
||||
hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
|
||||
peer_hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
|
||||
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
|
||||
NULL, 0, (state == MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
|
||||
hs_key.data(), hs_key.size()))
|
||||
return on_error("derive_kdf: ");
|
||||
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
|
||||
NULL, 0, (state != MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
|
||||
peer_hs_key.data(), peer_hs_key.size()))
|
||||
return on_error("derive_kdf: ");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::sign(std::vector<uint8_t> & out)
|
||||
{
|
||||
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||
if (!md_ctx)
|
||||
return on_error("EVP_MD_CTX_create: ");
|
||||
if (EVP_DigestSignInit(md_ctx.get(), NULL, ctx->md, NULL, ctx->my_privkey) <= 0)
|
||||
return on_error("EVP_DigestSignInit: ");
|
||||
if (EVP_DigestSignUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
|
||||
return on_error("EVP_DigestSignUpdate: ");
|
||||
size_t siglen = 0;
|
||||
if (EVP_DigestSignFinal(md_ctx.get(), NULL, &siglen) <= 0)
|
||||
return on_error("EVP_DigestSignFinal get length: ");
|
||||
size_t oldsize = out.size();
|
||||
out.resize(oldsize + siglen);
|
||||
if (EVP_DigestSignFinal(md_ctx.get(), out.data() + oldsize, &siglen) <= 0)
|
||||
return on_error("EVP_DigestSignFinal: ");
|
||||
out.resize(oldsize + siglen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::verify(const uint8_t *signature, size_t signature_len)
|
||||
{
|
||||
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||
if (!md_ctx)
|
||||
return on_error("EVP_MD_CTX_create: ");
|
||||
if (EVP_DigestVerifyInit(md_ctx.get(), NULL, ctx->md, NULL, X509_get0_pubkey(peer_cert)) <= 0)
|
||||
return on_error("EVP_DigestVerifyInit: ");
|
||||
if (EVP_DigestVerifyUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
|
||||
return on_error("EVP_DigestVerifyUpdate: ");
|
||||
if (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) <= 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::encrypt(const uint8_t* src, size_t len, uint8_t* dest)
|
||||
{
|
||||
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> enc_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx)
|
||||
return on_error("EVP_CIPHER_CTX_new: ");
|
||||
if (EVP_EncryptInit_ex(enc_ctx.get(), EVP_aes_256_gcm(), NULL, hs_key.data(), hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
|
||||
return on_error("EVP_EncryptInit AES-256-GCM: ");
|
||||
int actual_out;
|
||||
if (EVP_EncryptUpdate(enc_ctx.get(), dest, &actual_out, src, len) <= 0)
|
||||
return on_error("EVP_EncryptUpdate: ");
|
||||
assert(actual_out == len);
|
||||
if (EVP_EncryptFinal_ex(enc_ctx.get(), NULL, &actual_out) <= 0)
|
||||
return on_error("EVP_EncryptFinal: ");
|
||||
if (EVP_CIPHER_CTX_ctrl(enc_ctx.get(), EVP_CTRL_GCM_GET_TAG, 16, dest+len) <= 0)
|
||||
return on_error("EVP_CTRL_GCM_GET_TAG: ");
|
||||
(*(uint64_t*)(hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::decrypt(const uint8_t *src, size_t & len, uint8_t* dest)
|
||||
{
|
||||
if (len <= 16) // only tag?!
|
||||
{
|
||||
len = 0;
|
||||
error = "Handshake decryption failed";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> dec_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx)
|
||||
return on_error("EVP_CIPHER_CTX_new: ");
|
||||
if (EVP_DecryptInit_ex(dec_ctx.get(), EVP_aes_256_gcm(), NULL, peer_hs_key.data(), peer_hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
|
||||
return on_error("EVP_DecryptInit AES-256-GCM: ");
|
||||
int actual_out;
|
||||
len -= 16;
|
||||
if (EVP_DecryptUpdate(dec_ctx.get(), dest, &actual_out, src, len) <= 0)
|
||||
return on_error("EVP_DecryptUpdate: ");
|
||||
assert(actual_out == len);
|
||||
if (EVP_CIPHER_CTX_ctrl(dec_ctx.get(), EVP_CTRL_GCM_SET_TAG, 16, (void*)(src+len)) <= 0)
|
||||
return on_error("EVP_CTRL_GCM_SET_TAG: ");
|
||||
if (EVP_DecryptFinal_ex(dec_ctx.get(), NULL, &actual_out) <= 0)
|
||||
{
|
||||
error = "Handshake decryption failed";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
(*(uint64_t*)(peer_hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::make_client_init()
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
|
||||
if (!key_len)
|
||||
return on_error("EVP_PKEY_get1_encoded_public_key: ");
|
||||
const size_t old_out_size = out_buf_size;
|
||||
out_buf_size += key_len + sizeof(msgr_handshake_hdr_t);
|
||||
out_buf = (uint8_t*)realloc_or_die(out_buf, out_buf_size);
|
||||
uint8_t *buf = out_buf + old_out_size;
|
||||
msgr_handshake_hdr_t *hdr = (msgr_handshake_hdr_t *)buf;
|
||||
hdr->msg_len = key_len + sizeof(msgr_handshake_hdr_t);
|
||||
hdr->magic = MSGR_HS_MAGIC;
|
||||
hdr->type = MSGR_HS_CLIENT_INIT;
|
||||
memcpy(buf + sizeof(msgr_handshake_hdr_t), key, key_len);
|
||||
copy_to(full_handshake, &hdr->type, sizeof(hdr->type));
|
||||
copy_to_with_len(full_handshake, key, key_len);
|
||||
OPENSSL_free(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::make_server_reply()
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
|
||||
if (!key_len)
|
||||
return on_error("EVP_PKEY_get1_encoded_public_key: ");
|
||||
// Append type, key and raw certificate to signed data and sign it
|
||||
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_SERVER_REPLY };
|
||||
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
|
||||
copy_to_with_len(full_handshake, key, key_len);
|
||||
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
std::vector<uint8_t> signature;
|
||||
if (!sign(signature))
|
||||
{
|
||||
OPENSSL_free(key);
|
||||
return false;
|
||||
}
|
||||
// Encrypt certificate and signature
|
||||
std::vector<uint8_t> encrypt_data;
|
||||
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
copy_to_with_len(encrypt_data, signature.data(), signature.size());
|
||||
encrypt_data.resize(encrypt_data.size()+16);
|
||||
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
|
||||
{
|
||||
OPENSSL_free(key);
|
||||
return false;
|
||||
}
|
||||
// Construct message
|
||||
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + 4 + key_len + encrypt_data.size();
|
||||
out_buf = (uint8_t*)realloc_or_die(out_buf, (out_buf_size += hdr.msg_len));
|
||||
uint8_t *cur = out_buf + out_buf_size - hdr.msg_len;
|
||||
copy_to_raw(cur, &hdr, sizeof(hdr));
|
||||
copy_to_raw(cur, &key_len, 4);
|
||||
copy_to_raw(cur, key, key_len);
|
||||
copy_to_raw(cur, encrypt_data.data(), encrypt_data.size());
|
||||
OPENSSL_free(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::make_client_reply()
|
||||
{
|
||||
// Append type and raw certificate to signed data and sign it
|
||||
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_CLIENT_REPLY };
|
||||
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
|
||||
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
std::vector<uint8_t> signature;
|
||||
if (!sign(signature))
|
||||
return false;
|
||||
// Encrypt certificate and signature
|
||||
std::vector<uint8_t> encrypt_data;
|
||||
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
|
||||
copy_to_with_len(encrypt_data, signature.data(), signature.size());
|
||||
encrypt_data.resize(encrypt_data.size()+16);
|
||||
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
|
||||
return false;
|
||||
// Construct message
|
||||
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + encrypt_data.size();
|
||||
out_buf = (uint8_t*)realloc_or_die(out_buf, (out_buf_size += hdr.msg_len));
|
||||
uint8_t *cur = out_buf + out_buf_size - hdr.msg_len;
|
||||
copy_to_raw(cur, &hdr, sizeof(hdr));
|
||||
copy_to_raw(cur, encrypt_data.data(), encrypt_data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len)
|
||||
{
|
||||
BIO *bio = BIO_new_mem_buf(peer_cert_pem, peer_cert_len);
|
||||
if (!bio)
|
||||
return on_error("BIO_new_mem_buf: ");
|
||||
peer_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
|
||||
BIO_free(bio);
|
||||
if (!peer_cert)
|
||||
{
|
||||
error = "Invalid peer certificate";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<X509_STORE_CTX, decltype(&X509_STORE_CTX_free)> ca_ctx(X509_STORE_CTX_new(), X509_STORE_CTX_free);
|
||||
if (!ca_ctx)
|
||||
return on_error("X509_STORE_CTX_new: ");
|
||||
if (X509_STORE_CTX_init(ca_ctx.get(), ctx->ca, peer_cert, NULL) <= 0)
|
||||
return on_error("X509_STORE_CTX_init: ");
|
||||
// Maybe use X509_VERIFY_PARAM_set_auth_level(X509_STORE_CTX_get0_param(ca_ctx.get()), 2) ?
|
||||
X509_STORE_CTX_set_default(ca_ctx.get(), is_server ? "ssl_client" : "ssl_server");
|
||||
if (X509_verify_cert(ca_ctx.get()) <= 0)
|
||||
{
|
||||
error = "Peer certificate verification failed: ";
|
||||
error += X509_verify_cert_error_string(X509_STORE_CTX_get_error(ca_ctx.get()));
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
peer_is_osd = (X509_verify(peer_cert, X509_get0_pubkey(ctx->osd_ca)) > 0);
|
||||
if (!is_server && !peer_is_osd)
|
||||
{
|
||||
error = "Peer is not an OSD";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t msgr_handshake_t::start_msg(uint8_t* src, size_t len, uint32_t expected_type)
|
||||
{
|
||||
size_t orig_len = len;
|
||||
size_t to_buffer = (len < sizeof(msgr_handshake_hdr_t)-in_buf.size()
|
||||
? len : sizeof(msgr_handshake_hdr_t)-in_buf.size());
|
||||
in_buf.insert(in_buf.end(), src, src+to_buffer);
|
||||
len -= to_buffer;
|
||||
src += to_buffer;
|
||||
if (in_buf.size() < sizeof(msgr_handshake_hdr_t))
|
||||
return 0;
|
||||
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
|
||||
if (cur_hdr->magic != MSGR_HS_MAGIC ||
|
||||
cur_hdr->type != expected_type ||
|
||||
cur_hdr->msg_len <= sizeof(msgr_handshake_hdr_t) ||
|
||||
cur_hdr->msg_len >= MSGR_HS_MAX_LEN)
|
||||
{
|
||||
error = "Invalid handshake packet magic, type or size";
|
||||
state = MSGR_HS_ERROR;
|
||||
return -1;
|
||||
}
|
||||
to_buffer = (len < cur_hdr->msg_len-in_buf.size()
|
||||
? len : cur_hdr->msg_len-in_buf.size());
|
||||
in_buf.insert(in_buf.end(), src, src+to_buffer);
|
||||
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
|
||||
len -= to_buffer;
|
||||
src += to_buffer;
|
||||
if (in_buf.size() < cur_hdr->msg_len)
|
||||
return 0;
|
||||
cur_left = cur_hdr->msg_len - sizeof(msgr_handshake_hdr_t);
|
||||
cur_buf = in_buf.data() + sizeof(msgr_handshake_hdr_t);
|
||||
return orig_len - len;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::read_with_len(const uint8_t* & dst, uint32_t & dst_len)
|
||||
{
|
||||
if (cur_left < 4)
|
||||
{
|
||||
error = "Handshake packet too short";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
dst_len = *(uint32_t*)cur_buf;
|
||||
cur_buf += 4;
|
||||
cur_left -= 4;
|
||||
if (cur_left < dst_len)
|
||||
{
|
||||
error = "Handshake packet too short";
|
||||
state = MSGR_HS_ERROR;
|
||||
return false;
|
||||
}
|
||||
dst = cur_buf;
|
||||
cur_buf += dst_len;
|
||||
cur_left -= dst_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::handle_client_init()
|
||||
{
|
||||
// Derive shared secret and handshake keys
|
||||
if (!derive_hs_keys(cur_buf, cur_left))
|
||||
return false;
|
||||
// Add type and key to full_handshake
|
||||
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
|
||||
copy_to_with_len(full_handshake, cur_buf, cur_left);
|
||||
in_buf.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decrypt and check peer certificate
|
||||
bool msgr_handshake_t::handle_peer_cert(const uint8_t *key, uint32_t key_len)
|
||||
{
|
||||
if (!decrypt(cur_buf, cur_left, cur_buf))
|
||||
return false;
|
||||
const uint8_t *peer_cert_pem = NULL;
|
||||
uint32_t peer_cert_len = 0;
|
||||
if (!read_with_len(peer_cert_pem, peer_cert_len))
|
||||
return false;
|
||||
// Parse and verify certificate
|
||||
if (!verify_peer(peer_cert_pem, peer_cert_len))
|
||||
return false;
|
||||
// Verify signature
|
||||
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
|
||||
if (key)
|
||||
copy_to_with_len(full_handshake, key, key_len);
|
||||
copy_to_with_len(full_handshake, peer_cert_pem, peer_cert_len);
|
||||
const uint8_t *signature = NULL;
|
||||
uint32_t signature_len = 0;
|
||||
if (!read_with_len(signature, signature_len))
|
||||
return false;
|
||||
if (!verify(signature, signature_len))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::handle_server_reply()
|
||||
{
|
||||
// Derive shared secret and handshake keys
|
||||
const uint8_t *key = NULL;
|
||||
uint32_t key_len = 0;
|
||||
if (!read_with_len(key, key_len))
|
||||
return false;
|
||||
if (!derive_hs_keys(key, key_len))
|
||||
return false;
|
||||
// Decrypt and check peer certificate
|
||||
if (!handle_peer_cert(key, key_len))
|
||||
return false;
|
||||
in_buf.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::handle_client_reply()
|
||||
{
|
||||
// Decrypt and check peer certificate
|
||||
if (!handle_peer_cert(NULL, 0))
|
||||
return false;
|
||||
in_buf.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void msgr_handshake_t::complete()
|
||||
{
|
||||
state = MSGR_HS_DONE;
|
||||
hs_key.clear();
|
||||
peer_hs_key.clear();
|
||||
full_handshake.clear();
|
||||
}
|
||||
|
||||
ssize_t msgr_handshake_t::handle(uint8_t* in_buf, size_t in_size)
|
||||
{
|
||||
if (state == MSGR_HS_SERVER_INIT)
|
||||
{
|
||||
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_INIT);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return in_size;
|
||||
if (!handle_client_init())
|
||||
return -1;
|
||||
// Send encrypted & signed response
|
||||
if (!make_server_reply())
|
||||
return -1;
|
||||
state = MSGR_HS_SERVER_REPLY;
|
||||
return r;
|
||||
}
|
||||
else if (state == MSGR_HS_CLIENT_INIT)
|
||||
{
|
||||
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_SERVER_REPLY);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return in_size;
|
||||
if (!handle_server_reply())
|
||||
return -1;
|
||||
// Verification passed, send certificate to the server
|
||||
if (!make_client_reply())
|
||||
return -1;
|
||||
// Finished!
|
||||
complete();
|
||||
return r;
|
||||
}
|
||||
else if (state == MSGR_HS_SERVER_REPLY)
|
||||
{
|
||||
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_REPLY);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return in_size;
|
||||
if (!handle_client_reply())
|
||||
return -1;
|
||||
// Verification passed
|
||||
// Finished!
|
||||
complete();
|
||||
return r;
|
||||
}
|
||||
else if (state == MSGR_HS_DONE)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (state != MSGR_HS_ERROR)
|
||||
{
|
||||
error = "Unexpected handshake state: "+std::to_string(state);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool msgr_handshake_t::done()
|
||||
{
|
||||
return (state == MSGR_HS_DONE);
|
||||
}
|
||||
|
||||
uint8_t *msgr_handshake_t::get_out()
|
||||
{
|
||||
return out_buf;
|
||||
}
|
||||
|
||||
size_t msgr_handshake_t::out_size()
|
||||
{
|
||||
return out_buf_size;
|
||||
}
|
||||
|
||||
void msgr_handshake_t::eat_out(size_t n)
|
||||
{
|
||||
if (n >= out_buf_size)
|
||||
{
|
||||
free(out_buf);
|
||||
out_buf = NULL;
|
||||
out_buf_size = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
memmove(out_buf, out_buf + n, out_buf_size - n);
|
||||
out_buf_size -= n;
|
||||
}
|
||||
}
|
||||
|
||||
void msgr_handshake_t::reset_out()
|
||||
{
|
||||
out_buf = NULL;
|
||||
out_buf_size = 0;
|
||||
}
|
||||
|
||||
msgr_handshake_result_t msgr_handshake_t::get_result()
|
||||
{
|
||||
if (state != MSGR_HS_DONE)
|
||||
return msgr_handshake_result_t{};
|
||||
X509_up_ref(peer_cert);
|
||||
return msgr_handshake_result_t{
|
||||
.peer_cert = peer_cert,
|
||||
.peer_is_osd = peer_is_osd,
|
||||
.shared_secret = shared_secret,
|
||||
};
|
||||
}
|
||||
|
||||
std::string msgr_handshake_t::get_error()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/types.h>
|
||||
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
#define AES_256_GCM_MAX_IV_CTR ((uint64_t)1 << 32)
|
||||
|
||||
// TLS 1.3-like handshake
|
||||
|
||||
// 1. Client->server: EC public key
|
||||
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
|
||||
// 3. Client->server: encrypted certificate and digital signature of the handshake
|
||||
|
||||
struct msgr_handshake_result_t
|
||||
{
|
||||
X509 *peer_cert = NULL;
|
||||
bool peer_is_osd = false;
|
||||
std::vector<uint8_t> shared_secret;
|
||||
};
|
||||
|
||||
class msgr_handshake_i
|
||||
{
|
||||
public:
|
||||
// Workflow: create -> init -> handle_msg -> get_result/get_error -> destruct
|
||||
virtual ~msgr_handshake_i() = default;
|
||||
virtual bool init(bool server_mode) = 0;
|
||||
virtual ssize_t handle(uint8_t* in_buf, size_t in_size) = 0;
|
||||
virtual bool done() = 0;
|
||||
virtual uint8_t *get_out() = 0;
|
||||
virtual size_t out_size() = 0;
|
||||
virtual void eat_out(size_t n) = 0;
|
||||
virtual void reset_out() = 0;
|
||||
virtual msgr_handshake_result_t get_result() = 0;
|
||||
virtual std::string get_error() = 0;
|
||||
};
|
||||
|
||||
class msgr_handshake_ctx_i
|
||||
{
|
||||
public:
|
||||
static msgr_handshake_ctx_i* create_ctx();
|
||||
virtual ~msgr_handshake_ctx_i() = default;
|
||||
virtual msgr_handshake_i* create() = 0;
|
||||
virtual bool init(const std::string & pem_cert, const std::string & pem_key,
|
||||
const std::string & pem_osd_ca, const std::string & pem_client_ca) = 0;
|
||||
virtual std::string get_error() = 0;
|
||||
virtual bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) = 0;
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "messenger.h"
|
||||
#include "msgr_iothread.h"
|
||||
|
||||
msgr_iothread_t::msgr_iothread_t():
|
||||
ring(RINGLOOP_DEFAULT_SIZE, true),
|
||||
thread(&msgr_iothread_t::run, this)
|
||||
{
|
||||
eventfd = ring.register_eventfd();
|
||||
if (eventfd < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("failed to register eventfd: ") + strerror(-eventfd));
|
||||
}
|
||||
}
|
||||
|
||||
msgr_iothread_t::~msgr_iothread_t()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_sqe(io_uring_sqe & sqe)
|
||||
{
|
||||
mu.lock();
|
||||
queue.push_back((iothread_sqe_t){ .sqe = sqe, .data = std::move(*(ring_data_t*)sqe.user_data) });
|
||||
if (queue.size() == 1)
|
||||
{
|
||||
cond.notify_all();
|
||||
}
|
||||
mu.unlock();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::stop()
|
||||
{
|
||||
mu.lock();
|
||||
if (stopped)
|
||||
{
|
||||
mu.unlock();
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
if (outer_loop_data)
|
||||
{
|
||||
outer_loop_data->callback = [](ring_data_t*){};
|
||||
}
|
||||
cond.notify_all();
|
||||
close(eventfd);
|
||||
mu.unlock();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_to_ringloop(ring_loop_i *outer_loop)
|
||||
{
|
||||
assert(!this->outer_loop || this->outer_loop == outer_loop);
|
||||
io_uring_sqe *sqe = outer_loop->get_sqe();
|
||||
assert(sqe != NULL);
|
||||
this->outer_loop = outer_loop;
|
||||
this->outer_loop_data = ((ring_data_t*)sqe->user_data);
|
||||
io_uring_prep_poll_add(sqe, eventfd, POLLIN);
|
||||
outer_loop_data->callback = [this](ring_data_t *data)
|
||||
{
|
||||
if (data->res < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("eventfd poll failed: ") + strerror(-data->res));
|
||||
}
|
||||
outer_loop_data = NULL;
|
||||
if (stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
add_to_ringloop(this->outer_loop);
|
||||
ring.loop();
|
||||
};
|
||||
}
|
||||
|
||||
void msgr_iothread_t::run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mu);
|
||||
while (!stopped && !queue.size())
|
||||
cond.wait(lk);
|
||||
if (stopped)
|
||||
return;
|
||||
int i = 0;
|
||||
for (; i < queue.size(); i++)
|
||||
{
|
||||
io_uring_sqe *sqe = ring.get_sqe();
|
||||
if (!sqe)
|
||||
break;
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
*data = std::move(queue[i].data);
|
||||
*sqe = queue[i].sqe;
|
||||
sqe->user_data = (uint64_t)data;
|
||||
}
|
||||
queue.erase(queue.begin(), queue.begin()+i);
|
||||
}
|
||||
// We only want to offload sendmsg/recvmsg. Callbacks will be called in main thread
|
||||
ring.submit();
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_iothreads()
|
||||
{
|
||||
for (int i = 0; i < iothread_count; i++)
|
||||
{
|
||||
auto iot = new msgr_iothread_t();
|
||||
iothreads.push_back(iot);
|
||||
iot->add_to_ringloop(ringloop);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_iothreads()
|
||||
{
|
||||
if (iothreads.size())
|
||||
{
|
||||
for (auto iot: iothreads)
|
||||
{
|
||||
delete iot;
|
||||
}
|
||||
iothreads.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
|
||||
#include "ringloop.h"
|
||||
|
||||
struct iothread_sqe_t
|
||||
{
|
||||
io_uring_sqe sqe;
|
||||
ring_data_t data;
|
||||
};
|
||||
|
||||
class msgr_iothread_t
|
||||
{
|
||||
protected:
|
||||
ring_loop_t ring;
|
||||
ring_loop_i *outer_loop = NULL;
|
||||
ring_data_t *outer_loop_data = NULL;
|
||||
int eventfd = -1;
|
||||
bool stopped = false;
|
||||
std::mutex mu;
|
||||
std::condition_variable cond;
|
||||
std::vector<iothread_sqe_t> queue;
|
||||
std::thread thread;
|
||||
|
||||
void run();
|
||||
public:
|
||||
|
||||
msgr_iothread_t();
|
||||
~msgr_iothread_t();
|
||||
|
||||
void add_sqe(io_uring_sqe & sqe);
|
||||
void stop();
|
||||
void add_to_ringloop(ring_loop_i *outer_loop);
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
osd_op_t::~osd_op_t()
|
||||
{
|
||||
assert(!bs_op);
|
||||
assert(!op_data);
|
||||
if (bitmap_buf)
|
||||
{
|
||||
free(bitmap_buf);
|
||||
@@ -22,10 +23,6 @@ osd_op_t::~osd_op_t()
|
||||
// So we don't reuse it, but free it every time
|
||||
free(buf);
|
||||
}
|
||||
if (op_data)
|
||||
{
|
||||
free(op_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_op_t::is_recovery_related()
|
||||
|
||||
+23
-2
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -16,6 +18,8 @@
|
||||
|
||||
#define OSD_OP_INLINE_BUF_COUNT 16
|
||||
|
||||
#define AES_256_XTS_KEY_SIZE 64
|
||||
|
||||
// Kind of a vector with small-list-optimisation
|
||||
struct osd_op_buf_list_t
|
||||
{
|
||||
@@ -152,11 +156,25 @@ struct blockstore_op_t;
|
||||
|
||||
struct osd_primary_op_data_t;
|
||||
|
||||
struct osd_op_enc_t
|
||||
{
|
||||
// Keys may contain more information in the future, like encryption algorithm and key ID
|
||||
// In this case, key_chain will become inode_key_t* with inode_key_t also being a structure
|
||||
// Currently all keys are required to be 512 bit (64 byte) long, for AES-256-XTS
|
||||
// Raw pointers are convenient for messenger code; external users may use shared_ptr aliasing
|
||||
// to implement complex freeing of osd_op_enc_t along with their external inode cache info
|
||||
uint8_t** key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
uint32_t read_chain_bitmap_pos = 0;
|
||||
uint32_t bitmap_granularity = 0;
|
||||
};
|
||||
|
||||
struct __attribute__((visibility("default"))) osd_op_t
|
||||
{
|
||||
timespec tv_begin = { 0 }, tv_end = { 0 };
|
||||
uint64_t op_type = OSD_OP_IN;
|
||||
uint64_t client_id = 0;
|
||||
osd_num_t osd_num = 0;
|
||||
osd_any_op_t req;
|
||||
osd_any_reply_t reply;
|
||||
blockstore_op_t *bs_op = NULL;
|
||||
@@ -164,9 +182,11 @@ struct __attribute__((visibility("default"))) osd_op_t
|
||||
// bitmap, bitmap_len, bmp_data are only meaningful for reads
|
||||
void *bitmap = NULL;
|
||||
unsigned bitmap_len = 0;
|
||||
unsigned bmp_data = 0;
|
||||
void *bitmap_buf = NULL;
|
||||
size_t bmp_data = 0;
|
||||
uint8_t *bitmap_buf = NULL;
|
||||
void *rmw_buf = NULL;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
uint64_t csum = 0; // network layer checksum
|
||||
osd_primary_op_data_t* op_data = NULL;
|
||||
std::function<void(osd_op_t*)> callback;
|
||||
|
||||
@@ -176,4 +196,5 @@ struct __attribute__((visibility("default"))) osd_op_t
|
||||
void cancel();
|
||||
|
||||
bool is_recovery_related();
|
||||
uint64_t calc_data_checksum();
|
||||
};
|
||||
|
||||
+50
-64
@@ -187,6 +187,8 @@ std::vector<msgr_rdma_context_t*> msgr_rdma_context_t::create_all(const std::vec
|
||||
ibv_device **raw_dev_list = NULL;
|
||||
ibv_device **dev_list = NULL;
|
||||
ibv_device *single_list[2] = {};
|
||||
int up_ports = 0;
|
||||
int single_port_num = 0;
|
||||
|
||||
raw_dev_list = dev_list = ibv_get_device_list(NULL);
|
||||
if (!dev_list || !*dev_list)
|
||||
@@ -221,6 +223,7 @@ std::vector<msgr_rdma_context_t*> msgr_rdma_context_t::create_all(const std::vec
|
||||
dev_list = single_list;
|
||||
}
|
||||
|
||||
retry:
|
||||
for (int i = 0; dev_list[i]; ++i)
|
||||
{
|
||||
auto dev = dev_list[i];
|
||||
@@ -258,6 +261,9 @@ std::vector<msgr_rdma_context_t*> msgr_rdma_context_t::create_all(const std::vec
|
||||
fprintf(stderr, "RDMA device %s port %d GID %d does not exist\n", ibv_get_device_name(dev), port_num, sel_gid_index);
|
||||
continue;
|
||||
}
|
||||
up_ports++;
|
||||
single_port_num = port_num;
|
||||
single_list[0] = dev;
|
||||
uint32_t port_mtu = sel_mtu ? sel_mtu : ibv_mtu_to_bytes(portinfo.active_mtu);
|
||||
#ifdef IBV_ADVISE_MR_ADVICE_PREFETCH_NO_FAULT
|
||||
if (sel_gid_index < 0)
|
||||
@@ -298,6 +304,14 @@ cleanup_dev:
|
||||
ibv_close_device(context);
|
||||
}
|
||||
|
||||
if (!ret.size() && up_ports == 1 && dev_list != single_list)
|
||||
{
|
||||
// Auto-select the only available device/port if there is only one
|
||||
dev_list = single_list;
|
||||
sel_port_num = single_port_num;
|
||||
goto retry;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (raw_dev_list)
|
||||
ibv_free_device_list(raw_dev_list);
|
||||
@@ -554,28 +568,6 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
|
||||
cl->rdma_conn->cur_send++;
|
||||
}
|
||||
|
||||
static int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len)
|
||||
{
|
||||
auto rc = cl->rdma_conn;
|
||||
int total_dst_len = dst_len;
|
||||
while (dst_len > 0 && rc->send_pos < cl->send_list.size())
|
||||
{
|
||||
iovec & iov = cl->send_list[rc->send_pos];
|
||||
uint32_t len = (uint32_t)(iov.iov_len-rc->send_buf_pos < dst_len
|
||||
? iov.iov_len-rc->send_buf_pos : dst_len);
|
||||
memcpy(dst, (uint8_t*)iov.iov_base+rc->send_buf_pos, len);
|
||||
dst += len;
|
||||
dst_len -= len;
|
||||
rc->send_buf_pos += len;
|
||||
if (rc->send_buf_pos >= iov.iov_len)
|
||||
{
|
||||
rc->send_pos++;
|
||||
rc->send_buf_pos = 0;
|
||||
}
|
||||
}
|
||||
return total_dst_len-dst_len;
|
||||
}
|
||||
|
||||
void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
{
|
||||
auto rc = cl->rdma_conn;
|
||||
@@ -598,17 +590,24 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
while (!rc->send_out_full && copied > 0 && rc->cur_send < rc->max_send)
|
||||
{
|
||||
dst = (uint8_t*)rc->send_out.buf + rc->send_out_pos;
|
||||
dst_len = (rc->send_out_pos < rc->send_out_size ? rc->send_out_size-rc->send_out_pos : rc->send_done_pos-rc->send_out_pos);
|
||||
dst_len = (rc->send_out_pos >= rc->send_done_pos
|
||||
? rc->send_out_size-rc->send_out_pos
|
||||
: rc->send_done_pos-rc->send_out_pos);
|
||||
if (dst_len > rc->max_msg)
|
||||
dst_len = rc->max_msg;
|
||||
copied = try_send_rdma_copy(cl, dst, dst_len);
|
||||
copied = copy_ops_to(cl, dst, dst_len);
|
||||
if (cl->io_error)
|
||||
{
|
||||
stop_client(cl->client_id);
|
||||
return;
|
||||
}
|
||||
if (copied > 0)
|
||||
{
|
||||
rc->send_out_pos += copied;
|
||||
if (rc->send_out_pos == rc->send_out_size)
|
||||
rc->send_out_pos = 0;
|
||||
assert(rc->send_out_pos < rc->send_out_size);
|
||||
if (rc->send_out_pos >= rc->send_done_pos)
|
||||
if (rc->send_out_pos == rc->send_done_pos)
|
||||
rc->send_out_full = true;
|
||||
ibv_sge sge = {
|
||||
.addr = (uintptr_t)dst,
|
||||
@@ -617,6 +616,7 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
};
|
||||
try_send_rdma_wr(cl, &sge, 1);
|
||||
rc->send_sizes.push_back(copied);
|
||||
cl->send_free_ops.push_back(NULL); // end marker
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,9 +713,6 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
}
|
||||
if (!is_send)
|
||||
{
|
||||
// Reset OSD ping state - client is obviously alive
|
||||
cl->ping_time_remaining = 0;
|
||||
cl->idle_time_remaining = osd_idle_timeout;
|
||||
rc->cur_recv--;
|
||||
if (!handle_read_buffer(cl, rc->recv_buffers[rc->next_recv_buf], wc[i].byte_len))
|
||||
{
|
||||
@@ -727,54 +724,43 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
else
|
||||
{
|
||||
rc->cur_send--;
|
||||
uint64_t sent_size = rc->send_sizes.at(0);
|
||||
rc->send_sizes.erase(rc->send_sizes.begin(), rc->send_sizes.begin()+1);
|
||||
// byte_len is not filled for send operations
|
||||
uint64_t sent_size = rc->send_sizes.front();
|
||||
rc->send_sizes.pop_front();
|
||||
rc->send_done_pos += sent_size;
|
||||
rc->send_out_full = false;
|
||||
if (rc->send_done_pos == rc->send_out_size)
|
||||
rc->send_done_pos = 0;
|
||||
assert(rc->send_done_pos < rc->send_out_size);
|
||||
int send_pos = 0, send_buf_pos = 0;
|
||||
while (sent_size > 0)
|
||||
while (osd_op_t *op = cl->send_free_ops.front())
|
||||
{
|
||||
if (sent_size >= cl->send_list.at(send_pos).iov_len)
|
||||
{
|
||||
sent_size -= cl->send_list[send_pos].iov_len;
|
||||
send_pos++;
|
||||
}
|
||||
if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
{
|
||||
send_buf_pos = sent_size;
|
||||
sent_size = 0;
|
||||
}
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
cl->send_free_ops.pop_front();
|
||||
}
|
||||
assert(rc->send_pos >= send_pos);
|
||||
if (rc->send_pos == send_pos)
|
||||
cl->send_free_ops.pop_front();
|
||||
if ((cl->proto_csum_status & MSGR_CSUM_NEG) && !cl->write_op && !cl->write_ops.size())
|
||||
{
|
||||
rc->send_buf_pos -= send_buf_pos;
|
||||
}
|
||||
rc->send_pos -= send_pos;
|
||||
for (int i = 0; i < send_pos; i++)
|
||||
{
|
||||
if (cl->outbox[i].flags & MSGR_SENDP_FREE)
|
||||
{
|
||||
// Reply fully sent
|
||||
delete cl->outbox[i].op;
|
||||
}
|
||||
}
|
||||
if (send_pos > 0)
|
||||
{
|
||||
cl->send_list.erase(cl->send_list.begin(), cl->send_list.begin()+send_pos);
|
||||
cl->outbox.erase(cl->outbox.begin(), cl->outbox.begin()+send_pos);
|
||||
}
|
||||
if (send_buf_pos > 0)
|
||||
{
|
||||
cl->send_list[0].iov_base = (uint8_t*)cl->send_list[0].iov_base + send_buf_pos;
|
||||
cl->send_list[0].iov_len -= send_buf_pos;
|
||||
// Checksums negotiated, enable
|
||||
cl->proto_csum_status = cl->proto_csum_status & (~MSGR_CSUM_NEG);
|
||||
}
|
||||
try_send_rdma(cl);
|
||||
}
|
||||
}
|
||||
} while (event_count > 0);
|
||||
handle_immediate_ops();
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn)
|
||||
{
|
||||
if (rdma_conn->cmid)
|
||||
{
|
||||
auto rdma_it = rdmacm_connections.find(rdma_conn->cmid);
|
||||
if (rdma_it != rdmacm_connections.end() && rdma_it->second->rdma_conn == rdma_conn)
|
||||
{
|
||||
rdmacm_connections.erase(rdma_it);
|
||||
}
|
||||
}
|
||||
delete rdma_conn;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
#include <infiniband/verbs.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include "addr_util.h"
|
||||
|
||||
struct osd_op_t;
|
||||
|
||||
struct msgr_rdma_address_t
|
||||
{
|
||||
ibv_gid gid;
|
||||
@@ -72,11 +75,11 @@ struct msgr_rdma_connection_t
|
||||
int cur_send = 0, cur_recv = 0;
|
||||
int send_pos = 0, send_buf_pos = 0;
|
||||
int next_recv_buf = 0;
|
||||
std::vector<void*> recv_buffers;
|
||||
std::vector<uint8_t*> recv_buffers;
|
||||
msgr_rdma_buf_t recv_buf;
|
||||
std::vector<uint64_t> send_sizes;
|
||||
std::deque<uint64_t> send_sizes;
|
||||
msgr_rdma_buf_t send_out;
|
||||
int send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
|
||||
size_t send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
|
||||
bool send_out_full = false;
|
||||
|
||||
~msgr_rdma_connection_t();
|
||||
|
||||
@@ -19,6 +19,7 @@ struct rdmacm_connecting_t
|
||||
int tcp_port = 0;
|
||||
int timeout_ms = 0;
|
||||
int timeout_id = -1;
|
||||
bool is_incoming = false;
|
||||
msgr_rdma_context_t *rdma_context = NULL;
|
||||
};
|
||||
|
||||
@@ -292,6 +293,7 @@ void osd_messenger_t::rdmacm_accept(rdma_cm_event *ev)
|
||||
conn->client_id = next_client_id++;
|
||||
conn->parsed_addr = *(sockaddr_storage*)rdma_get_peer_addr(ev->id);
|
||||
conn->rdma_context = rdma_context;
|
||||
conn->is_incoming = true;
|
||||
rdmacm_set_conn_timeout(conn);
|
||||
rdmacm_connecting[ev->id] = conn;
|
||||
fprintf(stderr, "[OSD %ju] new client %ju: connection from %s via RDMA-CM\n", this->osd_num, conn->client_id,
|
||||
@@ -492,11 +494,13 @@ void osd_messenger_t::rdmacm_established(rdma_cm_event *ev)
|
||||
cl->peer_addr = conn->parsed_addr;
|
||||
cl->peer_port = conn->rdmacm_port;
|
||||
cl->client_id = conn->client_id;
|
||||
cl->is_incoming = conn->is_incoming;
|
||||
cl->peer_state = PEER_RDMA;
|
||||
cl->connect_timeout_id = -1;
|
||||
cl->osd_num = peer_osd;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
cl->rdma_conn = rc;
|
||||
init_tls_client(cl);
|
||||
clients[conn->client_id] = cl;
|
||||
if (conn->timeout_id >= 0)
|
||||
{
|
||||
|
||||
+807
-210
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user