Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
039c524f0a | ||
|
|
4383135434 | ||
|
|
767a61e47c | ||
|
|
5d42881736 | ||
|
|
9d2f6046c6 | ||
|
|
bed4dc11b5 | ||
|
|
4fb7687561 | ||
|
|
1f64e94f2e | ||
|
|
2435209071 | ||
|
|
0eba3e5862 | ||
|
|
1ffea58519 | ||
|
|
cb9ccb8a54 | ||
|
|
184920102c | ||
|
|
d6f1a28096 | ||
|
|
cb392ffdfd | ||
|
|
a21e4f9503 | ||
|
|
a9c12d9858 | ||
|
|
6d40fd86e2 | ||
|
|
a4f47cc67a | ||
|
|
32ab12a880 | ||
|
|
7a40c7f2f6 | ||
|
|
0f001b0e76 | ||
|
|
3551525f5e | ||
|
|
6cc899db84 | ||
|
|
64380856c2 | ||
|
|
2d9167dd57 | ||
|
|
04f624f2be | ||
|
|
8b6d978390 | ||
|
|
08401808e8 | ||
|
|
800744b5c3 | ||
|
|
a1ac51da24 | ||
|
|
64dd98a4d6 | ||
|
|
3ccfa25b47 | ||
|
|
798ae7c393 | ||
|
|
c88bf12484 | ||
|
|
e64d8c8c61 | ||
|
|
ced3cc3de9 | ||
|
|
43c00538b3 | ||
|
|
4cb718de44 | ||
|
|
45da16995e | ||
|
|
54feea5234 | ||
|
|
157191b770 | ||
|
|
c971a32226 | ||
|
|
14dabf4de5 | ||
|
|
5776837c58 | ||
|
|
e21a10940f | ||
|
|
ef3c9a0eb4 | ||
|
|
55e86dbb29 | ||
|
|
cd46bee266 | ||
|
|
a1130598c0 |
@@ -360,78 +360,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_dump_load:
|
||||
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_dump_load.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_dump_load_32k:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=32k OSD_ARGS="--data_csum_type crc32c --csum_block_size 32k" OFFSET_ARGS="$OSD_ARGS" /root/vitastor/tests/test_dump_load.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_dump_load:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: OLD=1 /root/vitastor/tests/test_dump_load.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_dump_load_old_32k:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=old_32k OLD=1 OSD_ARGS="--data_csum_type crc32c --csum_block_size 32k" OFFSET_ARGS="$OSD_ARGS" /root/vitastor/tests/test_dump_load.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_interrupted_rebalance:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -792,24 +720,6 @@ jobs:
|
||||
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
|
||||
@@ -1368,24 +1278,6 @@ 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
|
||||
@@ -1656,24 +1548,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_resize_last:
|
||||
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_resize_last.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_resize_auto:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1710,24 +1584,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_resize_last:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: OLD=1 /root/vitastor/tests/test_resize_last.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_resize_auto:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.15")
|
||||
set(VITASTOR_VERSION "3.0.9")
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
||||
+1
-1
Submodule cpp-btree updated: 431d2e1d35...ebe44c9b66
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
VITASTOR_VERSION ?= v3.0.9
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.15
|
||||
image: vitalif/vitastor-csi:v3.0.9
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.15
|
||||
image: vitalif/vitastor-csi:v3.0.9
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.15"
|
||||
vitastorCSIDriverVersion = "3.0.9"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.15-1) unstable; urgency=medium
|
||||
vitastor (3.0.9-1) unstable; urgency=medium
|
||||
|
||||
* Bugfixes
|
||||
|
||||
|
||||
Vendored
-1
@@ -11,7 +11,6 @@ override_dh_install:
|
||||
cp -v node-binding/package.json node-binding/index.js node-binding/addon.cc node-binding/addon.h node-binding/client.cc node-binding/client.h debian/tmp/usr/lib/x86_64-linux-gnu/nodejs/vitastor
|
||||
cp -v node-binding/build/Release/addon.node debian/tmp/usr/lib/x86_64-linux-gnu/nodejs/vitastor/build/Release
|
||||
dh_install
|
||||
cd debian/vitastor-mon/usr/lib/vitastor/mon && npm install --production
|
||||
|
||||
override_dh_installdeb:
|
||||
cat debian/fio_version >> debian/vitastor-fio.substvars
|
||||
|
||||
Vendored
+6
@@ -37,6 +37,12 @@ rm -rf a b
|
||||
|
||||
echo "dep:fio=$FIO" > debian/fio_version
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL/vitastor-$VER
|
||||
mkdir mon/node_modules
|
||||
cd mon/node_modules
|
||||
curl -s https://git.yourcmc.ru/vitalif/antietcd/archive/master.tar.gz | tar -zx
|
||||
curl -s https://git.yourcmc.ru/vitalif/tinyraft/archive/master.tar.gz | tar -zx
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL
|
||||
if [[ ( "$REL" = "trixie" || "$REL" = "resolute" ) && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
|
||||
# Fucking shit, archives differ between bookworm (xz 5.4.1) and trixie (xz 5.8.1)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
VITASTOR_VERSION ?= v3.0.9
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ 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 --data-dir /data
|
||||
$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'
|
||||
ExecStop=docker stop vitastor-etcd
|
||||
Restart=always
|
||||
StartLimitInterval=0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.15
|
||||
VITASTOR_VERSION=v3.0.9
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
|
||||
ETCD_NAME=""
|
||||
ETCD_IP=""
|
||||
ETCD_INITIAL_CLUSTER=""
|
||||
|
||||
@@ -50,9 +50,6 @@ or antietcd_data_dir options). All other antietcd parameters
|
||||
cluster, cluster_key, persist_filter, stale_read can also be set in
|
||||
Vitastor configuration with `antietcd_` prefix.
|
||||
|
||||
See also: [antietcd_cert](security.en.md#antietcd_cert),
|
||||
[antietcd_key](security.en.md#antietcd_key) and [etcd_proxy](security.en.md#etcd_proxyurls).
|
||||
|
||||
You can dump/load data to or from antietcd using Antietcd `anticli` tool:
|
||||
|
||||
```
|
||||
|
||||
@@ -50,9 +50,6 @@ antietcd_data_file или antietcd_data_dir). Все остальные пара
|
||||
node_id, cluster, cluster_key, persist_filter, stale_read также можно задавать
|
||||
в конфигурации Vitastor с префиксом `antietcd_`.
|
||||
|
||||
Смотрите также настройки [antietcd_cert](security.ru.md#antietcd_cert),
|
||||
[antietcd_key](security.ru.md#antietcd_key) и [etcd_proxy](security.ru.md#etcd_proxyurls).
|
||||
|
||||
Вы можете выгружать/загружать данные в или из antietcd с помощью его инструмента
|
||||
`anticli`:
|
||||
|
||||
|
||||
+30
-186
@@ -10,36 +10,13 @@ These parameters affect your Vitastor installation security and apply to OSDs, m
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
|
||||
All certificate and private key parameters mentioned may contain a path to a PEM file or just
|
||||
a PEM string with certificate or a private key. In the latter case, the string must begin with
|
||||
"-----BEGIN CERTIFICATE-----" or "-----BEGIN PRIVATE KEY-----".
|
||||
|
||||
- [use_perms](#use_perms)
|
||||
- [cert](#cert)
|
||||
- [pkey](#pkey)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [client_ca](#client_ca)
|
||||
- [osd_ca](#osd_ca)
|
||||
- [mon_ca](#mon_ca)
|
||||
- [antietcd_cert](#antietcd_cert)
|
||||
- [antietcd_key](#antietcd_key)
|
||||
- [etcd_proxy.urls](#etcd_proxyurls)
|
||||
- [etcd_proxy.cert](#etcd_proxycert)
|
||||
- [etcd_proxy.key](#etcd_proxykey)
|
||||
- [etcd_proxy.ca](#etcd_proxyca)
|
||||
- [osd_cert](#osd_cert)
|
||||
- [osd_pkey](#osd_pkey)
|
||||
- [api_cert](#api_cert)
|
||||
- [api_pkey](#api_pkey)
|
||||
- [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)
|
||||
- [proto_checksums](#proto_checksums)
|
||||
- [force_proto_checksums](#force_proto_checksums)
|
||||
- [max_cipher_pool_size](#max_cipher_pool_size)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
@@ -48,195 +25,54 @@ a PEM string with certificate or a private key. In the latter case, the string m
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
|
||||
## use_perms
|
||||
|
||||
- Type: boolean
|
||||
- Default: false
|
||||
|
||||
Enable client permissions in a Vitastor cluster, including Antietcd built into the Monitor.
|
||||
Requires configured encryption. Also note that separate Antietcd requires separate configuration
|
||||
to use permissions (see [security documentation](../intro/security.en.md) for details).
|
||||
|
||||
## cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client certificate of the current Vitastor user. Required for Vitastor protocol encryption.
|
||||
Must be signed with [client_ca](#client_ca). Also used as the client certificate for etcd/Antietcd
|
||||
connections by default.
|
||||
|
||||
## pkey
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key of the current Vitastor user.
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify etcd server certificate. Or just the etcd server's
|
||||
certificate itself - it's fine to use it for etcd_ca.
|
||||
|
||||
## client_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vitastor client certificates.
|
||||
Mandatory for Vitastor protocol encryption.
|
||||
|
||||
## osd_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vitastor OSD certificates. Also mandatory for Vitastor protocol
|
||||
encryption. Must be different from client_ca. May be equal to osd_cert - different OSDs
|
||||
don't require separate certificates at the moment because their permissions don't differ.
|
||||
|
||||
## mon_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vitastor Monitor certificates. Used only for separate Antietcd,
|
||||
not required when a monitor built-in Antietcd is used. May be equal to mon_client_etcd_cert.
|
||||
|
||||
## antietcd_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Server TLS certificate for Antietcd built into the Monitor.
|
||||
|
||||
## antietcd_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for antietcd_cert.
|
||||
|
||||
## etcd_proxy.urls
|
||||
|
||||
- Type: string or array of strings
|
||||
|
||||
etcd URLs for Antietcd etcd proxy mode.
|
||||
See [Mon as Etcd proxy](../intro/security.en.md#mon-as-etcd-proxy) for details.
|
||||
|
||||
## etcd_proxy.cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client certificate for Antietcd connections to etcd in proxy mode.
|
||||
|
||||
## etcd_proxy.key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for etcd_proxy.cert.
|
||||
|
||||
## etcd_proxy.ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify etcd server certificate when connecting to it from Antietcd.
|
||||
|
||||
## osd_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Vitastor OSD server certificate. Required for Vitastor protocol encryption. May be equal
|
||||
to [osd_ca](#osd_ca) - all OSDs share the same permission set for now. Also used as the client
|
||||
certificate for connections from OSD to etcd/Antietcd by default.
|
||||
|
||||
## osd_pkey
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for osd_cert.
|
||||
|
||||
## api_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Server TLS certificate for [vitastor-cli serve](../usage/cli.en.md#serve) API server.
|
||||
|
||||
## api_pkey
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for api_cert.
|
||||
- [max_cipher_pool_size](#max_cipher_pool_size)
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for connections from Vitastor clients to etcd/Antietcd if you don't want
|
||||
to use the common client certificate [cert](#cert).
|
||||
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.
|
||||
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
|
||||
|
||||
Client TLS certificate to use for connections from Vitastor OSDs to etcd/Antietcd if you don't want
|
||||
to use the common OSD certificate [osd_cert](#osd_cert).
|
||||
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
|
||||
|
||||
Private key for osd_etcd_client_cert.
|
||||
Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for connections from Vitastor Monitors to etcd/Antietcd - required
|
||||
if you don't use the built-in Antietcd. In case you use it Monitor has direct access to Antietcd data
|
||||
and doesn't require any connection.
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for mon_etcd_client_cert.
|
||||
|
||||
## proto_checksums
|
||||
|
||||
- Type: string
|
||||
- Default: payload
|
||||
|
||||
One of "full", "payload", "gcm", "none":
|
||||
- "full" means calculate and verify transport level checksums from the full message data
|
||||
including the header - recommended for unencrypted setups.
|
||||
- "payload" enables checksums only for the actual read/write data, but skips them for message
|
||||
headers - recommended for encrypted setups because headers are already protected by AES-GCM.
|
||||
- "gcm" disables checksums and enables AES-GCM encryption of the whole messages including headers
|
||||
and data - AES-GCM already includes MAC which is actually a stronger checksum. This option is
|
||||
slower and is only recommended for untrusted networks.
|
||||
- "none" disables transport level checksums at all.
|
||||
|
||||
## force_proto_checksums
|
||||
|
||||
- Type: string
|
||||
|
||||
To allow older clients to connect to a Vitastor cluster with enabled checksums, Vitastor OSDs
|
||||
allow clients to downgrade their proto_checksums by default. force_proto_checksums sets the
|
||||
minimum security level allowed for connecting clients. When encryption is disabled, default
|
||||
force_proto_checksums is none and clients without checksums are allowed. With enabled
|
||||
encryption, force_proto_checksums becomes "payload" by default to block unauthenticated data
|
||||
on the transport level.
|
||||
|
||||
## 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.
|
||||
Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
|
||||
|
||||
## vault_url
|
||||
|
||||
@@ -267,14 +103,14 @@ Vault v1 secret API mount path to use.
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for Vault connections if you don't want to use the common Vitastor
|
||||
client certificate [cert](#cert) which is also used for Vault connections by default.
|
||||
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 the vault_client_cert certificate.
|
||||
Private key for vault_client_cert (also a file or a PEM string).
|
||||
|
||||
## vault_ca
|
||||
|
||||
@@ -304,3 +140,11 @@ Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
|
||||
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.
|
||||
|
||||
+28
-186
@@ -12,36 +12,13 @@ OSD, мониторами и клиентами.
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
|
||||
Все параметры сертификатов и закрытых ключей могут быть путём к файлу или просто
|
||||
строкой с сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----" или "-----BEGIN PRIVATE KEY-----".
|
||||
|
||||
- [use_perms](#use_perms)
|
||||
- [cert](#cert)
|
||||
- [pkey](#pkey)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [client_ca](#client_ca)
|
||||
- [osd_ca](#osd_ca)
|
||||
- [mon_ca](#mon_ca)
|
||||
- [antietcd_cert](#antietcd_cert)
|
||||
- [antietcd_key](#antietcd_key)
|
||||
- [etcd_proxy.urls](#etcd_proxyurls)
|
||||
- [etcd_proxy.cert](#etcd_proxycert)
|
||||
- [etcd_proxy.key](#etcd_proxykey)
|
||||
- [etcd_proxy.ca](#etcd_proxyca)
|
||||
- [osd_cert](#osd_cert)
|
||||
- [osd_pkey](#osd_pkey)
|
||||
- [api_cert](#api_cert)
|
||||
- [api_pkey](#api_pkey)
|
||||
- [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)
|
||||
- [proto_checksums](#proto_checksums)
|
||||
- [force_proto_checksums](#force_proto_checksums)
|
||||
- [max_cipher_pool_size](#max_cipher_pool_size)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
@@ -50,199 +27,56 @@ OSD, мониторами и клиентами.
|
||||
- [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)
|
||||
|
||||
## use_perms
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
- Значение по умолчанию: false
|
||||
|
||||
Включает клиентские привилегии в кластере Vitastor, в том числе во встроенном в мониторе Antietcd.
|
||||
Требует настроенного шифрования протокола. Также обратите внимание, что отдельно установленный Antietcd
|
||||
требует отдельной настройки привилегий (подробности смотрите в [документации безопасности](../intro/security.ru.md)).
|
||||
|
||||
## cert
|
||||
## etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский сертификат текущего пользователя Vitastor. Требуется для шифрования протокола Vitastor.
|
||||
Должен быть подписан [client_ca](#client_ca). Также по умолчанию используется как клиентский
|
||||
сертификат для подключения к etcd/Antietcd и Vault.
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
|
||||
## pkey
|
||||
## etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ текущего пользователя Vitastor.
|
||||
Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Либо же просто сам сертификат сервера etcd - его можно использовать как etcd_ca.
|
||||
|
||||
## client_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификатов клиентов Vitastor.
|
||||
Требуется для шифрования протокола Vitastor.
|
||||
|
||||
## osd_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификатов OSD Vitastor. Также обязателен
|
||||
для шифрования протокола Vitastor. Должен отличаться от client_ca. Может быть равен
|
||||
osd_cert - разные OSD не требуют разных сертификатов, потому что на данный момент
|
||||
привилегии разных OSD никак не отличаются.
|
||||
|
||||
## mon_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификатов мониторов Vitastor. Используется
|
||||
только отдельно установленным Antietcd, не требуется при использовании встроенного в монитор
|
||||
Antietcd. Может быть равен mon_client_etcd_cert.
|
||||
|
||||
## antietcd_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Серверный TLS-сертификат для Antietcd, встроенного в монитор.
|
||||
|
||||
## antietcd_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата antietcd_cert.
|
||||
|
||||
## etcd_proxy.urls
|
||||
|
||||
- Тип: строка или массив строк
|
||||
|
||||
Адреса etcd для режима Antietcd etcd-прокси.
|
||||
Смотрите подробности в разделе [Mon в роли Etcd proxy](../intro/security.ru.md#mon-в-роли-etcd-proxy).
|
||||
|
||||
## etcd_proxy.cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский сертификат для подключений от Antietcd к etcd в режиме прокси.
|
||||
|
||||
## etcd_proxy.key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата etcd_proxy.cert.
|
||||
|
||||
## etcd_proxy.ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный TLS-сертификат для проверки сертификата сервера etcd при подключениях от Antietcd.
|
||||
|
||||
## osd_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Сертификат сервера Vitastor OSD. Требуется для шифрования протокола Vitastor. Может быть равен
|
||||
[osd_ca](#osd_ca) - все OSD на данный момент имеют одинаковые привилегии. Также по умолчанию
|
||||
используется как клиентский сертификат для подключения от OSD к etcd/Antietcd.
|
||||
|
||||
## osd_pkey
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата osd_cert.
|
||||
|
||||
## api_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Серверный TLS-сертификат для API-сервера [vitastor-cli serve](../usage/cli.ru.md#serve).
|
||||
|
||||
## api_pkey
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата api_cert.
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений от клиентов Vitastor к etcd/Antietcd, если вы не хотите
|
||||
использовать общий клиентский сертификат [cert](#cert).
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата etcd_client_cert.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений от Vitastor OSD к etcd/Antietcd, если вы не хотите
|
||||
использовать общий сертификат OSD [osd_cert](#osd_cert).
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата osd_etcd_client_cert.
|
||||
Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений от мониторов Vitastor к etcd/Antietcd - требуется, если
|
||||
вы не используете встроенный в монитор Antietcd. Если вы используете его, то монитор и так имеет
|
||||
прямой доступ к данным Antietcd и не требует никаких соединений.
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата mon_etcd_client_cert.
|
||||
|
||||
## proto_checksums
|
||||
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: payload
|
||||
|
||||
Одно из значений "full", "payload", "gcm" и "none":
|
||||
- "full" означает расчёт и проверку контрольных сумм на транспортном уровне от полных сообщений,
|
||||
включая их заголовки и данные - рекомендуется для кластеров без шифрования.
|
||||
- "payload" включает контрольные суммы только для данных сообщений, но пропускает заголовки -
|
||||
такая настройка рекомендуется для кластеров с включённым шифрованием, потому что в них заголовки
|
||||
и так защищены шифрованием AES-GCM.
|
||||
- "gcm" отключает контрольные суммы и включает шифрование полных сообщений включая заголовки и
|
||||
данные - AES-GCM уже включает в себя MAC, который по сути является криптостойкой контрольной
|
||||
суммой. Такая настройка медленнее и рекомендуется только для недоверенных сетей.
|
||||
- "none" полностью отключает контрольные суммы на транспортном уровне.
|
||||
|
||||
## force_proto_checksums
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Чтобы старые клиенты Vitastor могли подключаться к кластеру с включёнными контрольными
|
||||
суммами, Vitastor OSD по умолчанию разрешают клиентам отключать контрольные суммы
|
||||
данных (proto_checksums). Настройка force_proto_checksums задаёт минимальный уровень
|
||||
безопасности, разрешённый для подключающихся клиентов. Когда шифрование отключено,
|
||||
force_proto_checksums по умолчанию равно none и подключения клиентов без контрольных
|
||||
сумм разрешаются. При включённом шифровании значение по умолчанию force_proto_checksums
|
||||
становится "payload", чтобы блокировать подключения с неаутентифицированными данными.
|
||||
|
||||
## max_cipher_pool_size
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 256
|
||||
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
|
||||
|
||||
## vault_url
|
||||
|
||||
@@ -272,14 +106,14 @@ force_proto_checksums по умолчанию равно none и подключ
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений к Vault на тот случай, если вы не хотите использовать
|
||||
общий сертификат клиента Vitastor [cert](#cert), используемый для подключений к Vault по умолчанию.
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата vault_client_cert.
|
||||
Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
|
||||
|
||||
## vault_ca
|
||||
|
||||
@@ -310,3 +144,11 @@ force_proto_checksums по умолчанию равно none и подключ
|
||||
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
|
||||
## max_cipher_pool_size
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 256
|
||||
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
|
||||
@@ -64,7 +64,7 @@ for (const file of params_files)
|
||||
let out = '\n';
|
||||
for (const c of cfg)
|
||||
{
|
||||
out += `\n- [${c.name}](#${c.name.replace(/\./g, '')})`;
|
||||
out += `\n- [${c.name}](#${c.name})`;
|
||||
}
|
||||
for (const c of cfg)
|
||||
{
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
cluster, cluster_key, persist_filter, stale_read can also be set in
|
||||
Vitastor configuration with `antietcd_` prefix.
|
||||
|
||||
See also: [antietcd_cert](security.en.md#antietcd_cert),
|
||||
[antietcd_key](security.en.md#antietcd_key) and [etcd_proxy](security.en.md#etcd_proxyurls).
|
||||
|
||||
You can dump/load data to or from antietcd using Antietcd `anticli` tool:
|
||||
|
||||
```
|
||||
@@ -50,9 +47,6 @@
|
||||
node_id, cluster, cluster_key, persist_filter, stale_read также можно задавать
|
||||
в конфигурации Vitastor с префиксом `antietcd_`.
|
||||
|
||||
Смотрите также настройки [antietcd_cert](security.ru.md#antietcd_cert),
|
||||
[antietcd_key](security.ru.md#antietcd_key) и [etcd_proxy](security.ru.md#etcd_proxyurls).
|
||||
|
||||
Вы можете выгружать/загружать данные в или из antietcd с помощью его инструмента
|
||||
`anticli`:
|
||||
|
||||
|
||||
@@ -3,7 +3,3 @@
|
||||
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.
|
||||
|
||||
All certificate and private key parameters mentioned may contain a path to a PEM file or just
|
||||
a PEM string with certificate or a private key. In the latter case, the string must begin with
|
||||
"-----BEGIN CERTIFICATE-----" or "-----BEGIN PRIVATE KEY-----".
|
||||
|
||||
@@ -5,7 +5,3 @@ OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
|
||||
Все параметры сертификатов и закрытых ключей могут быть путём к файлу или просто
|
||||
строкой с сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----" или "-----BEGIN PRIVATE KEY-----".
|
||||
|
||||
+45
-190
@@ -1,203 +1,49 @@
|
||||
- name: use_perms
|
||||
type: bool
|
||||
default: false
|
||||
info: |
|
||||
Enable client permissions in a Vitastor cluster, including Antietcd built into the Monitor.
|
||||
Requires configured encryption. Also note that separate Antietcd requires separate configuration
|
||||
to use permissions (see [security documentation](../intro/security.en.md) for details).
|
||||
info_ru: |
|
||||
Включает клиентские привилегии в кластере Vitastor, в том числе во встроенном в мониторе Antietcd.
|
||||
Требует настроенного шифрования протокола. Также обратите внимание, что отдельно установленный Antietcd
|
||||
требует отдельной настройки привилегий (подробности смотрите в [документации безопасности](../intro/security.ru.md)).
|
||||
- name: cert
|
||||
type: string
|
||||
info: |
|
||||
Client certificate of the current Vitastor user. Required for Vitastor protocol encryption.
|
||||
Must be signed with [client_ca](#client_ca). Also used as the client certificate for etcd/Antietcd
|
||||
connections by default.
|
||||
info_ru: |
|
||||
Клиентский сертификат текущего пользователя Vitastor. Требуется для шифрования протокола Vitastor.
|
||||
Должен быть подписан [client_ca](#client_ca). Также по умолчанию используется как клиентский
|
||||
сертификат для подключения к etcd/Antietcd и Vault.
|
||||
- name: pkey
|
||||
type: string
|
||||
info: Private key of the current Vitastor user.
|
||||
info_ru: Закрытый ключ текущего пользователя Vitastor.
|
||||
- name: etcd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify etcd server certificate. Or just the etcd server's
|
||||
certificate itself - it's fine to use it for etcd_ca.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Либо же просто сам сертификат сервера etcd - его можно использовать как etcd_ca.
|
||||
- name: client_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vitastor client certificates.
|
||||
Mandatory for Vitastor protocol encryption.
|
||||
info_ru: |
|
||||
Доверенный TLS-сертификат для проверки сертификатов клиентов Vitastor.
|
||||
Требуется для шифрования протокола Vitastor.
|
||||
- name: osd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vitastor OSD certificates. Also mandatory for Vitastor protocol
|
||||
encryption. Must be different from client_ca. May be equal to osd_cert - different OSDs
|
||||
don't require separate certificates at the moment because their permissions don't differ.
|
||||
info_ru: |
|
||||
Доверенный TLS-сертификат для проверки сертификатов OSD Vitastor. Также обязателен
|
||||
для шифрования протокола Vitastor. Должен отличаться от client_ca. Может быть равен
|
||||
osd_cert - разные OSD не требуют разных сертификатов, потому что на данный момент
|
||||
привилегии разных OSD никак не отличаются.
|
||||
- name: mon_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vitastor Monitor certificates. Used only for separate Antietcd,
|
||||
not required when a monitor built-in Antietcd is used. May be equal to mon_client_etcd_cert.
|
||||
info_ru: |
|
||||
Доверенный TLS-сертификат для проверки сертификатов мониторов Vitastor. Используется
|
||||
только отдельно установленным Antietcd, не требуется при использовании встроенного в монитор
|
||||
Antietcd. Может быть равен mon_client_etcd_cert.
|
||||
- name: antietcd_cert
|
||||
type: string
|
||||
info: Server TLS certificate for Antietcd built into the Monitor.
|
||||
info_ru: Серверный TLS-сертификат для Antietcd, встроенного в монитор.
|
||||
- name: antietcd_key
|
||||
type: string
|
||||
info: Private key for antietcd_cert.
|
||||
info_ru: Закрытый ключ для сертификата antietcd_cert.
|
||||
- name: etcd_proxy.urls
|
||||
type: string or array of strings
|
||||
type_ru: строка или массив строк
|
||||
info: |
|
||||
etcd URLs for Antietcd etcd proxy mode.
|
||||
See [Mon as Etcd proxy](../intro/security.en.md#mon-as-etcd-proxy) for details.
|
||||
info_ru: |
|
||||
Адреса etcd для режима Antietcd etcd-прокси.
|
||||
Смотрите подробности в разделе [Mon в роли Etcd proxy](../intro/security.ru.md#mon-в-роли-etcd-proxy).
|
||||
- name: etcd_proxy.cert
|
||||
type: string
|
||||
info: Client certificate for Antietcd connections to etcd in proxy mode.
|
||||
info_ru: Клиентский сертификат для подключений от Antietcd к etcd в режиме прокси.
|
||||
- name: etcd_proxy.key
|
||||
type: string
|
||||
info: Private key for etcd_proxy.cert.
|
||||
info_ru: Закрытый ключ для сертификата etcd_proxy.cert.
|
||||
- name: etcd_proxy.ca
|
||||
type: string
|
||||
info: Trusted TLS CA to verify etcd server certificate when connecting to it from Antietcd.
|
||||
info_ru: Доверенный TLS-сертификат для проверки сертификата сервера etcd при подключениях от Antietcd.
|
||||
- name: osd_cert
|
||||
type: string
|
||||
info: |
|
||||
Vitastor OSD server certificate. Required for Vitastor protocol encryption. May be equal
|
||||
to [osd_ca](#osd_ca) - all OSDs share the same permission set for now. Also used as the client
|
||||
certificate for connections from OSD to etcd/Antietcd by default.
|
||||
info_ru: |
|
||||
Сертификат сервера Vitastor OSD. Требуется для шифрования протокола Vitastor. Может быть равен
|
||||
[osd_ca](#osd_ca) - все OSD на данный момент имеют одинаковые привилегии. Также по умолчанию
|
||||
используется как клиентский сертификат для подключения от OSD к etcd/Antietcd.
|
||||
- name: osd_pkey
|
||||
type: string
|
||||
info: Private key for osd_cert.
|
||||
info_ru: Закрытый ключ для сертификата osd_cert.
|
||||
- name: api_cert
|
||||
type: string
|
||||
info: Server TLS certificate for [vitastor-cli serve](../usage/cli.en.md#serve) API server.
|
||||
info_ru: Серверный TLS-сертификат для API-сервера [vitastor-cli serve](../usage/cli.ru.md#serve).
|
||||
- name: api_pkey
|
||||
type: string
|
||||
info: Private key for api_cert.
|
||||
info_ru: Закрытый ключ для сертификата api_cert.
|
||||
- name: etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for connections from Vitastor clients to etcd/Antietcd if you don't want
|
||||
to use the common client certificate [cert](#cert).
|
||||
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 сертификат для подключений от клиентов Vitastor к etcd/Antietcd, если вы не хотите
|
||||
использовать общий клиентский сертификат [cert](#cert).
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
- name: etcd_client_key
|
||||
type: string
|
||||
info: Private key for etcd_client_cert.
|
||||
info_ru: Закрытый ключ для сертификата etcd_client_cert.
|
||||
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: |
|
||||
Client TLS certificate to use for connections from Vitastor OSDs to etcd/Antietcd if you don't want
|
||||
to use the common OSD certificate [osd_cert](#osd_cert).
|
||||
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: |
|
||||
Клиентский TLS сертификат для подключений от Vitastor OSD к etcd/Antietcd, если вы не хотите
|
||||
использовать общий сертификат OSD [osd_cert](#osd_cert).
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
- name: osd_etcd_client_key
|
||||
type: string
|
||||
info: Private key for osd_etcd_client_cert.
|
||||
info_ru: Закрытый ключ для сертификата osd_etcd_client_cert.
|
||||
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: |
|
||||
Client TLS certificate to use for connections from Vitastor Monitors to etcd/Antietcd - required
|
||||
if you don't use the built-in Antietcd. In case you use it Monitor has direct access to Antietcd data
|
||||
and doesn't require any connection.
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений от мониторов Vitastor к etcd/Antietcd - требуется, если
|
||||
вы не используете встроенный в монитор Antietcd. Если вы используете его, то монитор и так имеет
|
||||
прямой доступ к данным Antietcd и не требует никаких соединений.
|
||||
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: Private key for mon_etcd_client_cert.
|
||||
info_ru: Закрытый ключ для сертификата mon_etcd_client_cert.
|
||||
- name: proto_checksums
|
||||
type: string
|
||||
default: payload
|
||||
info: |
|
||||
One of "full", "payload", "gcm", "none":
|
||||
- "full" means calculate and verify transport level checksums from the full message data
|
||||
including the header - recommended for unencrypted setups.
|
||||
- "payload" enables checksums only for the actual read/write data, but skips them for message
|
||||
headers - recommended for encrypted setups because headers are already protected by AES-GCM.
|
||||
- "gcm" disables checksums and enables AES-GCM encryption of the whole messages including headers
|
||||
and data - AES-GCM already includes MAC which is actually a stronger checksum. This option is
|
||||
slower and is only recommended for untrusted networks.
|
||||
- "none" disables transport level checksums at all.
|
||||
info_ru: |
|
||||
Одно из значений "full", "payload", "gcm" и "none":
|
||||
- "full" означает расчёт и проверку контрольных сумм на транспортном уровне от полных сообщений,
|
||||
включая их заголовки и данные - рекомендуется для кластеров без шифрования.
|
||||
- "payload" включает контрольные суммы только для данных сообщений, но пропускает заголовки -
|
||||
такая настройка рекомендуется для кластеров с включённым шифрованием, потому что в них заголовки
|
||||
и так защищены шифрованием AES-GCM.
|
||||
- "gcm" отключает контрольные суммы и включает шифрование полных сообщений включая заголовки и
|
||||
данные - AES-GCM уже включает в себя MAC, который по сути является криптостойкой контрольной
|
||||
суммой. Такая настройка медленнее и рекомендуется только для недоверенных сетей.
|
||||
- "none" полностью отключает контрольные суммы на транспортном уровне.
|
||||
- name: force_proto_checksums
|
||||
type: string
|
||||
info: |
|
||||
To allow older clients to connect to a Vitastor cluster with enabled checksums, Vitastor OSDs
|
||||
allow clients to downgrade their proto_checksums by default. force_proto_checksums sets the
|
||||
minimum security level allowed for connecting clients. When encryption is disabled, default
|
||||
force_proto_checksums is none and clients without checksums are allowed. With enabled
|
||||
encryption, force_proto_checksums becomes "payload" by default to block unauthenticated data
|
||||
on the transport level.
|
||||
info_ru: |
|
||||
Чтобы старые клиенты Vitastor могли подключаться к кластеру с включёнными контрольными
|
||||
суммами, Vitastor OSD по умолчанию разрешают клиентам отключать контрольные суммы
|
||||
данных (proto_checksums). Настройка force_proto_checksums задаёт минимальный уровень
|
||||
безопасности, разрешённый для подключающихся клиентов. Когда шифрование отключено,
|
||||
force_proto_checksums по умолчанию равно none и подключения клиентов без контрольных
|
||||
сумм разрешаются. При включённом шифровании значение по умолчанию force_proto_checksums
|
||||
становится "payload", чтобы блокировать подключения с неаутентифицированными данными.
|
||||
- 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, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
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: |
|
||||
@@ -235,15 +81,15 @@
|
||||
- name: vault_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vault connections if you don't want to use the common Vitastor
|
||||
client certificate [cert](#cert) which is also used for Vault connections by default.
|
||||
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 на тот случай, если вы не хотите использовать
|
||||
общий сертификат клиента Vitastor [cert](#cert), используемый для подключений к Vault по умолчанию.
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
- name: vault_client_key
|
||||
type: string
|
||||
info: Private key for the vault_client_cert certificate.
|
||||
info_ru: Закрытый ключ для сертификата vault_client_cert.
|
||||
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: |
|
||||
@@ -274,3 +120,12 @@
|
||||
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.15`
|
||||
`docker pull vitalif/vitastor:v3.0.9`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 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.15`
|
||||
`docker pull vitalif/vitastor:v3.0.9`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- CMake
|
||||
- jerasure, c-ares headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
- tcmalloc (google-perftools-dev)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
- tcmalloc (google-perftools-dev)
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
|
||||
@@ -41,16 +41,12 @@
|
||||
## Configure monitors
|
||||
|
||||
On the monitor hosts:
|
||||
- Create minimal configuration in `/etc/vitastor/vitastor.conf`:
|
||||
- Put identical etcd_address into `/etc/vitastor/vitastor.conf`. Example:
|
||||
```
|
||||
{
|
||||
"etcd_address": ["http://10.200.1.10:2379","http://10.200.1.11:2379","http://10.200.1.12:2379"],
|
||||
"osd_network": "10.200.1.0/24",
|
||||
"use_perms": false
|
||||
"etcd_address": ["10.200.1.10:2379","10.200.1.11:2379","10.200.1.12:2379"]
|
||||
}
|
||||
```
|
||||
- Note that you can enable encryption by using `https://` and `use_perms` option.
|
||||
[Details](security.en.md#quick-setup) about encryption setup with make-etcd.
|
||||
- Create systemd units for etcd by running: `/usr/lib/vitastor/mon/make-etcd`
|
||||
Or, if you installed Vitastor in Docker, run `systemctl start vitastor-host; docker exec vitastor make-etcd`.
|
||||
- Start etcd and monitors: `systemctl enable --now vitastor-etcd vitastor-mon`
|
||||
|
||||
@@ -41,23 +41,25 @@
|
||||
## Настройте мониторы
|
||||
|
||||
На хостах, выделенных под мониторы:
|
||||
- Создайте минимальную конфигурацию в `/etc/vitastor/vitastor.conf`:
|
||||
- Пропишите одинаковые etcd_address в `/etc/vitastor/vitastor.conf`. Например:
|
||||
```
|
||||
{
|
||||
"etcd_address": ["http://10.200.1.10:2379","http://10.200.1.11:2379","http://10.200.1.12:2379"],
|
||||
"osd_network": "10.200.1.0/24",
|
||||
"use_perms": false
|
||||
"etcd_address": ["10.200.1.10:2379","10.200.1.11:2379","10.200.1.12:2379"]
|
||||
}
|
||||
```
|
||||
- Обратите внимание, что с помощью схемы `https://` и опции `use_perms` можно включить шифрование.
|
||||
[Подробно](security.ru.md#быстрая-настройка) о настройке шифрования через make-etcd.
|
||||
- Инициализируйте сервисы etcd, запустив `/usr/lib/vitastor/mon/make-etcd`.\
|
||||
Либо, если вы установили Vitastor в Docker, запустите `systemctl start vitastor-host; docker exec vitastor make-etcd`.
|
||||
- Запустите etcd и мониторы: `systemctl enable --now vitastor-etcd vitastor-mon`
|
||||
|
||||
## Настройте OSD
|
||||
|
||||
- Создайте/скопируйте с узлов с мониторами файл конфигурации `/etc/vitastor/vitastor.conf`.
|
||||
- Пропишите etcd_address и [osd_network](../config/network.ru.md#osd_network) в `/etc/vitastor/vitastor.conf`. Например:
|
||||
```
|
||||
{
|
||||
"etcd_address": ["10.200.1.10:2379","10.200.1.11:2379","10.200.1.12:2379"],
|
||||
"osd_network": "10.200.1.0/24"
|
||||
}
|
||||
```
|
||||
- Инициализуйте OSD:
|
||||
- Только SSD или только HDD: `vitastor-disk prepare /dev/sdXXX [/dev/sdYYY ...]`.
|
||||
Если вы используете десктопные SSD без конденсаторов, добавьте опцию `--disable_data_fsync off`,
|
||||
|
||||
@@ -1,657 +0,0 @@
|
||||
[Documentation](../../README.md#documentation) → Introduction → Security in Vitastor
|
||||
|
||||
-----
|
||||
|
||||
[Читать на русском](security.ru.md)
|
||||
|
||||
# Security in Vitastor
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Quick setup](#quick-setup)
|
||||
- Principles of operation
|
||||
- [etcd transport encryption (TLS)](#etcd-transport-encryption-tls)
|
||||
- [OSD transport encryption (AES-GCM)](#osd-transport-encryption-aes-gcm)
|
||||
- [End-to-end image data encryption (AES-XTS)](#end-to-end-image-data-encryption-aes-xts)
|
||||
- [Certificate-based authentication](#certificate-based-authentication)
|
||||
- [Users and access rights](#users-and-access-rights)
|
||||
- [etcd privileges](#etcd-privileges)
|
||||
- Manual setup
|
||||
- [Configuring OSD transport encryption](#configuring-osd-transport-encryption)
|
||||
- etcd/Antietcd setup options
|
||||
- [Mon with embedded Antietcd](#mon-with-embedded-antietcd)
|
||||
- [Mon as an Etcd proxy](#mon-as-an-etcd-proxy)
|
||||
- [Mon with a separate Antietcd Proxy](#mon-with-a-separate-antietcd-proxy)
|
||||
- [Standalone Antietcd without etcd](#standalone-antietcd-without-etcd)
|
||||
- [Vault/OpenBao setup](#vaultopenbao-setup)
|
||||
- [Vault setup example](#vault-setup-example)
|
||||
- Lists of allowed operations
|
||||
- [etcd data access rights](#etcd-data-access-rights)
|
||||
- [OSD data access rights](#osd-data-access-rights)
|
||||
- [API access rights](#api-access-rights)
|
||||
- [Encryption performance](#encryption-performance)
|
||||
|
||||
## Overview
|
||||
|
||||
Starting from version 3.1.0, Vitastor provides full data protection:
|
||||
control plane protection (etcd), data plane protection (OSDs), and end-to-end data encryption.
|
||||
|
||||
- Control plane protection:
|
||||
- etcd transport encryption (TLS)
|
||||
- Authentication via client TLS (X.509) certificates
|
||||
- Access control of clients to etcd data
|
||||
- Data plane protection:
|
||||
- Full AES-GCM encryption of OSD transport (similar to TLS, but faster)
|
||||
- Alternatively, AES-GCM encryption of just operation headers with data checksums using a secret "salt"
|
||||
- Authentication via client TLS (X.509) certificates
|
||||
- Access control of clients on the OSD side
|
||||
- End-to-end encryption:
|
||||
- Data is encrypted using AES-XTS on the client side, the Vitastor cluster has no access to plaintext data
|
||||
- AES-XTS keys can be stored in etcd or in an external Vault/OpenBao
|
||||
|
||||
All features are optional and disabled in the simplest configuration. By default, only
|
||||
transport-level data checksums ([proto_checksums](../config/security.en.md#proto_checksums)=payload)
|
||||
are enabled for clients that support them (>= 3.1.0). For older clients, connections
|
||||
without data checksums are allowed by default ([force_proto_checksums](../config/security.en.md#force_proto_checksums) is empty).
|
||||
|
||||
For a quick setup, jump to the [Quick setup](#quick-setup) section.
|
||||
|
||||
Descriptions of all security-related parameters can be found [here](../config/security.en.md).
|
||||
|
||||
## Quick setup
|
||||
|
||||
For a quick setup, use the `/usr/lib/vitastor/mon/make-etcd` script:
|
||||
|
||||
1. Log in to the node where the first monitor and etcd will be located.
|
||||
2. Create `/etc/vitastor/vitastor.conf` with minimal parameters: etcd_address,
|
||||
osd_network and, if you want to enable privileges, use_perms (note `https://`
|
||||
in etcd addresses):
|
||||
```
|
||||
{
|
||||
"etcd_address": ["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],
|
||||
"osd_network": "10.0.0.0/24",
|
||||
"use_perms": true
|
||||
}
|
||||
```
|
||||
3. Run `/usr/lib/vitastor/mon/make-etcd` without parameters or with the `--antietcd-only`
|
||||
parameter if you want to initialize the cluster with Antietcd only, without etcd.
|
||||
4. The script will generate all necessary certificates and offer to copy them to the other
|
||||
monitor nodes (agree!).
|
||||
5. Log in to all other monitor nodes and repeat the `/usr/lib/vitastor/mon/make-etcd` call there.
|
||||
6. If you also have nodes with OSDs only (without monitors), run the following command to
|
||||
copy only the required configuration to these nodes:
|
||||
```
|
||||
/usr/lib/vitastor/mon/make-etcd --copy-to-osd osdnode1,osdnode2,...
|
||||
```
|
||||
|
||||
After that, you can proceed with OSD initialization.
|
||||
|
||||
If you want to understand the setup in more detail, read the [Principles of operation](#principles-of-operation)
|
||||
and [Manual setup](#manual-setup) sections below.
|
||||
|
||||
## Principles of operation
|
||||
|
||||
### etcd transport encryption (TLS)
|
||||
|
||||
Possible setups:
|
||||
- Without encryption (http)
|
||||
- With encryption (https)
|
||||
- With encryption and client certificate authentication. Either the same certificate
|
||||
used for authentication on the OSD side (`cert`+`pkey` / `osd_cert`+`osd_pkey`)
|
||||
is used, or a separately specified certificate (`etcd_client_cert`+`etcd_client_key`).
|
||||
|
||||
### OSD transport encryption (AES-GCM)
|
||||
|
||||
Possible setups:
|
||||
- Unencrypted transport without checksums: `proto_checksums=none`.
|
||||
- Unencrypted transport with data checksums: `proto_checksums=payload` (may be omitted,
|
||||
this is the default value). It's allowed to disable checksums on the client side, or
|
||||
use an older client that does not support checksums. If you want to block connections
|
||||
from clients without checksums, use the option `force_proto_checksums=payload`.
|
||||
- Header-only encryption with data checksums: activated when the options
|
||||
`cert`, `pkey`, `osd_ca` are set on the client side and `osd_cert`, `osd_pkey`, `osd_ca`, `client_ca`
|
||||
on the OSD side, with `proto_checksums=payload`. In this mode, disabling checksums on the client
|
||||
side is forbidden by default, i.e. `force_proto_checksums=payload` is used.
|
||||
- Full transport encryption of all traffic: same as the previous option, but with `proto_checksums=gcm`.
|
||||
In this case, clients are by default allowed to downgrade to checksums only, but this
|
||||
can also be forbidden via `force_proto_checksums=gcm`. This is the slowest setup and
|
||||
it's only recommended for insecure (public) networks. In particular, full traffic
|
||||
encryption together with end-to-end AES-XTS image encryption encrypts data twice.
|
||||
|
||||
Encryption uses the AES-256-GCM algorithm and a custom simplified key exchange protocol,
|
||||
fully analogous to TLS 1.3 ECDHE.
|
||||
|
||||
### End-to-end image data encryption (AES-XTS)
|
||||
|
||||
The Vitastor client supports encrypting each image's data with its own key. In this case,
|
||||
data is encrypted by the client before sending it to OSDs and OSDs can't see it in plain.
|
||||
The encryption key can be changed when cloning/creating image snapshots. For example,
|
||||
you can make a base VM image (say, Debian Linux) unencrypted, but have encrypted client VM
|
||||
images inheriting from it.
|
||||
|
||||
Image encryption keys can be stored in etcd or in an external Vault. In the latter case,
|
||||
etcd only stores key IDs and Vitastor cluster can't decrypt the data at all. To use
|
||||
Vault, create an image with the `--enc_key vault:ID` option, specify vault_url and vault_ca
|
||||
options in the configuration, create accounts for all clients in Vault, and grant them access
|
||||
to the required v1 secrets.
|
||||
|
||||
Once again, if AES-XTS is used together with full traffic encryption (`proto_checksums=gcm`),
|
||||
image data is encrypted twice — first with AES-XTS, and then with AES-GCM. Use it only if
|
||||
you are completely paranoid :-).
|
||||
|
||||
### Certificate-based authentication
|
||||
|
||||
When encryption is enabled, Vitastor clients, OSDs, and monitors authenticate via certificates
|
||||
for both etcd (Antietcd) and OSD connections.
|
||||
|
||||
Separate certificates must be used for OSDs and monitors — either self-signed, or signed
|
||||
by separate CAs (`osd_ca` and `mon_ca`). All OSDs can use the same certificate, and all
|
||||
monitors can also use the same certificate, since the privileges of different OSDs or
|
||||
different monitors do not differ (theoretically, one could differentiate OSD certificates
|
||||
by pool, but there has been no need for this so far).
|
||||
|
||||
Also, a monitor certificate may not be needed at all if Antietcd is embedded into the monitor
|
||||
itself. In this case, the monitor already has access to all etcd data directly in memory.
|
||||
|
||||
### Users and access rights
|
||||
|
||||
When transport encryption is disabled, Vitastor operates without access control, i.e.,
|
||||
any cluster client has full access to both the management layer and the data layer. This
|
||||
option is suitable for dedicated trusted storage networks.
|
||||
|
||||
When OSD transport encryption is enabled (at least for headers), you can enable access
|
||||
rights by turning on the `use_perms=true` option. When this option is enabled, each user
|
||||
can perform only the operations that they are permitted, and even OSDs and monitors are
|
||||
also forbidden from performing "unnecessary" operations.
|
||||
|
||||
Each user (or administrator) must have their own certificate signed by a common root
|
||||
certificate for clients (`client_ca`), with a Common Name equal to the user name.
|
||||
Privilege settings are stored in etcd. OSDs and monitors don't need user accounts;
|
||||
they authenticate via separate certificates.
|
||||
|
||||
User privileges are stored in etcd data under the keys `/vitastor/config/user/<name>`.
|
||||
The following is defined per user in this key:
|
||||
- Type:
|
||||
- Client (`type=client` or omitted) — can only read and modify explicitly permitted images.
|
||||
- Administrator (`type=admin`) — can read and modify all images, and also administer the
|
||||
cluster: view overall statistics and status, create and delete OSDs, etc.
|
||||
- List of group names the user is a member of.
|
||||
|
||||
Images have the following properties:
|
||||
- Owner (owner) — the user name that is allowed to both read and modify the image
|
||||
- Owner group (owner_group) — the owner group name
|
||||
- Reader group (reader_group) — the name of the group of users allowed to read the image
|
||||
|
||||
And there is also a property on the pool:
|
||||
- Creator group (creator_group) — the name of the group of users allowed to create images in the pool
|
||||
|
||||
For the list of allowed operations on image data on the OSD side, see the
|
||||
[OSD data access rights](#osd-data-access-rights) section.
|
||||
|
||||
### etcd privileges
|
||||
|
||||
etcd privileges are implemented through Antietcd in all modes of operation.
|
||||
|
||||
Built-in etcd privileges are not supported due to numerous inconveniences:
|
||||
- Certificate-based authentication does not work at all in etcd's REST interface,
|
||||
- Privileges are stored separately from k/v data and cannot participate in transactions,
|
||||
- Only the administrator (root) can change privileges,
|
||||
- There is no support for filtering range read responses by privileges.
|
||||
|
||||
If etcd is used, Antietcd acts as a filtering proxy and can be embedded in the Vitastor
|
||||
monitor or run separately. In this case, etcd must allow incoming connections only from
|
||||
Antietcd, and all other components must connect to Antietcd.
|
||||
|
||||
If Antietcd runs as a part of the Vitastor monitor, it is sufficient to enable the option
|
||||
`use_perms=true` and set the required certificates. If Antietcd is run separately, privileges
|
||||
have to be enabled separately using Antietcd options. For more details on the setup, see
|
||||
the [etcd/Antietcd setup options](#etcdantietcd-setup-options) section.
|
||||
|
||||
For the list of allowed operations with etcd data, see the
|
||||
[etcd data access rights](#etcd-data-access-rights) section.
|
||||
|
||||
## Manual setup
|
||||
|
||||
### Configuring OSD transport encryption
|
||||
|
||||
You need 2 certificates: one for OSDs and one for signing all client certificates.
|
||||
For OSDs, you can use a self-signed certificate (osd_ca.crt) or a separate certificate (osd.crt)
|
||||
signed by a trusted osd_ca.crt certificate. For clients, you must use separate certificates
|
||||
signed by a common trusted (client_ca.crt).
|
||||
|
||||
Add to the Vitastor configuration on OSD servers:
|
||||
- use_perms: true
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- osd_cert: osd_ca.crt
|
||||
- osd_pkey: osd_ca.key
|
||||
|
||||
On the client side:
|
||||
- use_perms: true
|
||||
- cert: client.crt
|
||||
- pkey: client.key
|
||||
|
||||
### etcd/Antietcd setup options
|
||||
|
||||
The following configuration options are available:
|
||||
|
||||
#### Mon with embedded Antietcd
|
||||
|
||||
The simplest option. You need 1 certificate for Antietcd (antietcd.crt), plus root
|
||||
certificates for OSDs and clients.
|
||||
|
||||
Vitastor settings (`/etc/vitastor/vitastor.conf`):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
#### Mon as an Etcd proxy
|
||||
|
||||
If you want to enable privileges, but stay on etcd, you can use etcd proxy mode.
|
||||
|
||||
You will need 2 separate certificates: one for etcd (etcd.crt) and one for antietcd (antietcd.crt).
|
||||
The etcd client port must be different from the standard 2379 — for example, you can pick 2381.
|
||||
OSD and client certificates are also needed.
|
||||
|
||||
Vitastor settings:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- etcd_proxy:
|
||||
```
|
||||
{
|
||||
"urls": [ "http://mon1:2381", ... ], // addresses of your etcd with port 2381
|
||||
"cert": "antietcd.crt",
|
||||
"key": "antietcd.key",
|
||||
"ca": "etcd.crt"
|
||||
}
|
||||
```
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
etcd command-line options:
|
||||
```
|
||||
--advertise-client-urls=https://<ADDRESS>:2381 --listen-client-urls=https://<ADDRESS>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Mon with a separate Antietcd Proxy
|
||||
|
||||
If in addition to the previous option you want to offload Antietcd from the Vitastor monitor's
|
||||
tasks, you can run it separately.
|
||||
|
||||
Similar to the previous option, 2 certificates are needed: one for etcd and one for antietcd,
|
||||
plus separate certificates for clients, OSDs, and monitors will be needed.
|
||||
|
||||
Vitastor settings:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Antietcd command-line options:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt \
|
||||
--etcd_cert antietcd.crt --etcd_key antietcd.key --etcd_ca etcd.crt
|
||||
```
|
||||
|
||||
etcd command-line options (same as in the previous option):
|
||||
```
|
||||
--advertise-client-urls=https://<ADDRESS>:2381 --listen-client-urls=https://<ADDRESS>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Standalone Antietcd without etcd
|
||||
|
||||
Same as the previous option, but etcd and its certificate are not needed:
|
||||
|
||||
Vitastor settings (same as in the previous option):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Antietcd command-line options:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js \
|
||||
--persist_filter vitastor_persist_filter.js \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt
|
||||
```
|
||||
|
||||
### Vault/OpenBao setup
|
||||
|
||||
To use Vault, each client that needs to get image keys from Vault needs a Vault account.
|
||||
Vitastor only supports client certificate-based authentication, so all client certificates
|
||||
(`cert`+`pkey`) must be registered in Vault, and they must be granted access to the
|
||||
corresponding secrets (v1 secrets API is supported).
|
||||
|
||||
The required format of a Vault secret is a single `key` field as a hexadecimal string.
|
||||
The AES-256-XTS algorithm is used, so the key length is 64 bytes, i.e., the string must
|
||||
consist of 128 hexadecimal digits.
|
||||
|
||||
To connect to Vault, set the following settings in Vitastor.conf:
|
||||
- `vault_url` — Vault address (e.g., `https://vault:8200`)
|
||||
- `vault_ca` — Vault's own certificate
|
||||
|
||||
After that, if you create an image (`vitastor-cli create`) with the option `--enc_key vault:<ID>`,
|
||||
Vitastor clients will first contact Vault to obtain a token at `/v1/auth/cert/login`,
|
||||
and then request the actual secret from Vault at `/v1/secret/<ID>`.
|
||||
|
||||
#### Vault setup example
|
||||
|
||||
Step-by-step instructions for setting up a test Vault using OpenBao as an example:
|
||||
|
||||
1. If TLS is not yet configured, generate a self-signed TLS certificate for Vault:
|
||||
```
|
||||
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
|
||||
-new -newkey rsa:4096 -nodes -keyout /etc/openbao/vault.key -out /etc/openbao/vault.crt
|
||||
```
|
||||
Configure it in `/etc/openbao/openbao.hcl`:
|
||||
```
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/etc/openbao/vault.crt"
|
||||
tls_key_file = "/etc/openbao/vault.key"
|
||||
}
|
||||
```
|
||||
And restart OpenBao (`systemctl restart openbao`).
|
||||
2. Copy Vault's TLS certificate for Vitastor:
|
||||
```
|
||||
cp /etc/openbao/vault.crt /etc/vitastor/vault.crt
|
||||
```
|
||||
Transfer it to all client nodes and specify it in `/etc/vitastor/vitastor.conf`:
|
||||
```
|
||||
{
|
||||
...
|
||||
"vault_url": "http://vault:8200",
|
||||
"vault_ca": "/etc/vitastor/vault.crt"
|
||||
}
|
||||
```
|
||||
3. Check Vault status:
|
||||
```
|
||||
bao status -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
4. Initialize Vault in test mode from 1 node (with 1 key share):
|
||||
```
|
||||
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
5. Unseal Vault:
|
||||
```
|
||||
bao operator unseal -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
6. Enable certificate-based authentication:
|
||||
```
|
||||
bao auth enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 cert
|
||||
```
|
||||
7. Enable v1 secrets:
|
||||
```
|
||||
bao secrets enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 -path=secret kv-v1
|
||||
```
|
||||
8. Create a test secret:
|
||||
```
|
||||
bao kv put -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
|
||||
```
|
||||
9. Generate a signed certificate for a Vitastor user (on a machine where you have `client_ca.crt` and `client_ca.key`):
|
||||
```
|
||||
openssl req -subj '/CN=testimg3' -nodes -new -keyout testimg3.key -out testimg3.csr
|
||||
openssl x509 -req -days 3650 -CA client_ca.crt -CAkey client_ca.key -CAcreateserial -in testimg3.csr -out testimg3.crt
|
||||
rm testimg3.csr
|
||||
```
|
||||
10. Create a user in Vault and grant it access to the secret:
|
||||
```
|
||||
cat >testimg3.policy <<EOF
|
||||
path "/secret/vitastor/testimg3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
bao policy write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
|
||||
|
||||
bao write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 \
|
||||
certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
|
||||
```
|
||||
11. Test access to the secret:
|
||||
```
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key \
|
||||
--json '{}' https://vault:8200/v1/auth/cert/login
|
||||
```
|
||||
A token will be printed, substitute it into the following request:
|
||||
```
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key \
|
||||
-H 'X-Vault-Token: <RECEIVED TOKEN>' https://vault:8200/v1/secret/vitastor/testimg3
|
||||
```
|
||||
12. Create an image in Vitastor with the given secret (as an administrator or someone who
|
||||
has the right to create images in your pool):
|
||||
```
|
||||
vitastor-cli create -s 100G --enc_key vault:vitastor/testimg3 --owner testimg3 testimg3
|
||||
```
|
||||
13. Test access to the image as user testimg3:
|
||||
```
|
||||
vitastor-cli --cert testimg3.crt --pkey testimg3.key dd if=/dev/urandom oimg=testimg3 bs=1M count=100
|
||||
```
|
||||
|
||||
## Lists of allowed operations
|
||||
|
||||
### etcd data access rights
|
||||
|
||||
Below, all key names are given without the common prefix `/vitastor`.
|
||||
|
||||
Allowed operations with keys in Antietcd for clients (`type=client`):
|
||||
- Read-only:
|
||||
- Always allowed:
|
||||
- `/config/global`
|
||||
- `/config/node_placement`
|
||||
- `/config/pools`
|
||||
- `/pg/config`
|
||||
- `/osd/state/*`
|
||||
- `/pg/state/*`
|
||||
- `/index/maxid/*`
|
||||
- For images [readable by the user](#users-and-access-rights):
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
- `/inode/stats/*`
|
||||
- Read and write:
|
||||
- For pools in which the user can create images:
|
||||
- `/index/maxid/*`
|
||||
- For images owned by the user:
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
|
||||
Allowed operations with keys in Antietcd for administrators (`type=admin`):
|
||||
- Read:
|
||||
- `/stats`
|
||||
- `/mon/*`
|
||||
- `/pg/*`
|
||||
- `/pgstats/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
- Read and write:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/index/*`
|
||||
- `/pg/history/*`
|
||||
|
||||
Allowed operations with keys in etcd for OSDs:
|
||||
- Read:
|
||||
- `/pg/config`
|
||||
- `/config/*`
|
||||
- Read and write:
|
||||
- `/osd/*`
|
||||
- `/pg/state/*`
|
||||
- `/pg/history/*`
|
||||
- `/pgstats/*`
|
||||
|
||||
Allowed operations with keys in etcd for monitors:
|
||||
- Read:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/pgstats/*`
|
||||
- Read and write:
|
||||
- `/pg/config`
|
||||
- `/stats`
|
||||
- `/history/last_clean_pgs`
|
||||
- `/mon/*`
|
||||
- `/pg/history/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
|
||||
### OSD data access rights
|
||||
|
||||
When the `use_perms` option and encryption are enabled, OSDs authenticate clients via
|
||||
certificates and allow each client only what is allowed by the access control model.
|
||||
|
||||
Client operations:
|
||||
- READ — allowed for images the user has read access to.
|
||||
- WRITE, DELETE, SCRUB — allowed for images the user has write access to.
|
||||
- SYNC — the operation is not tied to an image and is always allowed.
|
||||
- DESCRIBE — the operation is allowed only for administrators (used by the commands
|
||||
`vitastor-cli describe` and `fix`).
|
||||
- PING — the operation is always allowed.
|
||||
- SHOW_CONFIG — the operation is always allowed, however, if the client presents
|
||||
itself as an OSD in it, then it is verified that it uses a certificate signed by `osd_ca`.
|
||||
- SEC_LIST (listing) — allowed for other OSDs and administrators with any parameters,
|
||||
and for regular clients only allowed for requests limited to an image the user has
|
||||
read access to.
|
||||
|
||||
Cluster operations — allowed only for other OSDs:
|
||||
- SEC_READ
|
||||
- SEC_WRITE
|
||||
- SEC_WRITE_STABLE
|
||||
- SEC_SYNC
|
||||
- SEC_STABILIZE
|
||||
- SEC_ROLLBACK
|
||||
- SEC_DELETE
|
||||
- SEC_READ_BMP
|
||||
- SEC_LOCK
|
||||
|
||||
### API access rights
|
||||
|
||||
[vitastor-cli serve](../usage/cli.en.md#serve) also supports client authentication
|
||||
via certificates. Only certificates signed by `client_ca` are accepted. A separate
|
||||
certificate `server_cert` with the key `server_pkey` is used as the server certificate.
|
||||
|
||||
For `vitastor-cli serve` to work correctly, it itself must use a certificate
|
||||
(`cert`+`pkey`) of a user with administrator rights (`type=admin`) to access Vitastor.
|
||||
|
||||
Regular clients, when accessing the API, are only allowed API operations on images
|
||||
available to them either for reading (for reads) or for writing (for modification).
|
||||
All other API calls are allowed only for administrators.
|
||||
|
||||
List of allowed API operations:
|
||||
|
||||
Clients (users with `type=client`) are allowed the following operations:
|
||||
- image/list — for images the user can read.
|
||||
- image/create — for pools in which the user is allowed to create images, or for
|
||||
creating snapshots of images owned by the user.
|
||||
- image/delete, image/flatten, image/modify — for images owned by the user.
|
||||
|
||||
All other operations are allowed only for administrators (`type=admin`).
|
||||
|
||||
## Encryption performance
|
||||
|
||||
You may wonder — how fast is all this wonderful encryption?
|
||||
|
||||
The answer is — it depends heavily on the CPU. On modern processors (with AVX512 with VAES
|
||||
support) it is very fast — AES encryption speed can reach 10-20 GB/s and above. This
|
||||
primarily concerns the CPU of client machines, because end-to-end encryption is performed
|
||||
entirely on the client, and client uses its signle thread for transport encryption too,
|
||||
while there are many OSDs on the server side, and it is easier to add resources there.
|
||||
|
||||
On older processors, the speed is noticeably worse — for example, on a Xeon E5 v4 it is
|
||||
only 3 GB/s.
|
||||
|
||||
You can evaluate the performance of your processors using the `vitastor-cli cpubench` command.
|
||||
|
||||
Example output (💪 AMD EPYC 9575F):
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 209000 iterations in 2001 ms = 104447.78 MB/s
|
||||
xxhash3 4 K block... 37000000 iterations in 2022 ms = 71479.35 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 210000 iterations in 2015 ms = 104218.36 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 26000000 iterations in 2073 ms = 48993.01 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 54000 iterations in 2000 ms = 27000.00 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 11700000 iterations in 2014 ms = 22692.71 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 50000 iterations in 2039 ms = 24521.82 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 12600000 iterations in 2009 ms = 24499.13 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2013 ms = 19870.84 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 10200000 iterations in 2011 ms = 19812.90 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2014 ms = 19860.97 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 8700000 iterations in 2011 ms = 16899.24 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 26000 iterations in 2062 ms = 12609.12 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 6300000 iterations in 2006 ms = 12267.88 MB/s
|
||||
```
|
||||
|
||||
And here is Xeon E5-2680v4:
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 62000 iterations in 2021 ms = 30677.88 MB/s
|
||||
xxhash3 4 K block... 12400000 iterations in 2006 ms = 24146.31 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 62000 iterations in 2027 ms = 30587.07 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 6800000 iterations in 2011 ms = 13208.60 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 1500000 iterations in 2102 ms = 2787.52 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 1600000 iterations in 2088 ms = 2993.30 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2188 ms = 2742.23 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 1400000 iterations in 2053 ms = 2663.78 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2190 ms = 2739.73 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 1300000 iterations in 2101 ms = 2417.00 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 4000 iterations in 2666 ms = 1500.38 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 800000 iterations in 2113 ms = 1478.94 MB/s
|
||||
```
|
||||
@@ -1,662 +0,0 @@
|
||||
[Документация](../../README-ru.md#документация) → Введение → Безопасность в Vitastor
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Безопасность в Vitastor
|
||||
|
||||
- [Обзор](#обзор)
|
||||
- [Быстрая настройка](#быстрая-настройка)
|
||||
- Принципы работы
|
||||
- [Шифрование соединений с etcd (TLS)](#шифрование-соединений-с-etcd-tls)
|
||||
- [Шифрование соединений с OSD (AES-GCM)](#шифрование-соединений-с-osd-aes-gcm)
|
||||
- [Сквозное шифрование данных образов (AES-XTS)](#сквозное-шифрование-данных-образов-aes-xts)
|
||||
- [Аутентификация по сертификатам](#аутентификация-по-сертификатам)
|
||||
- [Пользователи и права доступа](#пользователи-и-права-доступа)
|
||||
- [Привилегии etcd](#привилегии-etcd)
|
||||
- Ручная настройка
|
||||
- [Настройка шифрования соединений OSD](#настройка-шифрования-соединений-osd)
|
||||
- Варианты настройки etcd/Antietcd
|
||||
- [Mon со встроенным Antietcd](#mon-со-встроенным-antietcd)
|
||||
- [Mon в роли Etcd proxy](#mon-в-роли-etcd-proxy)
|
||||
- [Mon с отдельным Antietcd Proxy](#mon-с-отдельным-antietcd-proxy)
|
||||
- [Отдельный Antietcd без etcd](#отдельный-antietcd-без-etcd)
|
||||
- [Настройка Vault/OpenBao](#настройка-vaultopenbao)
|
||||
- [Пример настройки Vault](#пример-настройки-vault)
|
||||
- Списки разрешённых операций
|
||||
- [Права доступа к данным etcd](#права-доступа-к-данным-etcd)
|
||||
- [Права доступа к данным OSD](#права-доступа-к-данным-osd)
|
||||
- [Права доступа к API](#права-доступа-к-api)
|
||||
- [Производительность шифрования](#производительность-шифрования)
|
||||
|
||||
## Обзор
|
||||
|
||||
Начиная с версии 3.1.0, Vitastor предоставляет полную защиту данных: защиту слоя
|
||||
управления (etcd), защиту слоя данных (OSD) и сквозное шифрование данных.
|
||||
|
||||
- Защита слоя управления:
|
||||
- Шифрование соединений с etcd (TLS)
|
||||
- Аутентификация по клиентским TLS (X.509) сертификатам
|
||||
- Разграничение прав доступа клиентов к данным etcd
|
||||
- Защита слоя данных:
|
||||
- Либо полное AES-GCM шифрование соединений с OSD (аналогично TLS, но быстрее)
|
||||
- Либо шифрование AES-GCM только заголовков команд с контрольными суммами данных с секретной "солью"
|
||||
- Аутентификация по клиентским TLS (X.509) сертификатам
|
||||
- Разграничение прав доступа клиентов на стороне OSD
|
||||
- Сквозное шифрование:
|
||||
- Данные шифруются AES-XTS на стороне клиента, кластер Vitastor не имеет доступа к открытым данным
|
||||
- Ключи AES-XTS могут храниться в etcd или во внешнем Vault/OpenBao
|
||||
|
||||
Все функции опциональны и в простейшем варианте настройки выключены. По умолчанию включены
|
||||
только контрольные суммы данных на транспортном уровне ([proto_checksums](../config/security.ru.md#proto_checksums)=payload) для
|
||||
поддерживающих их клиентов (>= 3.1.0). Для более старых клиентов по умолчанию разрешены
|
||||
соединения без контрольных сумм данных ([force_proto_checksums](../config/security.ru.md#force_proto_checksums) пусто).
|
||||
|
||||
Для быстрой настройки перейдите к разделу [Быстрая настройка](#быстрая-настройка).
|
||||
|
||||
Описания всех параметров, связанных с безопасностью, читайте [здесь](../config/security.ru.md).
|
||||
|
||||
## Быстрая настройка
|
||||
|
||||
Для быстрой настройки используйте скрипт `/usr/lib/vitastor/mon/make-etcd`:
|
||||
|
||||
1. Зайдите на узел, на котором будет располагаться первый монитор и etcd.
|
||||
2. Создайте там минимальный `/etc/vitastor/vitastor.conf` с параметрами etcd_address,
|
||||
osd_network и, если хотите включить привилегии - use_perms (обратите внимание на `https://`
|
||||
в адресах etcd):
|
||||
```
|
||||
{
|
||||
"etcd_address": ["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],
|
||||
"osd_network": "10.0.0.0/24",
|
||||
"use_perms": true
|
||||
}
|
||||
```
|
||||
3. Запустите `/usr/lib/vitastor/mon/make-etcd` без параметров или с параметром `--antietcd-only`,
|
||||
если хотите инициализировать кластер только с Antietcd без etcd.
|
||||
4. Скрипт сгенерирует все необходимые сертификаты и предложит скопировать их на остальные узлы
|
||||
мониторов (соглашайтесь!).
|
||||
5. Зайдите на все остальные узлы мониторов и повторите там вызов `/usr/lib/vitastor/mon/make-etcd`.
|
||||
6. Если у вас будут узлы только с OSD без мониторов, выполните следующую команду, чтобы скопировать
|
||||
только нужную конфигурацию на эти узлы:
|
||||
```
|
||||
/usr/lib/vitastor/mon/make-etcd --copy-to-osd osdnode1,osdnode2,...
|
||||
```
|
||||
|
||||
После этого можете переходить к инициализации OSD.
|
||||
|
||||
Если хотите разобраться в настройке подробнее, читайте далее разделы [Принципы работы](#принципы-работы)
|
||||
и [Ручная настройка](#ручная-настройка).
|
||||
|
||||
## Принципы работы
|
||||
|
||||
### Шифрование соединений с etcd (TLS)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования (http)
|
||||
- С шифрованием (https)
|
||||
- С шифрованием и аутентификацией по клиентским сертификатам. Используется либо тот
|
||||
же сертификат, что используется для аутентификации на стороне OSD (`cert`+`pkey` / `osd_cert`+`osd_pkey`),
|
||||
либо отдельно указанный сертификат (`etcd_client_cert`+`etcd_client_key`)
|
||||
|
||||
### Шифрование соединений с 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 и дать им доступ
|
||||
к требуемым секретам v1.
|
||||
|
||||
Ещё раз повторимся, что если AES-XTS используется с полным шифрованием трафика (`proto_checksums=gcm`),
|
||||
то данные образов шифруются дважды - сначала AES-XTS, а потом AES-GCM. Можете использовать,
|
||||
только если вы совсем параноик :-).
|
||||
|
||||
### Аутентификация по сертификатам
|
||||
|
||||
При включённом шифровании клиенты, OSD и мониторы Vitastor аутентифицируются по сертификатам
|
||||
как при соединениях с etcd (Antietcd), так и с OSD.
|
||||
|
||||
Для OSD и мониторов должны использоваться отдельные сертификаты - либо самоподписанные, либо
|
||||
подписанные отдельными CA (`osd_ca` и `mon_ca`). При этом все OSD могут использовать один и
|
||||
тот же сертификат и все мониторы тоже могут использовать один и тот же сертификат, так как
|
||||
привилегии разных OSD или разных мониторов ничем не отличаются (теоретически можно было бы
|
||||
сделать разграничение сертификатов OSD по пулам, но пока что такой необходимости не было).
|
||||
|
||||
Также сертификат монитора может быть вообще не нужен, если Antietcd встраивается в сам монитор.
|
||||
В этом случае монитор и так имеет доступ ко всем данным etcd прямо в памяти.
|
||||
|
||||
### Пользователи и права доступа
|
||||
|
||||
При отключённом шифровании трафика Vitastor работает без разграничения прав доступа, то есть,
|
||||
любой клиент кластера имеет полный доступ как к слою управлению, так и к слою данных. Такой
|
||||
вариант подходит для выделенных доверенных сетей хранения.
|
||||
|
||||
При включённом шифровании трафика OSD (хотя бы заголовков) есть возможность задействовать
|
||||
права доступа, включив опцию `use_perms=true`. При включённой опции каждый пользователь может
|
||||
выполнять только те операции, которые ему разрешены, и даже OSD и мониторам также запрещены
|
||||
"лишние" операции.
|
||||
|
||||
Каждый пользователь (или администратор) должен иметь свой сертификат, подписанный общим
|
||||
корневым сертификатом для клиентов (`client_ca`), с Common Name, равным имени пользователя.
|
||||
Настройки привилегий же хранятся в etcd. Для OSD и мониторов учётные записи не нужны,
|
||||
они аутентифицируются по отдельным сертификатам.
|
||||
|
||||
Привилегии пользователей хранятся в данных etcd в ключах `/vitastor/config/user/<имя>`.
|
||||
В этом ключе для каждого пользователя задаётся:
|
||||
- Тип:
|
||||
- Клиент (`type=client` или не указано) - может читать и модифицировать только явным образом
|
||||
разрешённые образы.
|
||||
- Администратор (`type=admin`) - может читать и модифицировать все образы, а также администрировать
|
||||
кластер: смотреть общую статистику и состояние, создавать и удалять OSD и так далее.
|
||||
- Список имён групп, членом которых пользователь является.
|
||||
|
||||
У образов есть следующие свойства:
|
||||
- Владелец (owner) - имя пользователя, которому разрешено и читать, и менять образ
|
||||
- Группа владельцев (owner_group) - имя группы владельцев
|
||||
- Группа читателей (reader_group) - имя группы пользователей, которым разрешено читать образ
|
||||
|
||||
И также есть свойство у пула:
|
||||
- Группа создателей (creator_group) - имя группы пользователей, которым разрешено создавать образы в пуле
|
||||
|
||||
Перечень разрешённых операций с данными образов на стороне OSD смотрите в разделе
|
||||
[Права доступа к данным OSD](#права-доступа-к-данным-osd).
|
||||
|
||||
### Привилегии etcd
|
||||
|
||||
Привилегии etcd реализуются через Antietcd во всех режимах работы.
|
||||
|
||||
Встроенные привилегии etcd не поддерживаются по причине их многочисленных неудобств:
|
||||
- Аутентификация по сертификатам вообще не работает в REST интерфейсе etcd,
|
||||
- Привилегии хранятся отдельно от k/v данных и не могут участвовать в транзакциях,
|
||||
- Менять привилегии может только администратор (root),
|
||||
- Нет поддержки фильтрации диапазонных ответов чтения по привилегиям.
|
||||
|
||||
Если используется etcd, то Antietcd выступает в роли фильтрующего прокси, при этом он
|
||||
может быть встроен в монитор Vitastor или запущен отдельно. В этом случае etcd должен
|
||||
разрешать входящие подключения только от Antietcd, а все остальные компоненты должны
|
||||
соединяться с Antietcd.
|
||||
|
||||
Если Antietcd запускается в составе монитора Vitastor, то достаточно включить опцию
|
||||
`use_perms=true` и задать нужные сертификаты. Если Antietcd запускается отдельно, то
|
||||
привилегии нужно включать отдельно опциями Antietcd. Подробнее о настройке смотрите
|
||||
раздел [Варианты настройки etcd/Antietcd](#варианты-настройки-etcdantietcd).
|
||||
|
||||
Перечень разрешённых операций с данными etcd смотрите в разделе
|
||||
[Права доступа к данным etcd](#права-доступа-к-данным-etcd).
|
||||
|
||||
## Ручная настройка
|
||||
|
||||
### Настройка шифрования соединений OSD
|
||||
|
||||
Вам нужно 2 сертификата: один для OSD и один для подписи сертификатов всех клиентов.
|
||||
Для OSD можно использовать самоподписанный сертификат (osd_ca.crt) или отдельный сертификат (osd.crt),
|
||||
подписанный доверенным сертификатом osd_ca.crt. Для клиентов нужно использовать отдельные
|
||||
сертификаты, подписанные общим доверенным (client_ca.crt).
|
||||
|
||||
В конфигурацию Vitastor на серверах OSD нужно добавить:
|
||||
- use_perms: true
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- osd_cert: osd_ca.crt
|
||||
- osd_pkey: osd_ca.key
|
||||
|
||||
На стороне клиентов:
|
||||
- use_perms: true
|
||||
- cert: client.crt
|
||||
- pkey: client.key
|
||||
|
||||
### Варианты настройки etcd/Antietcd
|
||||
|
||||
Доступны следующие варианты настройки:
|
||||
|
||||
#### Mon со встроенным Antietcd
|
||||
|
||||
Самый простой вариант. Вам нужен 1 сертификат для Antietcd (antietcd.crt), плюс
|
||||
корневые сертификаты для OSD и клиентов.
|
||||
|
||||
Настройки Vitastor (`/etc/vitastor/vitastor.conf`):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
#### Mon в роли Etcd proxy
|
||||
|
||||
Если вы хотите включить привилегии, но остаться на etcd, можно задействовать режим etcd proxy.
|
||||
|
||||
Вам понадобится 2 отдельных сертификата: один для etcd (etcd.crt) и один для antietcd (antietcd.crt).
|
||||
Клиентский порт etcd должен отличаться от стандартного 2379, например, можно выбрать 2381.
|
||||
Также нужны сертификаты OSD и клиентов.
|
||||
|
||||
Настройки Vitastor:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: true
|
||||
- etcd_proxy:
|
||||
```
|
||||
{
|
||||
"urls": [ "http://mon1:2381", ... ], // адреса ваших etcd с портом 2381
|
||||
"cert": "antietcd.crt",
|
||||
"key": "antietcd.key",
|
||||
"ca": "etcd.crt"
|
||||
}
|
||||
```
|
||||
- antietcd_cert: antietcd.crt
|
||||
- antietcd_key: antietcd.key
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
|
||||
Опции командной строки etcd:
|
||||
```
|
||||
--advertise-client-urls=https://<АДРЕС>:2381 --listen-client-urls=https://<АДРЕС>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Mon с отдельным Antietcd Proxy
|
||||
|
||||
Если в дополнение к предыдущему варианту вы хотите разгрузить Antietcd от задач монитора Vitastor,
|
||||
можно запустить его отдельно.
|
||||
|
||||
Аналогично предыдущему варианту нужно 2 сертификата: один для etcd и один для antietcd, плюс понадобятся
|
||||
отдельные сертификаты для клиентов, OSD и монитора.
|
||||
|
||||
Настройки Vitastor:
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Опции командной строки Antietcd:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt \
|
||||
--etcd_cert antietcd.crt --etcd_key antietcd.key --etcd_ca etcd.crt
|
||||
```
|
||||
|
||||
Опции командной строки etcd (не отличаются от предыдущего варианта):
|
||||
```
|
||||
--advertise-client-urls=https://<АДРЕС>:2381 --listen-client-urls=https://<АДРЕС>:2381 \
|
||||
--client-cert-auth --cert-file=etcd.crt --key-file=etcd.key --trusted-ca-file=antietcd.crt \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.crt --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.crt
|
||||
```
|
||||
|
||||
#### Отдельный Antietcd без etcd
|
||||
|
||||
Аналогично предыдущему варианту, но etcd и его сертификат не нужны:
|
||||
|
||||
Настройки Vitastor (не отличаются от предыдущего варианта):
|
||||
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
|
||||
- use_perms: true
|
||||
- use_antietcd: false
|
||||
- etcd_ca: antietcd.crt
|
||||
- osd_ca: osd_ca.crt
|
||||
- client_ca: client_ca.crt
|
||||
- mon_etcd_client_cert: mon_ca.crt
|
||||
- mon_etcd_client_key: mon_ca.key
|
||||
|
||||
Опции командной строки Antietcd:
|
||||
```
|
||||
--port 2379 \
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js \
|
||||
--persist_filter vitastor_persist_filter.js \
|
||||
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt
|
||||
```
|
||||
|
||||
### Настройка Vault/OpenBao
|
||||
|
||||
Для использования Vault каждому клиенту, который будет получать из Vault ключи
|
||||
образов, нужна учётная запись в Vault. Vitastor поддерживает только аутентификацию
|
||||
по клиентским сертификатам, так что все сертификаты клиентов (`cert`+`pkey`) должны
|
||||
быть зарегистрированы в Vault и им должен быть дан доступ к соответствующим секретам
|
||||
(поддерживается API секретов v1).
|
||||
|
||||
Требуемый формат секрета Vault - одно поле `key` в формате шестнадцатеричной строки.
|
||||
Используется алгоритм AES-256-XTS, так что длина ключа - 64 байта, то есть строка
|
||||
должна состоять из 128 шестнадцатеричных цифр.
|
||||
|
||||
Для подключения Vault включите следующие настройки в Vitastor.conf:
|
||||
- `vault_url` - адрес Vault (например, `https://vault:8200`)
|
||||
- `vault_ca` - сертификат самого Vault
|
||||
|
||||
После этого, если создать образ (`vitastor-cli create`) с опцией `--enc_key vault:<ID>`,
|
||||
то для получения ключа клиенты Vitastor сначала обратятся к Vault для получения токена
|
||||
по адресу `/v1/auth/cert/login`, а потом запросят из Vault сам секрет по адресу `/v1/secret/<ID>`.
|
||||
|
||||
#### Пример настройки Vault
|
||||
|
||||
Пошаговая инструкция для настройки тестового Vault на примере OpenBao:
|
||||
|
||||
1. Если ещё не настроен TLS, генерируем самоподписанный TLS сертификат для Vault:
|
||||
```
|
||||
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
|
||||
-new -newkey rsa:4096 -nodes -keyout /etc/openbao/vault.key -out /etc/openbao/vault.crt
|
||||
```
|
||||
Настраиваем его в `/etc/openbao/openbao.hcl`:
|
||||
```
|
||||
listener "tcp" {
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/etc/openbao/vault.crt"
|
||||
tls_key_file = "/etc/openbao/vault.key"
|
||||
}
|
||||
```
|
||||
И перезапускаем OpenBao (`systemctl restart openbao`).
|
||||
2. Копируем TLS сертификат Vault для Vitastor:
|
||||
```
|
||||
cp /etc/openbao/vault.crt /etc/vitastor/vault.crt
|
||||
```
|
||||
Переносим его на все клиентские ноды и прописываем в `/etc/vitastor/vitastor.conf`:
|
||||
```
|
||||
{
|
||||
...
|
||||
"vault_url": "http://vault:8200",
|
||||
"vault_ca": "/etc/vitastor/vault.crt"
|
||||
}
|
||||
```
|
||||
3. Проверяем статус Vault:
|
||||
```
|
||||
bao status -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
4. Инициализируем Vault в тестовом режиме из 1 ноды (с 1 частью ключа):
|
||||
```
|
||||
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
5. Разблокируем Vault:
|
||||
```
|
||||
bao operator unseal -ca-cert /etc/openbao/vault.crt -address=https://vault:8200
|
||||
```
|
||||
6. Включаем аутентификацию по сертификатам:
|
||||
```
|
||||
bao auth enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 cert
|
||||
```
|
||||
7. Включаем секреты v1:
|
||||
```
|
||||
bao secrets enable -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 -path=secret kv-v1
|
||||
```
|
||||
8. Создаём тестовый секрет:
|
||||
```
|
||||
bao kv put -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
|
||||
```
|
||||
9. Генерируем подписанный сертификат для пользователя Vitastor (там, где у вас есть `client_ca.crt` и `client_ca.key`):
|
||||
```
|
||||
openssl req -subj '/CN=testimg3' -nodes -new -keyout testimg3.key -out testimg3.csr
|
||||
openssl x509 -req -days 3650 -CA client_ca.crt -CAkey client_ca.key -CAcreateserial -in testimg3.csr -out testimg3.crt
|
||||
rm testimg3.csr
|
||||
```
|
||||
10. Создаём пользователя в Vault и даём ему доступ к секрету:
|
||||
```
|
||||
cat >testimg3.policy <<EOF
|
||||
path "/secret/vitastor/testimg3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
bao policy write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
|
||||
|
||||
bao write -ca-cert /etc/openbao/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 \
|
||||
certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
|
||||
```
|
||||
11. Тестируем доступ к секрету:
|
||||
```
|
||||
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: <ПОЛУЧЕННЫЙ ТОКЕН>' https://vault:8200/v1/secret/vitastor/testimg3
|
||||
```
|
||||
12. Создаём образ в Vitastor с заданным секретом (от имени администратора или того, кто имеет
|
||||
право создавать образы в вашем пуле):
|
||||
```
|
||||
vitastor-cli create -s 100G --enc_key vault:vitastor/testimg3 --owner testimg3 testimg3
|
||||
```
|
||||
13. Тестируем доступ к образу от имени пользователя testimg3:
|
||||
```
|
||||
vitastor-cli --cert testimg3.crt --pkey testimg3.key dd if=/dev/urandom oimg=testimg3 bs=1M count=100
|
||||
```
|
||||
|
||||
## Списки разрешённых операций
|
||||
|
||||
### Права доступа к данным 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
|
||||
|
||||
При включённой опции `use_perms` и шифровании 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
|
||||
|
||||
[vitastor-cli serve](../usage/cli.ru.md#serve) также поддерживает клиентскую
|
||||
аутентификацию по сертификатам. Принимаются только сертификаты, подписанные
|
||||
`client_ca`. В качестве серверного сертификата используется отдельный сертификат
|
||||
`server_cert` с ключом `server_pkey`.
|
||||
|
||||
При этом для корректной работы `vitastor-cli serve` он сам должен использовать
|
||||
для доступа в Vitastor сертификат (`cert`+`pkey`) пользователя с правами
|
||||
администратора (`type=admin`).
|
||||
|
||||
Обычным клиентам при доступе к API разрешаются только API-операции с образами,
|
||||
доступными им либо на чтение (для чтения), либо на запись (для модификации).
|
||||
Все остальные API-вызовы разрешаются только для администраторов.
|
||||
|
||||
Список разрешённых операций API:
|
||||
|
||||
Клиентам (пользователям с `type=client`) разрешаются операции:
|
||||
- image/list - для образов, которые пользователь может читать.
|
||||
- image/create - для пулов, в которых пользователю разрешено создавать образы, либо
|
||||
для создания снимков образов, которыми пользователь владеет.
|
||||
- image/delete, image/flatten, image/modify - для образов, которыми пользователь владеет.
|
||||
|
||||
Все остальные операции разрешаются только администраторам (`type=admin`).
|
||||
|
||||
## Производительность шифрования
|
||||
|
||||
У вас может возникнуть вопрос - а как быстро всё это прекрасное шифрование работает?
|
||||
|
||||
Ответ - сильно зависит от процессора. На современных процессорах (при наличии AVX512 с VAES)
|
||||
очень быстро - скорость шифрования AES может составлять 10-20 Гбайт/с и выше. В первую очередь
|
||||
подразумевается CPU клиентских машин, потому что сквозное шифрование выполняется целиком на
|
||||
клиенте, а транспортное хоть также и затрагивает OSD, но у клиента поток один, а OSD на стороне
|
||||
сервера много и добавить там ресурсов легче.
|
||||
|
||||
На более старых процессорах скорость заметно хуже, например, на Xeon E5 v4 она составляет
|
||||
буквально 3 Гбайт/с.
|
||||
|
||||
Вы можете оценить производительность своих процессоров с помощью команды `vitastor-cli cpubench`.
|
||||
|
||||
Пример вывода (💪 AMD EPYC 9575F):
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 209000 iterations in 2001 ms = 104447.78 MB/s
|
||||
xxhash3 4 K block... 37000000 iterations in 2022 ms = 71479.35 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 210000 iterations in 2015 ms = 104218.36 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 26000000 iterations in 2073 ms = 48993.01 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 54000 iterations in 2000 ms = 27000.00 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 11700000 iterations in 2014 ms = 22692.71 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 50000 iterations in 2039 ms = 24521.82 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 12600000 iterations in 2009 ms = 24499.13 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2013 ms = 19870.84 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 10200000 iterations in 2011 ms = 19812.90 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 40000 iterations in 2014 ms = 19860.97 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 8700000 iterations in 2011 ms = 16899.24 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 26000 iterations in 2062 ms = 12609.12 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 6300000 iterations in 2006 ms = 12267.88 MB/s
|
||||
```
|
||||
|
||||
А вот Xeon E5-2680v4:
|
||||
|
||||
```
|
||||
$ vitastor-cli cpubench
|
||||
Vitastor transport encryption benchmark (AES-256-GCM, AES-256-XTS and xxhash3)
|
||||
|
||||
Warmup...
|
||||
|
||||
No transport encryption, data checksums enabled, e2e unencrypted image
|
||||
xxhash3 1 M block... 62000 iterations in 2021 ms = 30677.88 MB/s
|
||||
xxhash3 4 K block... 12400000 iterations in 2006 ms = 24146.31 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e unencrypted image
|
||||
AES-256-GCM encrypt header + xxhash3 1 M block... 62000 iterations in 2027 ms = 30587.07 MB/s
|
||||
AES-256-GCM encrypt header + xxhash3 4 K block... 6800000 iterations in 2011 ms = 13208.60 MB/s
|
||||
|
||||
Full transport encryption, e2e unencrypted image
|
||||
AES-256-GCM encrypt header and 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-GCM encrypt header and 4 K block... 1500000 iterations in 2102 ms = 2787.52 MB/s
|
||||
|
||||
No transport encryption, no checksums, e2e encrypted image
|
||||
AES-256-XTS encrypt 1 M block... 7000 iterations in 2317 ms = 3021.15 MB/s
|
||||
AES-256-XTS encrypt 4 K block... 1600000 iterations in 2088 ms = 2993.30 MB/s
|
||||
|
||||
No transport encryption, e2e encrypted image, data checksums enabled
|
||||
AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2188 ms = 2742.23 MB/s
|
||||
AES-256-XTS encrypt + xxhash3 4 K block... 1400000 iterations in 2053 ms = 2663.78 MB/s
|
||||
|
||||
Header encryption with payload checksums, e2e encrypted image
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 1 M block... 6000 iterations in 2190 ms = 2739.73 MB/s
|
||||
AES-256-GCM encrypt header + AES-256-XTS encrypt + xxhash3 4 K block... 1300000 iterations in 2101 ms = 2417.00 MB/s
|
||||
|
||||
Full transport encryption, e2e encrypted image
|
||||
AES-256-XTS + AES-256-GCM encrypt 1 M block... 4000 iterations in 2666 ms = 1500.38 MB/s
|
||||
AES-256-XTS + AES-256-GCM encrypt 4 K block... 800000 iterations in 2113 ms = 1478.94 MB/s
|
||||
```
|
||||
+15
-41
@@ -3,7 +3,6 @@
|
||||
|
||||
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');
|
||||
|
||||
@@ -28,17 +27,7 @@ class AntiEtcdAdapter
|
||||
is_local['::'] = true;
|
||||
is_local[''] = true;
|
||||
// split :, 3 -> <schema>:<//ip>:<port>
|
||||
const selected = [];
|
||||
for (let i = 0; i < cluster.length; i++)
|
||||
{
|
||||
const m = /^(https?:\/\/)?(?:\[(.*)\]|([^\[\:]+))(?::(\d+))?$/.exec(cluster[i]);
|
||||
if (!m)
|
||||
continue;
|
||||
const ip = m[3] || m[2];
|
||||
const port = m[4] || 2379;
|
||||
if (is_local[ip] && (!cfg_port || port == cfg_port))
|
||||
selected.push({ idx: i, ip, port });
|
||||
}
|
||||
const selected = cluster.map(s => s.split(':', 3)).filter(ip => is_local[ip[1].substr(2)] && (!cfg_port || ip[2] == cfg_port));
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
@@ -47,44 +36,29 @@ class AntiEtcdAdapter
|
||||
else if (selected.length == 1)
|
||||
{
|
||||
const antietcd_config = {
|
||||
ip: selected[0].ip,
|
||||
port: selected[0].port,
|
||||
ip: selected[0][1].substr(2),
|
||||
port: selected[0][2],
|
||||
cert: config.antietcd_cert,
|
||||
key: config.antietcd_key,
|
||||
ca: config.client_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0].port+'.json.gz'),
|
||||
ca: config.etcd_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][2]+'.json.gz'),
|
||||
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
|
||||
node_id: cluster[selected[0].idx].replace(/^(https?:\/\/)/, ''), // same as in <cluster> below
|
||||
node_id: selected[0][1].substr(2)+':'+selected[0][2], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
|
||||
cluster_key: (config.etcd_prefix || '/vitastor'),
|
||||
stale_read: 1,
|
||||
log_level: 1,
|
||||
logs: { cluster: true },
|
||||
};
|
||||
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;
|
||||
}
|
||||
if (config.use_perms)
|
||||
if (config.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;
|
||||
antietcd_config.auth_filter = require('./vitastor_auth_filter.js');
|
||||
antietcd_config.peer_ca = config.antietcd_server_ca;
|
||||
if (!config.antietcd_server_ca || config.antietcd_server_ca == config.etcd_ca)
|
||||
{
|
||||
console.error('Secure setup requires separate antietcd_server_ca (for signing antietcd server certificates) and etcd_ca (for signing client certificates)');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
for (const key in config)
|
||||
{
|
||||
@@ -210,7 +184,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, { user_type: 'mon' });
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { username: 'root' });
|
||||
if (res.error)
|
||||
{
|
||||
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
|
||||
|
||||
@@ -112,10 +112,9 @@ function make_cyclic(pgs, parity_space)
|
||||
{
|
||||
if (parity_space > 1)
|
||||
{
|
||||
for (const id in pgs)
|
||||
for (const pg in pgs)
|
||||
{
|
||||
const pg = pgs[id];
|
||||
for (let i = 1; i < pg.length; i++)
|
||||
for (let i = 1; i < pg.size; i++)
|
||||
{
|
||||
const cyclic = [ ...pg.slice(i), ...pg.slice(0, i) ];
|
||||
pgs['pg_'+cyclic.join('_')] = cyclic;
|
||||
|
||||
+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] = JSON.parse(JSON.stringify(this.state.pg.history[pool_id][pg]));
|
||||
pg_history[pg-1] = 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();
|
||||
}, (this.next_recheck_at-now)*1000);
|
||||
}, now-this.next_recheck_at);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.15",
|
||||
"version": "3.0.9",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "Vitaliy Filippov",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"antietcd": "^1.3.1",
|
||||
"antietcd": "^1.2.4",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"ws": "^7.2.5"
|
||||
},
|
||||
|
||||
+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] = JSON.parse(JSON.stringify(merged_history[1]));
|
||||
new_pg_history[i] = { ...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)
|
||||
|
||||
+46
-379
@@ -1,324 +1,38 @@
|
||||
#!/usr/bin/node
|
||||
// Simple Vitastor etcd / antietcd / TLS configurator
|
||||
// Simple systemd unit generator for etcd
|
||||
// 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 path = require('path');
|
||||
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, 2026+ (MIT)
|
||||
|
||||
USAGE:
|
||||
1) Create a minimal vitastor.conf with etcd_address, osd_network and (optionally) use_perms.
|
||||
Non-encrypted: {"etcd_address":["http://10.0.0.10:2379","http://10.0.0.11:2379","http://10.0.0.12:2379"],"osd_network":"10.0.0.0/24"}
|
||||
Encrypted: {"etcd_address":["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],"use_perms":true,"osd_network":"10.0.0.0/24"}
|
||||
(Note https:// etcd URLs!)
|
||||
2) Run: ${process.argv[1]} [./vitastor.conf] [--antietcd-only]
|
||||
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:
|
||||
--antietcd-only
|
||||
disable etcd (proxy or direct mode), use only antietcd
|
||||
--gen-certs
|
||||
force certificate generation even if it's not the first node
|
||||
--no-certs
|
||||
disable certificate generation
|
||||
--copy yes|no|ask
|
||||
copy vitastor.conf and TLS certificates for monitor&etcd to monitor nodes using scp
|
||||
(default is ask)
|
||||
--copy-to-osd-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates for OSDs to NODES using scp
|
||||
`;
|
||||
|
||||
async function run()
|
||||
{
|
||||
let config_path = '/etc/vitastor/vitastor.conf';
|
||||
let config_dir = '/etc/vitastor/';
|
||||
let gen_certs = 'auto';
|
||||
let antietcd_only = false;
|
||||
let copy = 'ask';
|
||||
let copy_to_osd = null;
|
||||
for (let i = 2; i < process.argv.length; i++)
|
||||
const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
|
||||
if (config_path == '-h' || config_path == '--help')
|
||||
{
|
||||
const arg = process.argv[i];
|
||||
if (arg == '-h' || arg == '--help')
|
||||
{
|
||||
console.log(help_text);
|
||||
process.exit(0);
|
||||
}
|
||||
else if (arg == '--gen-certs')
|
||||
{
|
||||
gen_certs = true;
|
||||
}
|
||||
else if (arg == '--no-certs')
|
||||
{
|
||||
gen_certs = false;
|
||||
}
|
||||
else if (arg == '--antietcd-only')
|
||||
{
|
||||
antietcd_only = true;
|
||||
}
|
||||
else if (arg == '--copy-to-osd-node' && i < process.argv.length-1)
|
||||
{
|
||||
i++;
|
||||
copy_to_osd = process.argv[i].split(/,/);
|
||||
}
|
||||
else if (arg == '--copy' && i < process.argv.length-1)
|
||||
{
|
||||
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;
|
||||
config_dir = path.dirname(arg);
|
||||
}
|
||||
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 (!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_perms = tls && config.use_perms;
|
||||
const num = select_local_etcd(etcds);
|
||||
if (copy_to_osd)
|
||||
{
|
||||
copy_to_osd_nodes(copy_to_osd, config_dir, use_perms, antietcd_only);
|
||||
process.exit(0);
|
||||
}
|
||||
if (tls)
|
||||
{
|
||||
const etcd_ca = config_dir+'/'+path.basename(config.etcd_ca);
|
||||
if (gen_certs === true)
|
||||
{
|
||||
console.log('Certificate generation is requested explicitly, generating');
|
||||
}
|
||||
else if (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 (config.etcd_ca && fs.existsSync(etcd_ca))
|
||||
{
|
||||
gen_certs = false;
|
||||
console.log(etcd_ca+' 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)+', '+etcd_ca+' does not exist, please copy certificates to this node');
|
||||
process.exit(1);
|
||||
}
|
||||
await write_auth_config(config, config_path, etcds, use_perms, antietcd_only);
|
||||
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(config_dir, copy, etcds, use_perms, antietcd_only);
|
||||
}
|
||||
}
|
||||
if (num < 0)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(tls && gen_certs ? 0 : 1);
|
||||
}
|
||||
await configure_etcd(etcds, num, tls, use_perms);
|
||||
await enable_mon();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function ask_copy(question)
|
||||
{
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: '> ',
|
||||
});
|
||||
let copy;
|
||||
while (copy != 'y' && copy != 'n' && copy != 'yes' && copy != 'no')
|
||||
{
|
||||
if (copy)
|
||||
console.log('Please type "yes" or "no"');
|
||||
copy = await new Promise(ok => rl.question(question, ok));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
async function copy_to_osd_nodes(to, dir, use_perms, antietcd_only)
|
||||
{
|
||||
const osd_to_copy = [ 'vitastor.conf' ];
|
||||
if (!antietcd_only && !use_perms)
|
||||
osd_to_copy.push('etcd_ca.crt');
|
||||
else
|
||||
osd_to_copy.push('antietcd_ca.crt');
|
||||
if (use_perms)
|
||||
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
|
||||
console.warn('Copying configuration to OSD nodes '+to.join(', '));
|
||||
for (const node of to)
|
||||
await system("scp "+dir+osd_to_copy.join(" "+dir)+" root@"+node+":/etc/vitastor/");
|
||||
}
|
||||
|
||||
async function make_certs(dir, copy, etcds, use_perms, antietcd_only)
|
||||
{
|
||||
console.log(`-----
|
||||
Generating certificates in ${dir}
|
||||
-----
|
||||
`);
|
||||
const to_copy = [ 'vitastor.conf' ];
|
||||
const osd_to_copy = [ 'vitastor.conf' ];
|
||||
if (!antietcd_only)
|
||||
{
|
||||
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(','));
|
||||
to_copy.push('etcd_ca.crt', 'etcd.crt', 'etcd.key');
|
||||
if (!use_perms)
|
||||
osd_to_copy.push('etcd_ca.crt');
|
||||
}
|
||||
if (use_perms || antietcd_only)
|
||||
{
|
||||
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(','));
|
||||
to_copy.push('antietcd_ca.crt', 'antietcd.crt', 'antietcd.key');
|
||||
osd_to_copy.push('antietcd_ca.crt');
|
||||
}
|
||||
if (use_perms)
|
||||
{
|
||||
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");
|
||||
to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
|
||||
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
|
||||
}
|
||||
console.log(`-----
|
||||
Certificates generated, commands to copy them:
|
||||
- Monitor+OSD node:
|
||||
cd ${dir} && scp ${to_copy.join(' ')} root@NODE:/etc/vitastor/
|
||||
- Monitor node:
|
||||
cd ${dir} && scp ${to_copy.filter(f => f != 'osd.key').join(' ')} root@NODE:/etc/vitastor/
|
||||
- OSD node:
|
||||
cd ${dir} && scp ${osd_to_copy.join(' ')} root@NODE:/etc/vitastor/
|
||||
-----
|
||||
`);
|
||||
if (copy)
|
||||
{
|
||||
for (const node of etcds)
|
||||
{
|
||||
await system("scp "+dir+to_copy.join(" "+dir)+" root@"+node.ip+":/etc/vitastor/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn('Certificates generated in '+dir+', please copy them to other nodes');
|
||||
}
|
||||
}
|
||||
|
||||
async function write_auth_config(config, config_path, etcds, use_perms, antietcd_only)
|
||||
{
|
||||
const auth = {};
|
||||
if (use_perms)
|
||||
{
|
||||
auth["use_antietcd"] = true;
|
||||
if (!antietcd_only)
|
||||
{
|
||||
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
|
||||
{
|
||||
if (antietcd_only)
|
||||
{
|
||||
auth["use_antietcd"] = true;
|
||||
auth["antietcd_cert"] = "/etc/vitastor/antietcd.crt";
|
||||
auth["antietcd_key"] = "/etc/vitastor/antietcd.key";
|
||||
auth["etcd_ca"] = "/etc/vitastor/antietcd_ca.crt";
|
||||
}
|
||||
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 function configure_etcd(etcds, num, tls, use_perms)
|
||||
{
|
||||
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"))
|
||||
@@ -331,55 +45,37 @@ async function configure_etcd(etcds, num, tls, use_perms)
|
||||
console.log("/etc/systemd/system/etcd.service already exists");
|
||||
process.exit(1);
|
||||
}
|
||||
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_perms ? 2381 : 2379),
|
||||
listen_client_urls: etcd_url+':'+(use_perms ? 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)
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
{
|
||||
options['cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_perms)
|
||||
{
|
||||
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_perms)
|
||||
{
|
||||
options['peer_client_cert_auth'] = '1';
|
||||
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd.crt';
|
||||
}
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
}
|
||||
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
|
||||
? fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' })
|
||||
: "";
|
||||
for (const k in options)
|
||||
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)
|
||||
{
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_'+k.toUpperCase().replace(/-/, '_'), options[k]);
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(0);
|
||||
}
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
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(',');
|
||||
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);
|
||||
}
|
||||
await system(`mkdir -p /var/lib/etcd/vitastor`);
|
||||
fs.writeFileSync(
|
||||
"/etc/systemd/system/vitastor-etcd.service", `[Unit]
|
||||
"/etc/systemd/system/vitastor-etcd.service",
|
||||
`[Unit]
|
||||
Description=etcd for vitastor
|
||||
After=network-online.target local-fs.target time-sync.target
|
||||
Wants=network-online.target local-fs.target time-sync.target
|
||||
@@ -387,8 +83,12 @@ Wants=network-online.target local-fs.target time-sync.target
|
||||
[Service]
|
||||
Restart=always
|
||||
Environment=GOGC=50
|
||||
EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
ExecStart=etcd --data-dir /var/lib/etcd/vitastor
|
||||
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
|
||||
WorkingDirectory=/var/lib/etcd/vitastor
|
||||
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
|
||||
User=etcd
|
||||
@@ -406,11 +106,7 @@ 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`);
|
||||
}
|
||||
|
||||
async function enable_mon()
|
||||
{
|
||||
await system(`systemctl enable --now vitastor-mon`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function replace_env(text, key, value)
|
||||
@@ -423,29 +119,16 @@ 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])
|
||||
{
|
||||
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;
|
||||
}
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
if (etcds[i] == iface.address.toLowerCase())
|
||||
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());
|
||||
@@ -453,19 +136,3 @@ 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");
|
||||
}
|
||||
|
||||
+31
-99
@@ -6,19 +6,16 @@
|
||||
// 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.
|
||||
// - type, one of: osd, mon, admin, client.
|
||||
// osd, mon types should be used by OSDs/monitors.
|
||||
// admin should be used for administrative access from vitastor-cli.
|
||||
// client should be used for regular clients.
|
||||
// - 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: {
|
||||
@@ -27,7 +24,7 @@ const static_perms = {
|
||||
},
|
||||
osd: {
|
||||
keys: { '/pg/config': false },
|
||||
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
|
||||
prefixes: { '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
|
||||
},
|
||||
mon: {
|
||||
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
|
||||
@@ -45,15 +42,15 @@ const static_perms = {
|
||||
},
|
||||
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 },
|
||||
prefixes: { '/osd/stats/': 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 },
|
||||
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true },
|
||||
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true },
|
||||
admin: { maintenance_status: true },
|
||||
client: { maintenance_status: true },
|
||||
client: {},
|
||||
};
|
||||
|
||||
class VitastorAuthFilter
|
||||
@@ -66,43 +63,6 @@ class VitastorAuthFilter
|
||||
this.prefix_parts = this.prefix.split('/');
|
||||
}
|
||||
|
||||
async init()
|
||||
{
|
||||
if (!this.cfg.cert || !this.cfg.key || !this.cfg.ca || !this.cfg.osd_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 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;
|
||||
@@ -303,18 +263,6 @@ class VitastorAuthFilter
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -401,31 +349,19 @@ class VitastorAuthFilter
|
||||
return true;
|
||||
}
|
||||
|
||||
_get_user(context)
|
||||
_get_user(username)
|
||||
{
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
if (!username)
|
||||
{
|
||||
return {
|
||||
name: context.user_type,
|
||||
type: context.user_type,
|
||||
perms: static_perms[context.user_type],
|
||||
};
|
||||
return null;
|
||||
}
|
||||
if (!context.username)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', 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;
|
||||
userInfo.name = username;
|
||||
if (userInfo.groups instanceof Array)
|
||||
{
|
||||
userInfo.groups = userInfo.groups.reduce((a, c) => { a[c] = true; return a; }, {});
|
||||
@@ -437,27 +373,23 @@ class VitastorAuthFilter
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
filter_api(context, api/*, data*/)
|
||||
filter_api(username, api/*, data*/)
|
||||
{
|
||||
let type = 'client';
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
if (username === 'root')
|
||||
{
|
||||
type = context.user_type;
|
||||
return true;
|
||||
}
|
||||
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];
|
||||
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', username ], true);
|
||||
return userInfo && api_perms[userInfo.type] && api_perms[userInfo.type][api];
|
||||
}
|
||||
|
||||
filter_txn(context, txn)
|
||||
filter_txn(username, txn)
|
||||
{
|
||||
const userInfo = this._get_user(context);
|
||||
if (username === 'root')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const userInfo = this._get_user(username);
|
||||
if (!userInfo)
|
||||
{
|
||||
return null;
|
||||
@@ -493,13 +425,13 @@ class VitastorAuthFilter
|
||||
return txn;
|
||||
}
|
||||
|
||||
filter_txn_response(context, txn, res)
|
||||
filter_txn_response(username, txn, res)
|
||||
{
|
||||
if (!res.responses)
|
||||
if (!res.responses || username === 'root')
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
const userInfo = this._get_user(username);
|
||||
if (!userInfo)
|
||||
{
|
||||
for (const resp of res.responses)
|
||||
@@ -520,13 +452,13 @@ class VitastorAuthFilter
|
||||
}
|
||||
}
|
||||
|
||||
filter_watch_message(context, msg)
|
||||
filter_watch_message(username, msg)
|
||||
{
|
||||
if (!msg.result || !msg.result.events)
|
||||
if (!msg.result || !msg.result.events || username === 'root')
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
const userInfo = this._get_user(username);
|
||||
if (!userInfo)
|
||||
{
|
||||
msg.result.events = [];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor",
|
||||
"version": "3.0.15",
|
||||
"version": "3.0.9",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
+10
-45
@@ -366,38 +366,15 @@ sub map_volume
|
||||
my $prefix = defined $scfg->{vitastor_prefix} ? $scfg->{vitastor_prefix} : 'pve/';
|
||||
|
||||
my ($vtype, $img_name, $vmid) = $class->parse_volname($volname);
|
||||
my $name = $prefix.$img_name;
|
||||
my $name = $img_name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
|
||||
my $mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
my ($kerneldev) = grep {
|
||||
$mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name
|
||||
} keys %$mapped;
|
||||
my ($kerneldev) = grep { $mapped->{$_}->{image} eq $prefix.$name } keys %$mapped;
|
||||
return $kerneldev if $kerneldev && -b $kerneldev; # already mapped
|
||||
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
my $size = `/usr/sbin/blockdev --getsize64 $kerneldev`;
|
||||
return $kerneldev if $size && $size > 0;
|
||||
}
|
||||
|
||||
my $map_out = run_cli($scfg, [ 'map', '--image', $name ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
$map_out =~ s/^\s+|\s+$//gso;
|
||||
|
||||
# Wait until the device is started
|
||||
for (my $i = 0; $i < 100; $i++)
|
||||
{
|
||||
$mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
($kerneldev) = grep { $mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name } keys %$mapped;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
my $size = `/usr/sbin/blockdev --getsize64 $kerneldev`;
|
||||
return $kerneldev if $size && $size > 0;
|
||||
}
|
||||
select(undef, undef, undef, 0.1);
|
||||
}
|
||||
|
||||
die "Failed to map Vitastor image $name via NBD".
|
||||
($map_out ? ", vitastor-nbd map returned '$map_out'" : "")."\n";
|
||||
$kerneldev = run_cli($scfg, [ 'map', '--image', $prefix.$name ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
return $kerneldev;
|
||||
}
|
||||
|
||||
sub unmap_volume
|
||||
@@ -406,19 +383,13 @@ sub unmap_volume
|
||||
my $prefix = defined $scfg->{vitastor_prefix} ? $scfg->{vitastor_prefix} : 'pve/';
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
$name = $prefix.$name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
|
||||
my $mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
|
||||
my @kerneldevs = grep {
|
||||
$mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name
|
||||
} keys %$mapped;
|
||||
|
||||
for my $kerneldev (@kerneldevs)
|
||||
my ($kerneldev) = grep { $mapped->{$_}->{image} eq $prefix.$name } keys %$mapped;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
next if !$kerneldev || !-b $kerneldev;
|
||||
eval { run_cli($scfg, [ 'unmap', $kerneldev ], binary => '/usr/bin/vitastor-nbd', json => 0); };
|
||||
warn "Failed to unmap Vitastor image $name from $kerneldev: $@" if $@;
|
||||
run_cli($scfg, [ 'unmap', $kerneldev ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -434,13 +405,7 @@ sub activate_volume
|
||||
sub deactivate_volume
|
||||
{
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
|
||||
# Even with vitastor_nbd=0, Proxmox may call map_volume() for special
|
||||
# volumes like tpmstate0 because swtpm needs a local file/block path.
|
||||
# Therefore, always try to unmap an existing NBD mapping here.
|
||||
# unmap_volume() is a no-op if the volume is not currently mapped.
|
||||
$class->unmap_volume($storeid, $scfg, $volname, $snapname);
|
||||
|
||||
$class->unmap_volume($storeid, $scfg, $volname, $snapname) if $scfg->{vitastor_nbd};
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.15'
|
||||
VITASTOR_VERSION = '3.0.9'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.15
|
||||
Version: 3.0.9
|
||||
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.15.el10.tar.gz
|
||||
Source0: vitastor-3.0.9.el10.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.15
|
||||
Version: 3.0.9
|
||||
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.15.el7.tar.gz
|
||||
Source0: vitastor-3.0.9.el7.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: devtoolset-9-gcc-c++
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.15
|
||||
Version: 3.0.9
|
||||
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.15.el8.tar.gz
|
||||
Source0: vitastor-3.0.9.el8.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-toolset-9-gcc-c++
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.15
|
||||
Version: 3.0.9
|
||||
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.15.el9.tar.gz
|
||||
Source0: vitastor-3.0.9.el9.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
|
||||
+1
-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.15")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.9")
|
||||
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})
|
||||
|
||||
@@ -100,7 +100,6 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
(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;
|
||||
@@ -521,7 +520,7 @@ void blockstore_disk_t::close_all()
|
||||
|
||||
// Sadly DISCARD only works through ioctl(), but it seems to always block the device queue,
|
||||
// so it's not a big deal that we can only run it synchronously.
|
||||
int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_used)
|
||||
int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_free)
|
||||
{
|
||||
if (mock_mode)
|
||||
{
|
||||
@@ -532,7 +531,7 @@ int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_used)
|
||||
uint64_t discarded = 0;
|
||||
for (; i <= block_count; i++)
|
||||
{
|
||||
if (i >= block_count || is_used(i))
|
||||
if (i >= block_count || is_free(i))
|
||||
{
|
||||
if (i > j && (i-j)*data_block_size >= min_discard_size)
|
||||
{
|
||||
@@ -545,21 +544,17 @@ int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_used)
|
||||
if (range[0] % discard_granularity)
|
||||
range[0] = range[0] + discard_granularity - (range[0] % discard_granularity);
|
||||
if (range[0] >= range[1])
|
||||
range[1] = 0;
|
||||
else
|
||||
range[1] -= range[0];
|
||||
continue;
|
||||
range[1] -= range[0];
|
||||
}
|
||||
if (range[1] > 0)
|
||||
r = ioctl(data_fd, BLKDISCARD, &range);
|
||||
if (r != 0)
|
||||
{
|
||||
r = ioctl(data_fd, BLKDISCARD, &range);
|
||||
if (r != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to execute BLKDISCARD %ju+%ju on %s: %s (code %d)\n",
|
||||
range[0], range[1], data_device.c_str(), strerror(-r), r);
|
||||
return -errno;
|
||||
}
|
||||
discarded += range[1];
|
||||
fprintf(stderr, "Failed to execute BLKDISCARD %ju+%ju on %s: %s (code %d)\n",
|
||||
range[0], range[1], data_device.c_str(), strerror(-r), r);
|
||||
return -errno;
|
||||
}
|
||||
discarded += range[1];
|
||||
}
|
||||
j = i+1;
|
||||
}
|
||||
|
||||
@@ -60,8 +60,6 @@ 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;
|
||||
|
||||
@@ -84,7 +82,7 @@ struct blockstore_disk_t
|
||||
void calc_lengths(bool skip_meta_check = false);
|
||||
void check_lengths();
|
||||
void close_all();
|
||||
int trim_data(std::function<bool(uint64_t)> is_used);
|
||||
int trim_data(std::function<bool(uint64_t)> is_free);
|
||||
|
||||
inline uint64_t dirty_dyn_size(uint64_t offset, uint64_t len)
|
||||
{
|
||||
|
||||
@@ -174,18 +174,14 @@ 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:
|
||||
@@ -200,7 +196,6 @@ 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:
|
||||
@@ -264,9 +259,11 @@ resume_1:
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE ||
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity)
|
||||
{
|
||||
bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
auto res = 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)
|
||||
@@ -276,53 +273,30 @@ resume_1:
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
memcpy(new_ext_bmp, compact_info.clean_wr->get_ext_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
bitmap_copied = true;
|
||||
}
|
||||
if (bs->dsk.csum_block_size && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
memcpy(new_csums, compact_info.clean_wr->get_checksums(bs->heap), bs->dsk.data_block_size/bs->dsk.csum_block_size * (bs->dsk.data_csum_type & 0xFF));
|
||||
for (size_t i = csum_copy.size(); i > 0; i--)
|
||||
{
|
||||
auto wr = csum_copy[i-1];
|
||||
memcpy(new_csums + wr->small().offset/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF),
|
||||
wr->get_checksums(bs->heap), wr->small().len/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF));
|
||||
}
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
flusher->active_flushers++;
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
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 (compact_info.do_delete)
|
||||
{
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx up to l%ju (delete)\n", cur_oid.inode, cur_oid.stripe, compact_info.compact_lsn);
|
||||
}
|
||||
clean_loc = UINT64_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
memcpy(new_ext_bmp, compact_info.clean_wr->get_ext_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
bitmap_copied = true;
|
||||
}
|
||||
if (bs->dsk.csum_block_size && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
memcpy(new_csums, compact_info.clean_wr->get_checksums(bs->heap), bs->dsk.data_block_size/bs->dsk.csum_block_size * (bs->dsk.data_csum_type & 0xFF));
|
||||
for (size_t i = csum_copy.size(); i > 0; i--)
|
||||
{
|
||||
auto wr = csum_copy[i-1];
|
||||
memcpy(new_csums + wr->small().offset/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF),
|
||||
wr->get_checksums(bs->heap), wr->small().len/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF));
|
||||
}
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
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)
|
||||
@@ -362,13 +336,6 @@ 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--;
|
||||
@@ -382,7 +349,10 @@ resume_4:
|
||||
if (res == ENOENT)
|
||||
{
|
||||
// Abort compaction
|
||||
goto abort_compact;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
goto resume_0;
|
||||
}
|
||||
if (res == EAGAIN)
|
||||
{
|
||||
@@ -411,14 +381,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_COALESCED) ||
|
||||
(read_vec[i].copy_flags & COPY_BUF_PADDED)) // FIXME Shit, simplify these flags
|
||||
{
|
||||
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++;
|
||||
}
|
||||
@@ -429,17 +399,6 @@ 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
|
||||
@@ -449,14 +408,12 @@ resume_25:
|
||||
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;
|
||||
}
|
||||
@@ -465,7 +422,6 @@ resume_25:
|
||||
if (res == EBUSY)
|
||||
{
|
||||
// Abort compaction, object is already overwritten by something else
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -630,13 +586,13 @@ 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 punch_holes and then write the block to disk
|
||||
// Modified, we should add_punch_holes and then write the block to disk
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::calc_block_checksums()
|
||||
{
|
||||
if (bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity || compact_info.do_delete)
|
||||
if (bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -743,67 +699,6 @@ 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,15 +25,6 @@ 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
|
||||
@@ -67,7 +58,6 @@ 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;
|
||||
|
||||
@@ -78,8 +68,6 @@ 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);
|
||||
@@ -100,7 +88,6 @@ 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;
|
||||
|
||||
+204
-392
@@ -23,13 +23,12 @@
|
||||
|
||||
#define HEAP_INFLIGHT_DONE 1
|
||||
#define HEAP_INFLIGHT_COMPACTABLE 2
|
||||
#define HEAP_INFLIGHT_OVERWRITE 4
|
||||
#define HEAP_INFLIGHT_COMPACTED 4
|
||||
#define HEAP_INFLIGHT_GC 8
|
||||
#define HEAP_INFLIGHT_EXPLICIT 16
|
||||
|
||||
#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)
|
||||
|
||||
@@ -123,33 +122,31 @@ uint32_t heap_entry_t::get_size(blockstore_heap_t *heap)
|
||||
}
|
||||
if (type() == BS_HEAP_SMALL_WRITE || type() == BS_HEAP_INTENT_WRITE)
|
||||
{
|
||||
if (size < sizeof(heap_small_write_t))
|
||||
return heap->get_small_entry_size(0, 0);
|
||||
return heap->get_small_entry_size(small().offset, small().len);
|
||||
}
|
||||
return heap->get_simple_entry_size();
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_overwrite() const
|
||||
bool heap_entry_t::is_overwrite()
|
||||
{
|
||||
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() const
|
||||
bool heap_entry_t::is_compactable()
|
||||
{
|
||||
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(const heap_entry_t *other) const
|
||||
bool heap_entry_t::is_before(heap_entry_t *other)
|
||||
{
|
||||
return lsn < other->lsn || lsn == other->lsn && !is_overwrite() && other->is_overwrite();
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_garbage() const
|
||||
bool heap_entry_t::is_garbage()
|
||||
{
|
||||
return (entry_type & BS_HEAP_GARBAGE);
|
||||
}
|
||||
@@ -376,11 +373,14 @@ corrupted_object:
|
||||
return EDOM;
|
||||
}
|
||||
}
|
||||
if (wr->size != wr->get_size(this))
|
||||
if (((wr->entry_type & BS_HEAP_TYPE) == BS_HEAP_SMALL_WRITE ||
|
||||
(wr->entry_type & BS_HEAP_TYPE) == BS_HEAP_INTENT_WRITE) &&
|
||||
wr->size < sizeof(heap_small_write_t))
|
||||
{
|
||||
// Check entry size
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u != %u bytes)\n",
|
||||
wr->inode, wr->stripe, wr->version, block_num, block_offset, wr->size, wr->get_size(this));
|
||||
// Small writes require accessing offset & len to calculate correct length,
|
||||
// so require at least sizeof(heap_small_write_t) for them
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u < min %zu bytes)\n",
|
||||
wr->inode, wr->stripe, wr->version, block_num, block_offset, wr->size, sizeof(heap_small_write_t));
|
||||
goto corrupted_object;
|
||||
}
|
||||
if (wr->entry_type == BS_HEAP_COMMIT && !wr->version)
|
||||
@@ -418,13 +418,6 @@ 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;
|
||||
}
|
||||
@@ -451,7 +444,7 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
|
||||
next_lsn = wr->lsn;
|
||||
}
|
||||
entries_loaded++;
|
||||
insert_list_items(&li, 1, true);
|
||||
loaded_list_items.push_back(li);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
if (!inf.entries.size())
|
||||
@@ -557,26 +550,18 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
|
||||
|
||||
void blockstore_heap_t::finish_load()
|
||||
{
|
||||
if (postponed_items.size())
|
||||
if (loaded_list_items.size())
|
||||
{
|
||||
// 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)
|
||||
// Sort everything and load in correct order
|
||||
std::sort(loaded_list_items.begin(), loaded_list_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
|
||||
{
|
||||
return a->entry.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
|
||||
return a->entry.lsn < b->entry.lsn;
|
||||
});
|
||||
size_t s = 0, e, n = postponed_items.size();
|
||||
for (e = 1; e <= n; e++)
|
||||
for (auto & li: loaded_list_items)
|
||||
{
|
||||
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;
|
||||
}
|
||||
insert_list_item(li);
|
||||
}
|
||||
postponed_items.clear();
|
||||
loaded_list_items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,11 +574,26 @@ void blockstore_heap_t::fill_recheck_queue()
|
||||
inode_map_iterate(ip.second, [&](heap_list_item_t *li)
|
||||
{
|
||||
auto obj = &li->entry;
|
||||
// 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)
|
||||
// Add object to recheck queue
|
||||
if (obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
recheck_queue.push_back(obj);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -603,11 +603,6 @@ 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)
|
||||
@@ -661,45 +656,17 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
{
|
||||
if (is_data_used(wr->big_location(this)))
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
use_data(wr->inode, wr->big_location(this));
|
||||
}
|
||||
if (wr->is_compactable())
|
||||
if (wr->is_compactable() && !added)
|
||||
{
|
||||
to_compact_count++;
|
||||
if (!added)
|
||||
{
|
||||
compact_queue.push_back((object_id){ .inode = wr->inode, .stripe = wr->stripe });
|
||||
added = true;
|
||||
}
|
||||
compact_queue.push_back((object_id){ .inode = wr->inode, .stripe = wr->stripe });
|
||||
added = true;
|
||||
}
|
||||
if (wr->is_overwrite())
|
||||
{
|
||||
@@ -709,11 +676,6 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
});
|
||||
}
|
||||
}
|
||||
for (auto li: init_erase_items)
|
||||
{
|
||||
unlink_list_item(li);
|
||||
}
|
||||
init_erase_items.clear();
|
||||
if (dsk->gc_on_start)
|
||||
{
|
||||
recheck_full_gc();
|
||||
@@ -721,121 +683,6 @@ 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);
|
||||
}
|
||||
|
||||
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);
|
||||
// Can't erase (mutate map) while iterating, so postpone it
|
||||
init_erase_items.push_back(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);
|
||||
// Can't erase (mutate map) while iterating, so postpone it
|
||||
init_erase_items.push_back(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);
|
||||
// Can't erase (mutate map) while iterating, so postpone it
|
||||
init_erase_items.push_back(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
return erase_cur;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_full_gc()
|
||||
{
|
||||
uint32_t block_num = 0;
|
||||
@@ -876,99 +723,80 @@ void blockstore_heap_t::recheck_full_gc()
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr)
|
||||
void blockstore_heap_t::recheck_buffer(heap_entry_t *cwr, uint8_t *buf)
|
||||
{
|
||||
// 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))
|
||||
auto free_entry = [&](heap_list_item_t *li)
|
||||
{
|
||||
bad_count++;
|
||||
}
|
||||
auto prev_wr = prev(bad_wr);
|
||||
if (prev_wr)
|
||||
{
|
||||
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);
|
||||
unlink_list_item(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)
|
||||
uint32_t block_num = li->block_num;
|
||||
auto wr_size = li->entry.size;
|
||||
if (li->entry.is_garbage())
|
||||
{
|
||||
loc = wr->small().location;
|
||||
len = wr->small().len;
|
||||
garbage_entries--;
|
||||
garbage_memory -= list_item_overhead(wr_size);
|
||||
}
|
||||
else if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
live_entries--;
|
||||
live_memory -= list_item_overhead(wr_size);
|
||||
free(li);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
auto & bi = wr->big_intent();
|
||||
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
|
||||
len = bi.len;
|
||||
from_data = true;
|
||||
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));
|
||||
}
|
||||
else if (!calc_checksums(cwr, buf, false))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
assert(li);
|
||||
if (li->prev)
|
||||
{
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
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;
|
||||
free_entry(li);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,47 +819,70 @@ 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 *obj = recheck_queue.front();
|
||||
heap_entry_t *wr = recheck_queue.front();
|
||||
recheck_queue.pop_front();
|
||||
if (obj->type() == BS_HEAP_SMALL_WRITE && buffer_area)
|
||||
bool from_data = false;
|
||||
uint64_t loc = 0;
|
||||
uint32_t len = 0;
|
||||
if (wr->type() == BS_HEAP_INTENT_WRITE)
|
||||
{
|
||||
// Check this object synchronously
|
||||
heap_entry_t *bad_wr = NULL;
|
||||
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
auto prev_wr = prev(wr);
|
||||
while (prev_wr && prev_wr->entry_type == wr->entry_type)
|
||||
{
|
||||
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;
|
||||
// Skip other intent_writes
|
||||
prev_wr = prev(prev_wr);
|
||||
}
|
||||
if (bad_wr)
|
||||
recheck_drop_entries(obj, bad_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;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
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)
|
||||
@@ -1182,7 +1033,7 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
|
||||
while (pos < end && pos < block_end && !(bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
// zero padding at the beginning or at the end of the block is not counted
|
||||
if (pos > prev && prev > blk_start && pos < block_end)
|
||||
if (pos > prev && prev > 0 && pos < block_end)
|
||||
{
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
@@ -1553,51 +1404,43 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::insert_list_items(heap_list_item_t** v, size_t count, bool postpone)
|
||||
void blockstore_heap_t::insert_list_item(heap_list_item_t *li)
|
||||
{
|
||||
auto wr = &v[0]->entry;
|
||||
auto & inode_idx = block_index[get_pg_id(wr->inode, wr->stripe)][wr->inode];
|
||||
auto & inode_idx = block_index[get_pg_id(li->entry.inode, li->entry.stripe)][li->entry.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, 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++)
|
||||
inode_map_get(inode_idx, li_it, old_head, li->entry.stripe);
|
||||
if (old_head && !old_head->entry.is_before(&li->entry))
|
||||
{
|
||||
// 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 li = v[i];
|
||||
auto next_li = old_head;
|
||||
auto prev_li = old_head->prev;
|
||||
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 = 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1627,9 +1470,9 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
|
||||
// Remember the object as dirty and remove older entries when this block is written and fsynced
|
||||
push_inflight_lsn(next_lsn, new_wr,
|
||||
(explicit_complete ? HEAP_INFLIGHT_EXPLICIT : 0) |
|
||||
(new_wr->is_overwrite() ? HEAP_INFLIGHT_OVERWRITE : 0) |
|
||||
(new_wr->is_overwrite() ? HEAP_INFLIGHT_COMPACTED : 0) |
|
||||
(new_wr->is_compactable() ? HEAP_INFLIGHT_COMPACTABLE : 0));
|
||||
insert_list_items(&li, 1, false);
|
||||
insert_list_item(li);
|
||||
li->block_num = block_num;
|
||||
new_wr->size = wr_size;
|
||||
new_wr->checksum = new_wr->calc_checksum(this);
|
||||
@@ -1661,22 +1504,10 @@ int blockstore_heap_t::add_small_write(object_id oid, heap_entry_t **obj_ptr, ui
|
||||
wr->small().location = location;
|
||||
if (bitmap)
|
||||
memcpy(wr->get_ext_bitmap(this), bitmap, dsk->clean_entry_bitmap_size);
|
||||
else if (obj)
|
||||
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
iterate_with_stable(obj, UINT64_MAX, [&](heap_entry_t *old_wr, bool stable)
|
||||
{
|
||||
if (old_wr->get_ext_bitmap(this))
|
||||
{
|
||||
found = true;
|
||||
memcpy(wr->get_ext_bitmap(this), old_wr->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!found)
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
}
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
*obj_ptr = wr;
|
||||
});
|
||||
@@ -1837,7 +1668,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -2140,10 +1970,7 @@ void blockstore_heap_t::iterate_with_stable(heap_entry_t *obj, uint64_t max_lsn,
|
||||
{
|
||||
if (old_wr->type() == BS_HEAP_ROLLBACK)
|
||||
{
|
||||
if (rollback_version > old_wr->version)
|
||||
{
|
||||
rollback_version = old_wr->version;
|
||||
}
|
||||
rollback_version = old_wr->version;
|
||||
}
|
||||
else if (old_wr->type() == BS_HEAP_COMMIT)
|
||||
{
|
||||
@@ -2195,10 +2022,7 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
|
||||
res.compact_lsn = wr->lsn;
|
||||
res.compact_version = wr->version;
|
||||
}
|
||||
if (rollback_version > wr->version)
|
||||
{
|
||||
rollback_version = wr->version;
|
||||
}
|
||||
rollback_version = wr->version;
|
||||
continue;
|
||||
}
|
||||
if (wr->type() == BS_HEAP_COMMIT && wr->lsn <= fsynced_lsn)
|
||||
@@ -2376,7 +2200,7 @@ void blockstore_heap_t::use_data(inode_t inode, uint64_t location)
|
||||
{
|
||||
auto sh_it = pool_shard_settings.find(INODE_POOL(inode));
|
||||
if (sh_it != pool_shard_settings.end() && sh_it->second.no_inode_stats)
|
||||
inode = INODE_WITH_POOL(INODE_POOL(inode), 0);
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
assert(!data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, true);
|
||||
inode_space_stats[inode] += dsk->data_block_size;
|
||||
@@ -2387,7 +2211,7 @@ void blockstore_heap_t::free_data(inode_t inode, uint64_t location)
|
||||
{
|
||||
auto sh_it = pool_shard_settings.find(INODE_POOL(inode));
|
||||
if (sh_it != pool_shard_settings.end() && sh_it->second.no_inode_stats)
|
||||
inode = INODE_WITH_POOL(INODE_POOL(inode), 0);
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
assert(data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, false);
|
||||
auto sp_it = inode_space_stats.find(inode);
|
||||
@@ -2424,8 +2248,7 @@ void blockstore_heap_t::use_buffer_area(inode_t inode, uint64_t location, uint64
|
||||
return;
|
||||
}
|
||||
assert(!(size % dsk->bitmap_granularity));
|
||||
bool ok = buffer_alloc->use(location / dsk->bitmap_granularity, size / dsk->bitmap_granularity);
|
||||
assert(ok);
|
||||
buffer_alloc->use(location / dsk->bitmap_granularity, size / dsk->bitmap_granularity);
|
||||
buffer_area_used_space += size;
|
||||
}
|
||||
|
||||
@@ -2467,7 +2290,7 @@ void blockstore_heap_t::get_meta_block(uint32_t block_num, uint8_t *buffer)
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::fill_block_empty_space(uint8_t *buffer, uint64_t pos)
|
||||
void blockstore_heap_t::fill_block_empty_space(uint8_t *buffer, uint32_t pos)
|
||||
{
|
||||
if (pos > dsk->meta_block_size)
|
||||
{
|
||||
@@ -2555,7 +2378,7 @@ uint64_t blockstore_heap_t::get_garbage_memory()
|
||||
void blockstore_heap_t::push_inflight_lsn(uint64_t lsn, heap_entry_t *wr, uint64_t flags)
|
||||
{
|
||||
uint64_t next_inf = first_inflight_lsn + inflight_lsn.size();
|
||||
if (flags & (HEAP_INFLIGHT_COMPACTABLE|HEAP_INFLIGHT_OVERWRITE))
|
||||
if (flags & (HEAP_INFLIGHT_COMPACTABLE|HEAP_INFLIGHT_COMPACTED))
|
||||
{
|
||||
to_compact_count++;
|
||||
}
|
||||
@@ -2622,7 +2445,7 @@ void blockstore_heap_t::mark_lsn_fsynced(uint64_t lsn)
|
||||
void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
|
||||
{
|
||||
auto wr = inflight.wr;
|
||||
if (inflight.flags & HEAP_INFLIGHT_OVERWRITE)
|
||||
if (inflight.flags & HEAP_INFLIGHT_COMPACTED)
|
||||
{
|
||||
// Mark previous entries as garbage, sequentially
|
||||
mark_garbage_up_to(wr);
|
||||
@@ -2643,22 +2466,6 @@ 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;
|
||||
@@ -2668,20 +2475,25 @@ void blockstore_heap_t::unlink_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);
|
||||
if (!prev)
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
|
||||
else
|
||||
inode_map_replace(inode_idx, li_it, prev);
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
|
||||
}
|
||||
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())
|
||||
{
|
||||
|
||||
@@ -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() const;
|
||||
bool is_garbage();
|
||||
void set_garbage();
|
||||
bool is_overwrite() const;
|
||||
bool is_compactable() const;
|
||||
bool is_before(const heap_entry_t *other) const;
|
||||
bool is_overwrite();
|
||||
bool is_compactable();
|
||||
bool is_before(heap_entry_t *other);
|
||||
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);
|
||||
@@ -159,16 +159,6 @@ 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,
|
||||
@@ -219,12 +209,9 @@ class blockstore_heap_t
|
||||
|
||||
bool marked_used_blocks = false;
|
||||
bool recheck_queue_filled = false;
|
||||
std::vector<heap_list_item_t*> postponed_items;
|
||||
std::vector<heap_list_item_t*> init_erase_items;
|
||||
std::vector<heap_list_item_t*> loaded_list_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;
|
||||
@@ -233,12 +220,7 @@ 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);
|
||||
@@ -246,9 +228,8 @@ 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_items(heap_list_item_t** v, size_t count, bool postpone);
|
||||
void insert_list_item(heap_list_item_t *li);
|
||||
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);
|
||||
@@ -363,7 +344,7 @@ public:
|
||||
|
||||
// get metadata block data buffer and used space
|
||||
void get_meta_block(uint32_t block_num, uint8_t *buffer);
|
||||
void fill_block_empty_space(uint8_t *buffer, uint64_t pos);
|
||||
void fill_block_empty_space(uint8_t *buffer, uint32_t pos);
|
||||
uint32_t get_meta_block_used_space(uint32_t block_num);
|
||||
|
||||
// get space usage statistics
|
||||
|
||||
@@ -101,7 +101,6 @@ 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];
|
||||
@@ -139,13 +138,7 @@ 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
|
||||
if (!has_unfinished_sync)
|
||||
{
|
||||
wr_st = continue_sync(op);
|
||||
has_unfinished_sync = (wr_st != 2);
|
||||
}
|
||||
else
|
||||
wr_st = 0;
|
||||
wr_st = continue_sync(op);
|
||||
}
|
||||
else if (op->opcode == BS_OP_STABLE || op->opcode == BS_OP_ROLLBACK)
|
||||
{
|
||||
@@ -161,7 +154,9 @@ void blockstore_impl_t::loop()
|
||||
wr_st = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
wr_st = 0;
|
||||
}
|
||||
}
|
||||
if (wr_st == 2)
|
||||
{
|
||||
|
||||
@@ -117,12 +117,9 @@ public:
|
||||
|
||||
journal_flusher_t *flusher;
|
||||
int write_iodepth = 0;
|
||||
int inflight_big = 0;
|
||||
int intent_write_counter = 0;
|
||||
uint64_t data_fsync_next = 0;
|
||||
uint64_t data_fsync_cur = 0;
|
||||
uint64_t data_fsync_sent = 0;
|
||||
uint64_t data_fsync_done = 0;
|
||||
std::deque<bool> data_fsyncs;
|
||||
bool fsyncing_data = false;
|
||||
|
||||
bool live = false, queue_stall = false;
|
||||
ring_loop_i *ringloop = NULL;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define INIT_META_EMPTY 0
|
||||
#define INIT_META_READING 1
|
||||
#define INIT_META_READ_DONE 2
|
||||
#define INIT_META_WRITING 3
|
||||
|
||||
#define GET_SQE() \
|
||||
sqe = bs->get_sqe();\
|
||||
@@ -22,15 +23,14 @@ blockstore_init_meta::blockstore_init_meta(blockstore_impl_t *bs)
|
||||
this->bs = bs;
|
||||
}
|
||||
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num, const char *op)
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num)
|
||||
{
|
||||
if (data->res != data->iov.iov_len)
|
||||
if (data->res < 0)
|
||||
{
|
||||
throw std::runtime_error(strprintf(
|
||||
"%s failed at offset %ju: got %s (code %d), but expected %zu",
|
||||
op, (buf_num >= 0 ? bufs[buf_num].offset : last_read_offset), strerror(-data->res),
|
||||
data->res, data->iov.iov_len
|
||||
));
|
||||
throw std::runtime_error(
|
||||
std::string("read metadata failed at offset ") + std::to_string(buf_num >= 0 ? bufs[buf_num].offset : last_read_offset) +
|
||||
std::string(": ") + strerror(-data->res)
|
||||
);
|
||||
}
|
||||
if (buf_num >= 0)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ int blockstore_init_meta::loop()
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = { bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read metadata header"); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -72,19 +72,25 @@ resume_1:
|
||||
}
|
||||
if (is_zero((uint64_t*)bs->meta_superblock, bs->dsk.meta_block_size))
|
||||
{
|
||||
assert(bs->dsk.meta_format == BLOCKSTORE_META_FORMAT_HEAP);
|
||||
blockstore_meta_header_v3_t *hdr = (blockstore_meta_header_v3_t *)bs->meta_superblock;
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
hdr->completed_lsn = 0;
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
hdr->meta_area_size = bs->dsk.meta_area_size;
|
||||
hdr->set_crc32c();
|
||||
{
|
||||
blockstore_meta_header_v3_t *hdr = (blockstore_meta_header_v3_t *)bs->meta_superblock;
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_V2)
|
||||
{
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
}
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_HEAP)
|
||||
{
|
||||
hdr->meta_area_size = bs->dsk.meta_area_size;
|
||||
}
|
||||
hdr->set_crc32c();
|
||||
}
|
||||
if (bs->readonly)
|
||||
{
|
||||
printf("Skipping metadata initialization because blockstore is readonly\n");
|
||||
@@ -92,8 +98,21 @@ resume_1:
|
||||
else
|
||||
{
|
||||
printf("Initializing metadata area\n");
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_2:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 2;
|
||||
return 1;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -134,17 +153,9 @@ 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 && !zero_on_init)
|
||||
if (bs->dsk.inmemory_journal)
|
||||
{
|
||||
// Read buffer area
|
||||
printf("Reading buffered data\n");
|
||||
@@ -156,7 +167,7 @@ resume_1:
|
||||
bs->buffer_area + md_offset,
|
||||
(size_t)(bs->dsk.journal_len - md_offset < bs->metadata_buf_size ? bs->dsk.journal_len - md_offset : bs->metadata_buf_size),
|
||||
};
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read buffer area"); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.journal_fd, &data->iov, 1, bs->dsk.journal_offset + md_offset);
|
||||
md_offset += data->iov.iov_len;
|
||||
submitted++;
|
||||
@@ -175,7 +186,7 @@ resume_3:
|
||||
next_offset = md_offset;
|
||||
// Read the rest of the metadata
|
||||
resume_4:
|
||||
if (next_offset < bs->dsk.meta_area_size && submitted == 0 && (!zero_on_init || !bs->readonly))
|
||||
if (next_offset < bs->dsk.meta_area_size && submitted == 0)
|
||||
{
|
||||
// Submit one read
|
||||
for (int i = 0; i < 2; i++)
|
||||
@@ -192,15 +203,12 @@ resume_4:
|
||||
GET_SQE();
|
||||
assert(bufs[i].size <= 0x7fffffff);
|
||||
data->iov = { bufs[i].buf, (size_t)bufs[i].size };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i); };
|
||||
if (!zero_on_init)
|
||||
{
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "read metadata"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill metadata with empty block pattern
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "clear metadata"); };
|
||||
memset(bufs[i].buf, 0, bufs[i].size);
|
||||
for (uint64_t o = 0; o < bufs[i].size; o += bs->dsk.meta_block_size)
|
||||
bs->heap->fill_block_empty_space(bufs[i].buf + o, 0);
|
||||
@@ -216,14 +224,11 @@ resume_4:
|
||||
if (bufs[i].state == INIT_META_READ_DONE)
|
||||
{
|
||||
// Handle result
|
||||
if (!zero_on_init)
|
||||
{
|
||||
uint64_t loaded = 0;
|
||||
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
|
||||
if (r != 0)
|
||||
exit(1);
|
||||
entries_loaded += loaded;
|
||||
}
|
||||
uint64_t loaded = 0;
|
||||
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
|
||||
if (r != 0)
|
||||
exit(1);
|
||||
entries_loaded += loaded;
|
||||
bufs[i].state = 0;
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
@@ -233,9 +238,25 @@ resume_4:
|
||||
wait_state = 4;
|
||||
return 1;
|
||||
}
|
||||
// metadata read/clear finished
|
||||
// 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;
|
||||
@@ -316,44 +337,6 @@ resume_9:
|
||||
}
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
do_fsync:
|
||||
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, "fsync metadata"); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (zero_on_init && !header_written && !bs->readonly)
|
||||
{
|
||||
GET_SQE();
|
||||
header_written = true;
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "write metadata header"); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_2:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 2;
|
||||
return 1;
|
||||
}
|
||||
if (!bs->dsk.disable_meta_fsync)
|
||||
{
|
||||
goto do_fsync;
|
||||
}
|
||||
}
|
||||
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(),
|
||||
|
||||
@@ -17,7 +17,6 @@ class blockstore_init_meta
|
||||
int wait_state = 0;
|
||||
int wait_count = 0;
|
||||
bool zero_on_init = false;
|
||||
bool header_written = false;
|
||||
void *metadata_buffer = NULL;
|
||||
blockstore_init_meta_buf bufs[2] = {};
|
||||
int submitted = 0;
|
||||
@@ -30,7 +29,7 @@ class blockstore_init_meta
|
||||
std::vector<uint32_t> recheck_mod;
|
||||
int i = 0, j = 0;
|
||||
bool handle_meta_block(uint8_t *buf, uint64_t count, uint64_t done_cnt);
|
||||
void handle_event(ring_data_t *data, int buf_num, const char *op);
|
||||
void handle_event(ring_data_t *data, int buf_num);
|
||||
public:
|
||||
blockstore_init_meta(blockstore_impl_t *bs);
|
||||
int loop();
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#include "blockstore_mock.h"
|
||||
|
||||
blockstore_mock_t::blockstore_mock_t(const blockstore_config_t & config)
|
||||
{
|
||||
}
|
||||
|
||||
void blockstore_mock_t::parse_config(blockstore_config_t & config)
|
||||
{
|
||||
}
|
||||
|
||||
void* blockstore_mock_t::reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::reshard_continue(void *reshard_state, uint64_t chunk_limit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_mock_t::loop()
|
||||
{
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::is_started()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::is_stalled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::is_safe_to_stop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_mock_t::enqueue_op(blockstore_op_t *op)
|
||||
{
|
||||
}
|
||||
|
||||
int blockstore_mock_t::read_bitmap(object_id oid, uint64_t target_version, void *bitmap, uint64_t *result_version)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
const std::map<uint64_t, uint64_t> & blockstore_mock_t::get_inode_space_stats()
|
||||
{
|
||||
return inode_space;
|
||||
}
|
||||
|
||||
void blockstore_mock_t::set_no_inode_stats(const std::vector<uint64_t> & pool_ids)
|
||||
{
|
||||
}
|
||||
|
||||
void blockstore_mock_t::dump_diagnostics()
|
||||
{
|
||||
}
|
||||
|
||||
std::string blockstore_mock_t::get_op_diag(blockstore_op_t *op)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32_t blockstore_mock_t::get_block_size()
|
||||
{
|
||||
return block_size;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_block_count()
|
||||
{
|
||||
return block_count;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_free_block_count()
|
||||
{
|
||||
return block_count;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_journal_size()
|
||||
{
|
||||
return 32*1024*1024;
|
||||
}
|
||||
|
||||
uint32_t blockstore_mock_t::get_bitmap_granularity()
|
||||
{
|
||||
return bitmap_granularity;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_live_entries()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_live_memory()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_garbage_entries()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_garbage_memory()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "blockstore.h"
|
||||
|
||||
class blockstore_mock_t: public blockstore_i
|
||||
{
|
||||
public:
|
||||
uint32_t block_size = 128*1024;
|
||||
uint32_t bitmap_granularity = 4096;
|
||||
uint64_t block_count = 100*1024*8;
|
||||
std::map<uint64_t, uint64_t> inode_space;
|
||||
|
||||
blockstore_mock_t(const blockstore_config_t & config);
|
||||
void parse_config(blockstore_config_t & config) override;
|
||||
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit) override;
|
||||
bool reshard_continue(void *reshard_state, uint64_t chunk_limit) override;
|
||||
void loop() override;
|
||||
bool is_started() override;
|
||||
bool is_stalled() override;
|
||||
bool is_safe_to_stop() override;
|
||||
void enqueue_op(blockstore_op_t *op) override;
|
||||
int read_bitmap(object_id oid, uint64_t target_version, void *bitmap, uint64_t *result_version = NULL) override;
|
||||
const std::map<uint64_t, uint64_t> & get_inode_space_stats() override;
|
||||
void set_no_inode_stats(const std::vector<uint64_t> & pool_ids) override;
|
||||
void dump_diagnostics() override;
|
||||
std::string get_op_diag(blockstore_op_t *op) override;
|
||||
uint32_t get_block_size() override;
|
||||
uint64_t get_block_count() override;
|
||||
uint64_t get_free_block_count() override;
|
||||
uint64_t get_journal_size() override;
|
||||
uint32_t get_bitmap_granularity() override;
|
||||
uint64_t get_live_entries() override;
|
||||
uint64_t get_live_memory() override;
|
||||
uint64_t get_garbage_entries() override;
|
||||
uint64_t get_garbage_memory() override;
|
||||
};
|
||||
@@ -16,7 +16,6 @@ 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++)
|
||||
{
|
||||
@@ -28,7 +27,6 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
priv->modified_block2 = UINT32_MAX;
|
||||
int res = op->opcode == BS_OP_STABLE
|
||||
? heap->add_commit(obj, v[priv->stab_pos].version, &priv->modified_block2)
|
||||
: heap->add_rollback(obj, v[priv->stab_pos].version, &priv->modified_block2);
|
||||
@@ -38,12 +36,6 @@ 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())
|
||||
@@ -53,6 +45,11 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (priv->modified_block2 != UINT32_MAX)
|
||||
{
|
||||
priv->stab_pos--;
|
||||
goto resume_1;
|
||||
}
|
||||
priv->wait_for = WAIT_COMPACTION;
|
||||
priv->wait_detail = heap->get_compacted_count();
|
||||
flusher->request_trim();
|
||||
|
||||
@@ -9,7 +9,6 @@ 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)
|
||||
@@ -29,12 +28,9 @@ bool blockstore_impl_t::has_unsynced()
|
||||
|
||||
bool blockstore_impl_t::submit_fsyncs(int & wait_count)
|
||||
{
|
||||
int n = (unsynced_meta_write_count > 0 && !dsk.disable_meta_fsync ? 1 : 0) +
|
||||
(unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.journal_fd != dsk.meta_fd) ? 1 : 0) +
|
||||
(unsynced_data_write_count > 0 && !dsk.disable_data_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.data_fd != dsk.meta_fd) &&
|
||||
(!unsynced_buffer_write_count || dsk.data_fd != dsk.journal_fd) ? 1 : 0);
|
||||
int n = (unsynced_meta_write_count > 0 && !dsk.disable_meta_fsync) +
|
||||
(unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync && dsk.journal_fd != dsk.meta_fd) +
|
||||
(unsynced_data_write_count > 0 && !dsk.disable_data_fsync && dsk.data_fd != dsk.meta_fd && dsk.data_fd != dsk.journal_fd);
|
||||
if (ringloop->space_left() < n)
|
||||
{
|
||||
return false;
|
||||
@@ -63,8 +59,7 @@ bool blockstore_impl_t::submit_fsyncs(int & wait_count)
|
||||
data->callback = cb;
|
||||
wait_count++;
|
||||
}
|
||||
if (unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.journal_fd != dsk.meta_fd))
|
||||
if (unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync && dsk.meta_fd != dsk.journal_fd)
|
||||
{
|
||||
// fsync buffer
|
||||
io_uring_sqe *sqe = get_sqe();
|
||||
@@ -75,9 +70,7 @@ bool blockstore_impl_t::submit_fsyncs(int & wait_count)
|
||||
data->callback = cb;
|
||||
wait_count++;
|
||||
}
|
||||
if (unsynced_data_write_count > 0 && !dsk.disable_data_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.data_fd != dsk.meta_fd) &&
|
||||
(!unsynced_buffer_write_count || dsk.data_fd != dsk.journal_fd))
|
||||
if (unsynced_data_write_count > 0 && !dsk.disable_data_fsync && dsk.data_fd != dsk.meta_fd && dsk.data_fd != dsk.journal_fd)
|
||||
{
|
||||
// fsync data
|
||||
io_uring_sqe *sqe = get_sqe();
|
||||
@@ -111,11 +104,9 @@ 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;
|
||||
}
|
||||
assert(!PRIV(op)->lsn);
|
||||
PRIV(op)->lsn = heap->get_completed_lsn();
|
||||
PRIV(op)->modified_block = heap->get_completed_lsn();
|
||||
if (!submit_fsyncs(PRIV(op)->pending_ops))
|
||||
{
|
||||
PRIV(op)->lsn = 0;
|
||||
PRIV(op)->wait_detail = 1;
|
||||
PRIV(op)->wait_for = WAIT_SQE;
|
||||
return 0;
|
||||
@@ -127,6 +118,6 @@ resume_1:
|
||||
return 1;
|
||||
}
|
||||
resume_2:
|
||||
heap->mark_lsn_fsynced(PRIV(op)->lsn);
|
||||
heap->mark_lsn_fsynced(PRIV(op)->modified_block);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ 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
|
||||
);
|
||||
@@ -178,18 +177,14 @@ 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);
|
||||
if (!dsk.disable_data_fsync)
|
||||
{
|
||||
// use PRIV->lsn for fsync_data_id
|
||||
PRIV(op)->lsn = ++data_fsync_next;
|
||||
data_fsyncs.push_back(false);
|
||||
}
|
||||
PRIV(op)->pending_ops++;
|
||||
write_iodepth++;
|
||||
if (PRIV(op)->write_type == BS_HEAP_BIG_WRITE)
|
||||
{
|
||||
PRIV(op)->op_state = 1;
|
||||
inflight_big++;
|
||||
}
|
||||
else
|
||||
PRIV(op)->op_state = 3;
|
||||
}
|
||||
@@ -269,7 +264,6 @@ 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++;
|
||||
}
|
||||
@@ -300,6 +294,8 @@ again:
|
||||
goto resume_10;
|
||||
else if (op_state == 11)
|
||||
goto resume_11;
|
||||
else if (op_state == 12)
|
||||
goto resume_12;
|
||||
else
|
||||
{
|
||||
// In progress
|
||||
@@ -318,44 +314,38 @@ again:
|
||||
resume_2:
|
||||
// We must fsync all big writes to avoid complex write workflows
|
||||
// It's OK for all HDDs and for server SSDs, but slightly worse for desktop SSDs
|
||||
inflight_big--;
|
||||
if (!dsk.disable_data_fsync)
|
||||
{
|
||||
// Mark our data write as completed and advance data_fsync_cur
|
||||
data_fsyncs[PRIV(op)->lsn - data_fsync_cur - 1] = true;
|
||||
while (data_fsyncs.size() > 0 && data_fsyncs.front())
|
||||
{
|
||||
data_fsyncs.pop_front();
|
||||
data_fsync_cur++;
|
||||
}
|
||||
PRIV(op)->op_state = 11;
|
||||
// Then wait for all other data writes currently in progress to do less fsync calls
|
||||
// I.e. to fsync data in batches
|
||||
PRIV(op)->lsn = data_fsync_cur + data_fsyncs.size();
|
||||
// fsync data in a batch
|
||||
resume_11:
|
||||
if (data_fsync_cur < PRIV(op)->lsn)
|
||||
if (inflight_big > 0)
|
||||
{
|
||||
PRIV(op)->op_state = 11;
|
||||
return 1;
|
||||
}
|
||||
if (PRIV(op)->lsn > data_fsync_sent)
|
||||
if (fsyncing_data)
|
||||
{
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
io_uring_prep_fsync(sqe, dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
data->iov = { 0 };
|
||||
data->callback = [this, op, fs = data_fsync_cur](ring_data_t *data)
|
||||
resume_12:
|
||||
if (fsyncing_data)
|
||||
{
|
||||
if (fs > data_fsync_done)
|
||||
{
|
||||
data_fsync_done = fs;
|
||||
ringloop->wakeup();
|
||||
}
|
||||
};
|
||||
data_fsync_sent = data_fsync_cur;
|
||||
PRIV(op)->op_state = 12;
|
||||
return 1;
|
||||
}
|
||||
goto resume_4;
|
||||
}
|
||||
if (PRIV(op)->lsn > data_fsync_done)
|
||||
fsyncing_data = true;
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
io_uring_prep_fsync(sqe, dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
data->iov = { 0 };
|
||||
data->callback = [this, op](ring_data_t *data)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
PRIV(op)->lsn = 0;
|
||||
fsyncing_data = false;
|
||||
handle_write_event(data, op);
|
||||
};
|
||||
PRIV(op)->pending_ops++;
|
||||
PRIV(op)->op_state = 3;
|
||||
return 1;
|
||||
}
|
||||
resume_4:
|
||||
{
|
||||
@@ -463,7 +453,6 @@ 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;
|
||||
|
||||
@@ -12,7 +12,7 @@ multilist_alloc_t::multilist_alloc_t(uint32_t count, uint32_t maxn):
|
||||
count(count), maxn(maxn)
|
||||
{
|
||||
// not-so-memory-efficient: 16 MB memory per 1 GB buffer space, but buffer spaces are small, so OK
|
||||
assert(count > 1 && count < 0x80000000 && count >= maxn);
|
||||
assert(count > 1 && count < 0x80000000);
|
||||
sizes.resize(count);
|
||||
nexts.resize(count); // nexts[i] = 0 -> area is used; nexts[i] = 1 -> no next; nexts[i] >= 2 -> next item
|
||||
prevs.resize(count);
|
||||
@@ -171,7 +171,7 @@ void multilist_alloc_t::print()
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
bool multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
void multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
{
|
||||
assert(pos+size <= count && size > 0);
|
||||
if (sizes[pos] <= 0)
|
||||
@@ -182,8 +182,7 @@ bool multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
else
|
||||
while (start > 0 && !sizes[start])
|
||||
start--;
|
||||
if (sizes[start] < size+(pos-start))
|
||||
return false;
|
||||
assert(sizes[start] >= size);
|
||||
use_full(start);
|
||||
uint32_t full = sizes[start];
|
||||
sizes[pos-1] = -pos+start;
|
||||
@@ -200,8 +199,7 @@ bool multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sizes[pos] < size)
|
||||
return false;
|
||||
assert(sizes[pos] >= size);
|
||||
use_full(pos);
|
||||
if (sizes[pos] > size)
|
||||
{
|
||||
@@ -216,13 +214,12 @@ bool multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
#ifdef MULTILIST_TRACE
|
||||
print();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void multilist_alloc_t::use_full(uint32_t pos)
|
||||
{
|
||||
uint32_t prevsize = sizes[pos];
|
||||
assert(prevsize > 0);
|
||||
assert(prevsize);
|
||||
assert(nexts[pos]);
|
||||
uint32_t pi = (prevsize < maxn ? prevsize : maxn)-1;
|
||||
if (heads[pi] == pos+1)
|
||||
|
||||
@@ -17,7 +17,7 @@ struct multilist_alloc_t
|
||||
bool is_free(uint32_t pos);
|
||||
uint32_t find(uint32_t size);
|
||||
void use_full(uint32_t pos);
|
||||
bool use(uint32_t pos, uint32_t size);
|
||||
void use(uint32_t pos, uint32_t size);
|
||||
void do_free(uint32_t pos);
|
||||
void free(uint32_t pos);
|
||||
void verify();
|
||||
|
||||
@@ -141,7 +141,7 @@ struct __attribute__((__packed__)) journal_entry
|
||||
inline uint32_t je_crc32(journal_entry *je)
|
||||
{
|
||||
// 0x48674bc7 = crc32(4 zero bytes)
|
||||
return je->size < 4 ? 0 : crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
return crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
}
|
||||
|
||||
// "VITAstor"
|
||||
|
||||
+22
-55
@@ -71,11 +71,6 @@ bool journal_flusher_t::is_active()
|
||||
return active_flushers > 0 || dequeuing;
|
||||
}
|
||||
|
||||
size_t journal_flusher_t::get_queue_size()
|
||||
{
|
||||
return flush_queue.size();
|
||||
}
|
||||
|
||||
void journal_flusher_t::loop()
|
||||
{
|
||||
target_flusher_count = bs->write_iodepth*2;
|
||||
@@ -389,7 +384,6 @@ stop_flusher:
|
||||
wait_state = 0;
|
||||
return true;
|
||||
}
|
||||
copy_count = 0;
|
||||
try_trim = true;
|
||||
cur.oid = flusher->flush_queue.front();
|
||||
cur.version = flusher->flush_versions[cur.oid];
|
||||
@@ -517,31 +511,6 @@ resume_2:
|
||||
{
|
||||
uo_it->second.was_changed = true;
|
||||
}
|
||||
if (!bs->journal.inmemory)
|
||||
{
|
||||
// Verify journaled data checksums (but not COALESCED)
|
||||
for (it = v.begin(); it != v.end(); it++)
|
||||
{
|
||||
if (it->copy_flags == COPY_BUF_JOURNAL)
|
||||
{
|
||||
iovec iov = { .iov_base = it->buf, .iov_len = it->len };
|
||||
bs->verify_journal_checksums(
|
||||
it->csum_buf, it->offset, &iov, 1,
|
||||
[&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
|
||||
{
|
||||
printf(
|
||||
"Checksum mismatch in object %jx:%jx v%ju in journal at 0x%jx, checksum block #%u: got %08x, expected %08x\n",
|
||||
cur.oid.inode, cur.oid.stripe, cur.version, it->disk_offset,
|
||||
bad_block / bs->dsk.csum_block_size, calc_csum, stored_csum
|
||||
);
|
||||
bad_block += it->offset;
|
||||
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
|
||||
mangle_csum_blocks.insert(bad_block);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Submit data writes
|
||||
for (it = v.begin(); it != v.end(); it++)
|
||||
@@ -551,7 +520,6 @@ 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
|
||||
);
|
||||
@@ -665,7 +633,6 @@ resume_2:
|
||||
}
|
||||
// All done
|
||||
flusher->active_flushers--;
|
||||
copy_count = 0; // used by is_mutated()...
|
||||
wait_state = 0;
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -782,7 +749,6 @@ 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
|
||||
);
|
||||
@@ -847,21 +813,35 @@ bool journal_flusher_co::clear_incomplete_csum_block_bits(int wait_base)
|
||||
bs->verify_padded_checksums(new_clean_bitmap, new_clean_bitmap + 2*bs->dsk.clean_entry_bitmap_size,
|
||||
v[i].offset, &iov, 1, [&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
|
||||
{
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in data area at offset 0x%jx+0x%x during flush: got %08x, expected %08x\n",
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in data area at offset 0x%jx+0x%x: got %08x, expected %08x\n",
|
||||
cur.oid.inode, cur.oid.stripe, old_clean_ver, old_clean_loc, bad_block, calc_csum, stored_csum);
|
||||
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
|
||||
mangle_csum_blocks.insert(bad_block);
|
||||
for (uint32_t j = 0; j < bs->dsk.csum_block_size; j += bs->dsk.bitmap_granularity)
|
||||
{
|
||||
// Simplest method of mangling: flip one byte in every sector
|
||||
((uint8_t*)v[i].buf)[j+bad_block-v[i].offset] ^= 0xff;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
bs->verify_journal_checksums(v[i].csum_buf, v[i].offset, &iov, 1, [&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
|
||||
{
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in journal at offset 0x%jx+0x%x (block offset 0x%jx) during flush: got %08x, expected %08x\n",
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in journal at offset 0x%jx+0x%x (block offset 0x%jx): got %08x, expected %08x\n",
|
||||
cur.oid.inode, cur.oid.stripe, old_clean_ver,
|
||||
v[i].disk_offset, bad_block, v[i].offset, calc_csum, stored_csum);
|
||||
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
|
||||
mangle_csum_blocks.insert(bad_block);
|
||||
bad_block += (v[i].offset/bs->dsk.csum_block_size) * bs->dsk.csum_block_size;
|
||||
uint32_t bad_block_end = bad_block + bs->dsk.csum_block_size + (v[i].offset/bs->dsk.csum_block_size) * bs->dsk.csum_block_size;
|
||||
if (bad_block < v[i].offset)
|
||||
bad_block = v[i].offset;
|
||||
if (bad_block_end > v[i].offset+v[i].len)
|
||||
bad_block_end = v[i].offset+v[i].len;
|
||||
bad_block -= v[i].offset;
|
||||
bad_block_end -= v[i].offset;
|
||||
for (uint32_t j = bad_block; j < bad_block_end; j += bs->dsk.bitmap_granularity)
|
||||
{
|
||||
// Simplest method of mangling: flip one byte in every sector
|
||||
((uint8_t*)v[i].buf)[j] ^= 0xff;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -970,11 +950,6 @@ void journal_flusher_co::calc_block_checksums(uint32_t *new_data_csums, bool ski
|
||||
}
|
||||
// `v` should contain aligned items, possibly split into pieces
|
||||
assert(!block_done);
|
||||
for (uint32_t mangle_block: mangle_csum_blocks)
|
||||
{
|
||||
// Flip 1 bit
|
||||
new_data_csums[mangle_block / bs->dsk.csum_block_size] ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void journal_flusher_co::scan_dirty()
|
||||
@@ -1111,8 +1086,7 @@ void journal_flusher_co::scan_dirty()
|
||||
last--;
|
||||
read_to_fill_incomplete = bs->fill_partial_checksum_blocks(
|
||||
v, fulfilled, bmp_ptr, NULL, false, NULL, v[0].offset/bs->dsk.csum_block_size * bs->dsk.csum_block_size,
|
||||
((v[last].offset+v[last].len-1) / bs->dsk.csum_block_size + 1) * bs->dsk.csum_block_size,
|
||||
0, bs->dsk.data_block_size
|
||||
((v[last].offset+v[last].len-1) / bs->dsk.csum_block_size + 1) * bs->dsk.csum_block_size
|
||||
);
|
||||
}
|
||||
else if (fill_incomplete && clean_init_bitmap)
|
||||
@@ -1142,7 +1116,6 @@ bool journal_flusher_co::read_dirty(int wait_base)
|
||||
if (wait_state == wait_base) goto resume_0;
|
||||
else if (wait_state == wait_base+1) goto resume_1;
|
||||
wait_count = wait_journal_count = 0;
|
||||
mangle_csum_blocks.clear();
|
||||
if (bs->journal.inmemory && !read_to_fill_incomplete)
|
||||
{
|
||||
// Happy path: nothing to read :)
|
||||
@@ -1374,7 +1347,7 @@ bool journal_flusher_co::fsync_batch(bool fsync_meta, int wait_base)
|
||||
cur_sync->ready_count++;
|
||||
flusher->syncing_flushers++;
|
||||
resume_1:
|
||||
if (cur_sync->state == 0)
|
||||
if (!cur_sync->state)
|
||||
{
|
||||
if (flusher->syncing_flushers >= flusher->active_flushers || !flusher->flush_queue.size())
|
||||
{
|
||||
@@ -1402,12 +1375,6 @@ bool journal_flusher_co::fsync_batch(bool fsync_meta, int wait_base)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (cur_sync->state == 1)
|
||||
{
|
||||
// Wait for fsync completion
|
||||
wait_state = wait_base+1;
|
||||
return false;
|
||||
}
|
||||
flusher->syncing_flushers--;
|
||||
cur_sync->ready_count--;
|
||||
if (cur_sync->ready_count == 0)
|
||||
|
||||
@@ -66,7 +66,6 @@ class journal_flusher_co
|
||||
uint64_t clean_bitmap_offset, clean_bitmap_len;
|
||||
uint8_t *clean_init_dyn_ptr;
|
||||
uint8_t *new_clean_bitmap;
|
||||
std::unordered_set<uint32_t> mangle_csum_blocks;
|
||||
|
||||
uint64_t new_trim_pos;
|
||||
|
||||
@@ -124,7 +123,6 @@ public:
|
||||
void loop();
|
||||
bool is_trim_wanted() { return trim_wanted; }
|
||||
bool is_active();
|
||||
size_t get_queue_size();
|
||||
void mark_trim_possible();
|
||||
void request_trim();
|
||||
void release_trim();
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
|
||||
namespace v1 {
|
||||
|
||||
blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd, bool mock_mode)
|
||||
blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd)
|
||||
{
|
||||
assert(sizeof(blockstore_op_private_t) <= BS_OP_PRIVATE_DATA_SIZE);
|
||||
this->tfd = tfd;
|
||||
this->ringloop = ringloop;
|
||||
dsk.mock_mode = mock_mode;
|
||||
ring_consumer.loop = [this]() { loop(); };
|
||||
ringloop->register_consumer(&ring_consumer);
|
||||
initialized = 0;
|
||||
@@ -36,11 +35,6 @@ blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *
|
||||
|
||||
blockstore_impl_t::~blockstore_impl_t()
|
||||
{
|
||||
for (auto& obj: dirty_db)
|
||||
{
|
||||
if (obj.second.dyn_data)
|
||||
free(obj.second.dyn_data);
|
||||
}
|
||||
delete data_alloc;
|
||||
delete flusher;
|
||||
if (zero_object)
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
|
||||
//#define BLOCKSTORE_DEBUG
|
||||
|
||||
struct bs_test_t;
|
||||
|
||||
namespace v1 {
|
||||
|
||||
#include "journal.h"
|
||||
@@ -98,7 +96,7 @@ struct blockstore_op_private_t
|
||||
int op_state;
|
||||
|
||||
// Read
|
||||
uint64_t clean_loc_used;
|
||||
uint64_t clean_block_used;
|
||||
std::vector<copy_buffer_t> read_vec;
|
||||
|
||||
// Sync, write
|
||||
@@ -124,7 +122,6 @@ typedef uint64_t pool_pg_id_t;
|
||||
|
||||
class blockstore_impl_t: public blockstore_i
|
||||
{
|
||||
friend struct ::bs_test_t;
|
||||
blockstore_disk_t dsk;
|
||||
|
||||
/******* OPTIONS *******/
|
||||
@@ -223,7 +220,6 @@ class blockstore_impl_t: public blockstore_i
|
||||
|
||||
// Read
|
||||
int dequeue_read(blockstore_op_t *read_op);
|
||||
void release_clean(blockstore_op_t *op);
|
||||
void find_holes(std::vector<copy_buffer_t> & read_vec, uint32_t item_start, uint32_t item_end,
|
||||
std::function<int(int, bool, uint32_t, uint32_t)> callback);
|
||||
int fulfill_read(blockstore_op_t *read_op,
|
||||
@@ -234,8 +230,7 @@ class blockstore_impl_t: public blockstore_i
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data,
|
||||
uint32_t item_start, uint32_t item_end, uint64_t clean_loc, uint64_t clean_ver);
|
||||
int fill_partial_checksum_blocks(std::vector<copy_buffer_t> & rv, uint64_t & fulfilled,
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf,
|
||||
uint32_t read_offset, uint32_t read_end, uint32_t item_start, uint32_t item_end);
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf, uint64_t read_offset, uint64_t read_end);
|
||||
int pad_journal_read(std::vector<copy_buffer_t> & rv, copy_buffer_t & cp,
|
||||
uint64_t dirty_offset, uint64_t dirty_end, uint64_t dirty_loc, uint8_t *csum_ptr, int *dyn_data,
|
||||
uint64_t offset, uint64_t submit_len, uint64_t & blk_begin, uint64_t & blk_end, uint8_t* & blk_buf);
|
||||
@@ -286,7 +281,7 @@ class blockstore_impl_t: public blockstore_i
|
||||
|
||||
public:
|
||||
|
||||
blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd, bool mock_mode = false);
|
||||
blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd);
|
||||
~blockstore_impl_t();
|
||||
|
||||
void parse_config(blockstore_config_t & config);
|
||||
|
||||
+68
-83
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#include "str_util.h"
|
||||
#include "impl.h"
|
||||
#include "internal.h"
|
||||
|
||||
@@ -31,15 +30,14 @@ blockstore_init_meta::blockstore_init_meta(blockstore_impl_t *bs)
|
||||
this->bs = bs;
|
||||
}
|
||||
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num, const char *op)
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num)
|
||||
{
|
||||
if (data->res != data->iov.iov_len)
|
||||
if (data->res < 0)
|
||||
{
|
||||
throw std::runtime_error(strprintf(
|
||||
"%s failed at offset %ju: got %s (code %d), but expected %zu",
|
||||
op, (buf_num >= 0 ? bufs[buf_num].offset : last_read_offset), strerror(-data->res),
|
||||
data->res, data->iov.iov_len
|
||||
));
|
||||
throw std::runtime_error(
|
||||
std::string("read metadata failed at offset ") + std::to_string(buf_num >= 0 ? bufs[buf_num].offset : last_read_offset) +
|
||||
std::string(": ") + strerror(-data->res)
|
||||
);
|
||||
}
|
||||
if (buf_num >= 0)
|
||||
{
|
||||
@@ -67,11 +65,10 @@ int blockstore_init_meta::loop()
|
||||
if (!metadata_buffer)
|
||||
throw std::runtime_error("Failed to allocate metadata read buffer");
|
||||
// Read superblock
|
||||
hdr = (blockstore_meta_header_v2_t *)memalign_or_die(MEM_ALIGNMENT, bs->dsk.meta_block_size);
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = { hdr, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read metadata header"); };
|
||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -81,8 +78,24 @@ resume_1:
|
||||
wait_state = 1;
|
||||
return 1;
|
||||
}
|
||||
if (iszero((uint64_t*)hdr, bs->dsk.meta_block_size / sizeof(uint64_t)))
|
||||
if (iszero((uint64_t*)metadata_buffer, bs->dsk.meta_block_size / sizeof(uint64_t)))
|
||||
{
|
||||
{
|
||||
blockstore_meta_header_v2_t *hdr = (blockstore_meta_header_v2_t *)metadata_buffer;
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_V2)
|
||||
{
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
hdr->header_csum = 0;
|
||||
hdr->header_csum = crc32c(0, hdr, sizeof(*hdr));
|
||||
}
|
||||
}
|
||||
if (bs->readonly)
|
||||
{
|
||||
printf("Skipping metadata initialization because blockstore is readonly\n");
|
||||
@@ -90,11 +103,25 @@ resume_1:
|
||||
else
|
||||
{
|
||||
printf("Initializing metadata area\n");
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_3:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 3;
|
||||
return 1;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
blockstore_meta_header_v2_t *hdr = (blockstore_meta_header_v2_t *)metadata_buffer;
|
||||
if (hdr->zero != 0 || hdr->magic != BLOCKSTORE_META_MAGIC_V1 || hdr->version < BLOCKSTORE_META_FORMAT_V1)
|
||||
{
|
||||
printf(
|
||||
@@ -196,15 +223,12 @@ resume_2:
|
||||
GET_SQE();
|
||||
assert(bufs[i].size <= 0x7fffffff);
|
||||
data->iov = { bufs[i].buf, (size_t)bufs[i].size };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i); };
|
||||
if (!zero_on_init)
|
||||
{
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "read metadata"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill metadata with zeroes
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "clear metadata"); };
|
||||
memset(data->iov.iov_base, 0, data->iov.iov_len);
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
}
|
||||
@@ -232,7 +256,7 @@ resume_2:
|
||||
GET_SQE();
|
||||
assert(bufs[i].size <= 0x7fffffff);
|
||||
data->iov = { bufs[i].buf, (size_t)bufs[i].size };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "write metadata"); };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
bs->ringloop->submit();
|
||||
bufs[i].state = INIT_META_WRITING;
|
||||
@@ -261,7 +285,7 @@ resume_2:
|
||||
GET_SQE();
|
||||
last_read_offset = (1+next_offset)*bs->dsk.meta_block_size;
|
||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read metadata"); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -278,7 +302,7 @@ resume_5:
|
||||
}
|
||||
GET_SQE();
|
||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "write metadata"); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -293,64 +317,27 @@ resume_6:
|
||||
}
|
||||
// metadata read finished
|
||||
printf("Metadata entries loaded: %ju, free blocks: %ju / %ju\n", entries_loaded, bs->data_alloc->get_free_count(), bs->dsk.block_count);
|
||||
if (zero_on_init && !bs->readonly)
|
||||
{
|
||||
do_fsync:
|
||||
if (!bs->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, "fsync metadata"); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_4:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 4;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!header_written)
|
||||
{
|
||||
GET_SQE();
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_V2)
|
||||
{
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
hdr->header_csum = 0;
|
||||
hdr->header_csum = crc32c(0, hdr, sizeof(*hdr));
|
||||
}
|
||||
header_written = true;
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ hdr, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "write metadata header"); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_3:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 3;
|
||||
return 1;
|
||||
}
|
||||
goto do_fsync;
|
||||
}
|
||||
}
|
||||
if (!bs->inmemory_meta)
|
||||
{
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
}
|
||||
free(hdr);
|
||||
hdr = NULL;
|
||||
if (zero_on_init && !bs->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_4:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 4;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -358,8 +345,6 @@ bool blockstore_init_meta::handle_meta_block(uint8_t *buf, uint64_t entries_per_
|
||||
{
|
||||
bool updated = false;
|
||||
uint64_t max_i = entries_per_block;
|
||||
if (done_cnt > bs->dsk.block_count)
|
||||
return false;
|
||||
if (max_i > bs->dsk.block_count-done_cnt)
|
||||
max_i = bs->dsk.block_count-done_cnt;
|
||||
for (uint64_t i = 0; i < max_i; i++)
|
||||
@@ -470,21 +455,21 @@ blockstore_init_journal::blockstore_init_journal(blockstore_impl_t *bs)
|
||||
};
|
||||
}
|
||||
|
||||
void blockstore_init_journal::handle_event(ring_data_t *data)
|
||||
void blockstore_init_journal::handle_event(ring_data_t *data1)
|
||||
{
|
||||
if (data->res != data->iov.iov_len)
|
||||
if (data1->res <= 0)
|
||||
{
|
||||
throw std::runtime_error(strprintf(
|
||||
"read journal failed at offset %ju: got %s (code %d), but expected %zu",
|
||||
journal_pos, strerror(-data->res), data->res, data->iov.iov_len
|
||||
));
|
||||
throw std::runtime_error(
|
||||
std::string("read journal failed at offset ") + std::to_string(journal_pos) +
|
||||
std::string(": ") + strerror(-data1->res)
|
||||
);
|
||||
}
|
||||
done.push_back({
|
||||
.buf = submitted_buf,
|
||||
.pos = journal_pos,
|
||||
.len = (uint64_t)data->res,
|
||||
.len = (uint64_t)data1->res,
|
||||
});
|
||||
journal_pos += data->res;
|
||||
journal_pos += data1->res;
|
||||
if (journal_pos >= bs->journal.len)
|
||||
{
|
||||
// Continue from the beginning
|
||||
|
||||
@@ -16,9 +16,7 @@ class blockstore_init_meta
|
||||
blockstore_impl_t *bs;
|
||||
int wait_state = 0;
|
||||
bool zero_on_init = false;
|
||||
bool header_written = false;
|
||||
void *metadata_buffer = NULL;
|
||||
blockstore_meta_header_v2_t *hdr = NULL;
|
||||
blockstore_init_meta_buf bufs[2] = {};
|
||||
int submitted = 0;
|
||||
struct io_uring_sqe *sqe;
|
||||
@@ -31,7 +29,7 @@ class blockstore_init_meta
|
||||
int i = 0, j = 0;
|
||||
std::vector<uint64_t> entries_to_zero;
|
||||
bool handle_meta_block(uint8_t *buf, uint64_t count, uint64_t done_cnt);
|
||||
void handle_event(ring_data_t *data, int buf_num, const char *op);
|
||||
void handle_event(ring_data_t *data, int buf_num);
|
||||
public:
|
||||
blockstore_init_meta(blockstore_impl_t *bs);
|
||||
int loop();
|
||||
|
||||
@@ -193,7 +193,6 @@ 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
|
||||
);
|
||||
|
||||
+54
-145
@@ -101,8 +101,8 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
|
||||
.copy_flags = COPY_BUF_JOURNAL|COPY_BUF_CSUM_FILL,
|
||||
.offset = blk_begin,
|
||||
.len = blk_end-blk_begin,
|
||||
.csum_buf = (!csum ? NULL : (csum + (blk_begin/dsk.csum_block_size -
|
||||
item_start/dsk.csum_block_size) * (dsk.data_csum_type & 0xFF))),
|
||||
.csum_buf = (csum + (blk_begin/dsk.csum_block_size -
|
||||
item_start/dsk.csum_block_size) * (dsk.data_csum_type & 0xFF)),
|
||||
.dyn_data = dyn_data,
|
||||
});
|
||||
if (dyn_data)
|
||||
@@ -134,7 +134,7 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
|
||||
// If we don't track it then we may IN THEORY read another object's data:
|
||||
// submit read -> remove the object -> flush remove -> overwrite with another object -> finish read
|
||||
// Very improbable, but possible
|
||||
PRIV(read_op)->clean_loc_used = UINT64_MAX;
|
||||
PRIV(read_op)->clean_block_used = 1;
|
||||
}
|
||||
rv.insert(rv.begin() + pos, el);
|
||||
fulfilled += el.len;
|
||||
@@ -167,8 +167,7 @@ uint8_t* blockstore_impl_t::get_clean_entry_bitmap(uint64_t block_loc, int offse
|
||||
}
|
||||
|
||||
int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> & rv, uint64_t & fulfilled,
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf,
|
||||
uint32_t read_offset, uint32_t read_end, uint32_t item_start, uint32_t item_end)
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf, uint64_t read_offset, uint64_t read_end)
|
||||
{
|
||||
if (read_end == read_offset)
|
||||
return 0;
|
||||
@@ -176,38 +175,10 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
|
||||
read_buf -= read_offset;
|
||||
uint32_t last_block = (read_end-1)/dsk.csum_block_size;
|
||||
uint32_t start_block = read_offset/dsk.csum_block_size;
|
||||
uint32_t item_start_block = item_start/dsk.csum_block_size;
|
||||
uint32_t end_block = 0;
|
||||
auto zero_range = [&](int pos, bool alloc, uint32_t cur_start, uint32_t cur_end)
|
||||
{
|
||||
if (alloc)
|
||||
return 0;
|
||||
copy_buffer_t el = {
|
||||
.copy_flags = COPY_BUF_ZERO,
|
||||
.offset = cur_start,
|
||||
.len = cur_end-cur_start,
|
||||
};
|
||||
rv.insert(rv.begin() + pos, el);
|
||||
if (read_buf)
|
||||
memset(read_buf + el.offset - read_offset, 0, el.len);
|
||||
fulfilled += el.len;
|
||||
return 1;
|
||||
};
|
||||
if (read_offset < item_start)
|
||||
{
|
||||
// Zero-fill the beginning
|
||||
find_holes(rv, read_offset, item_start, zero_range);
|
||||
read_offset = item_start;
|
||||
}
|
||||
if (read_end > item_end)
|
||||
{
|
||||
// Zero-fill the end
|
||||
find_holes(rv, item_end, read_end, zero_range);
|
||||
read_end = item_end;
|
||||
}
|
||||
while (start_block <= last_block)
|
||||
{
|
||||
if (read_range_fulfilled(rv, fulfilled, read_buf, from_journal ? NULL : clean_entry_bitmap,
|
||||
if (read_range_fulfilled(rv, fulfilled, read_buf, clean_entry_bitmap,
|
||||
start_block*dsk.csum_block_size < read_offset ? read_offset : start_block*dsk.csum_block_size,
|
||||
(start_block+1)*dsk.csum_block_size > read_end ? read_end : (start_block+1)*dsk.csum_block_size))
|
||||
{
|
||||
@@ -219,7 +190,7 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
|
||||
// Find a sequence of checksum blocks required to be read
|
||||
end_block = start_block;
|
||||
while ((end_block+1)*dsk.csum_block_size < read_end &&
|
||||
!read_range_fulfilled(rv, fulfilled, read_buf, from_journal ? NULL : clean_entry_bitmap,
|
||||
!read_range_fulfilled(rv, fulfilled, read_buf, clean_entry_bitmap,
|
||||
(end_block+1)*dsk.csum_block_size < read_offset ? read_offset : (end_block+1)*dsk.csum_block_size,
|
||||
(end_block+2)*dsk.csum_block_size > read_end ? read_end : (end_block+2)*dsk.csum_block_size))
|
||||
{
|
||||
@@ -231,10 +202,8 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
|
||||
.copy_flags = COPY_BUF_CSUM_FILL | (from_journal ? COPY_BUF_JOURNALED_BIG : 0),
|
||||
.offset = start_block*dsk.csum_block_size,
|
||||
.len = (end_block-start_block)*dsk.csum_block_size,
|
||||
// save checksum reference if we're reading clean data from the journal
|
||||
.csum_buf = from_journal
|
||||
? clean_entry_bitmap + dsk.clean_entry_bitmap_size + (start_block-item_start_block)*(dsk.data_csum_type & 0xFF)
|
||||
: NULL,
|
||||
// save clean_entry_bitmap if we're reading clean data from the journal
|
||||
.csum_buf = from_journal ? clean_entry_bitmap : NULL,
|
||||
.dyn_data = dyn_data,
|
||||
});
|
||||
if (dyn_data)
|
||||
@@ -257,11 +226,6 @@ bool blockstore_impl_t::read_range_fulfilled(std::vector<copy_buffer_t> & rv, ui
|
||||
{
|
||||
if (alloc)
|
||||
return 0;
|
||||
if (!clean_entry_bitmap)
|
||||
{
|
||||
all_done = false;
|
||||
return 0;
|
||||
}
|
||||
int diff = 0;
|
||||
uint32_t bmp_start = cur_start/dsk.bitmap_granularity;
|
||||
uint32_t bmp_end = cur_end/dsk.bitmap_granularity;
|
||||
@@ -359,7 +323,7 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
|
||||
{
|
||||
iov[n_iov++] = (struct iovec){ (uint8_t*)op->buf+cur_start-op->offset, lim_end-cur_start };
|
||||
rv.insert(rv.begin() + pos, (copy_buffer_t){
|
||||
.copy_flags = COPY_BUF_DATA|COPY_BUF_COALESCED,
|
||||
.copy_flags = COPY_BUF_DATA,
|
||||
.offset = cur_start,
|
||||
.len = lim_end-cur_start,
|
||||
});
|
||||
@@ -397,10 +361,10 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
|
||||
PRIV(op)->pending_ops++;
|
||||
io_uring_prep_readv(sqe, submit_fd, iov + n_pos, n_cur, submit_offset + clean_loc + item_start + d_pos);
|
||||
data->callback = [this, op](ring_data_t *data) { handle_read_event(data, op); };
|
||||
if (n_pos > 0 || n_iov > IOV_MAX)
|
||||
if (n_pos > 0 || n_pos + IOV_MAX < n_iov)
|
||||
{
|
||||
uint32_t d_len = 0;
|
||||
for (int i = 0; i < n_cur; i++)
|
||||
for (int i = 0; i < IOV_MAX; i++)
|
||||
d_len += iov[n_pos+i].iov_len;
|
||||
data->iov.iov_len = d_len;
|
||||
d_pos += d_len;
|
||||
@@ -412,7 +376,7 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
|
||||
{
|
||||
// Reads running parallel to flushes of the same clean block may read
|
||||
// a mixture of old and new data. So we don't verify checksums for such blocks.
|
||||
PRIV(op)->clean_loc_used = UINT64_MAX;
|
||||
PRIV(op)->clean_block_used = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -438,7 +402,7 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
|
||||
}
|
||||
uint64_t fulfilled = 0;
|
||||
PRIV(read_op)->pending_ops = 0;
|
||||
PRIV(read_op)->clean_loc_used = 0;
|
||||
PRIV(read_op)->clean_block_used = 0;
|
||||
auto & rv = PRIV(read_op)->read_vec;
|
||||
uint64_t result_version = 0;
|
||||
if (dirty_found)
|
||||
@@ -551,50 +515,26 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
|
||||
return 2;
|
||||
undo_read:
|
||||
// need to wait. undo added requests, don't dequeue op
|
||||
release_clean(read_op);
|
||||
for (auto & vec: rv)
|
||||
if (dsk.csum_block_size > dsk.bitmap_granularity)
|
||||
{
|
||||
if ((vec.copy_flags & COPY_BUF_CSUM_FILL) && vec.buf)
|
||||
for (auto & vec: rv)
|
||||
{
|
||||
free(vec.buf);
|
||||
vec.buf = NULL;
|
||||
}
|
||||
if (vec.dyn_data && --(*vec.dyn_data) == 0) // refcount
|
||||
{
|
||||
free(vec.dyn_data);
|
||||
vec.dyn_data = NULL;
|
||||
if ((vec.copy_flags & COPY_BUF_CSUM_FILL) && vec.buf)
|
||||
{
|
||||
free(vec.buf);
|
||||
vec.buf = NULL;
|
||||
}
|
||||
if (vec.dyn_data && --(*vec.dyn_data) == 0) // refcount
|
||||
{
|
||||
free(vec.dyn_data);
|
||||
vec.dyn_data = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
rv.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blockstore_impl_t::release_clean(blockstore_op_t *op)
|
||||
{
|
||||
if (PRIV(op)->clean_loc_used == UINT64_MAX)
|
||||
{
|
||||
PRIV(op)->clean_loc_used = 0;
|
||||
}
|
||||
if (PRIV(op)->clean_loc_used)
|
||||
{
|
||||
// Release clean data block
|
||||
auto uo_it = used_clean_objects.find(PRIV(op)->clean_loc_used - 1);
|
||||
if (uo_it != used_clean_objects.end())
|
||||
{
|
||||
uo_it->second.refs--;
|
||||
if (uo_it->second.refs <= 0)
|
||||
{
|
||||
if (uo_it->second.was_freed)
|
||||
{
|
||||
data_alloc->set((PRIV(op)->clean_loc_used - 1) / dsk.data_block_size, false);
|
||||
}
|
||||
used_clean_objects.erase(uo_it);
|
||||
}
|
||||
}
|
||||
PRIV(op)->clean_loc_used = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int blockstore_impl_t::pad_journal_read(std::vector<copy_buffer_t> & rv, copy_buffer_t & cp,
|
||||
// FIXME Passing dirty_entry& would be nicer
|
||||
uint64_t dirty_offset, uint64_t dirty_end, uint64_t dirty_loc, uint8_t *csum_ptr, int *dyn_data,
|
||||
@@ -658,15 +598,11 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
{
|
||||
auto & rv = PRIV(read_op)->read_vec;
|
||||
int req = fill_partial_checksum_blocks(rv, fulfilled, clean_entry_bitmap, dyn_data, from_journal,
|
||||
(uint8_t*)read_op->buf, read_op->offset, read_op->offset+read_op->len, item_start, item_end);
|
||||
(uint8_t*)read_op->buf, read_op->offset, read_op->offset+read_op->len);
|
||||
if (!inmemory_meta && !from_journal && req > 0)
|
||||
{
|
||||
// Read checksums from disk
|
||||
uint8_t *csum_buf = read_clean_meta_block(read_op, clean_loc, rv.size()-req);
|
||||
if (!csum_buf)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = req; i > 0; i--)
|
||||
{
|
||||
rv[rv.size()-i].csum_buf = csum_buf;
|
||||
@@ -679,7 +615,7 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PRIV(read_op)->clean_loc_used = req > 0 ? UINT64_MAX : 0;
|
||||
PRIV(read_op)->clean_block_used = req > 0;
|
||||
}
|
||||
else if (from_journal)
|
||||
{
|
||||
@@ -729,10 +665,6 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
{
|
||||
// Read checksums from disk
|
||||
csum_buf = read_clean_meta_block(read_op, clean_loc, PRIV(read_op)->read_vec.size());
|
||||
if (!csum_buf)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
csum_done = true;
|
||||
}
|
||||
uint8_t *csum = !dsk.csum_block_size ? 0 : (csum_buf + 2*dsk.clean_entry_bitmap_size + bmp_start*(dsk.data_csum_type & 0xFF));
|
||||
@@ -747,13 +679,13 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
}
|
||||
}
|
||||
// Increment reference counter if clean data is being read from the disk
|
||||
if (PRIV(read_op)->clean_loc_used == UINT64_MAX)
|
||||
if (PRIV(read_op)->clean_block_used)
|
||||
{
|
||||
auto & uo = used_clean_objects[clean_loc];
|
||||
uo.refs++;
|
||||
if (dsk.csum_block_size && flusher->is_mutated(clean_loc))
|
||||
uo.was_changed = true;
|
||||
PRIV(read_op)->clean_loc_used = clean_loc + 1;
|
||||
PRIV(read_op)->clean_block_used = clean_loc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -793,18 +725,12 @@ bool blockstore_impl_t::verify_padded_checksums(uint8_t *clean_entry_bitmap, uin
|
||||
while (pos < iov[i].iov_len)
|
||||
{
|
||||
uint32_t start = pos;
|
||||
uint8_t bit = 1;
|
||||
if (clean_entry_bitmap)
|
||||
uint8_t bit = (clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1;
|
||||
while (pos < iov[i].iov_len && ((clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1) == bit)
|
||||
{
|
||||
bit = (clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1;
|
||||
while (pos < iov[i].iov_len && ((clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1) == bit)
|
||||
{
|
||||
pos += dsk.bitmap_granularity;
|
||||
bmp_pos++;
|
||||
}
|
||||
pos += dsk.bitmap_granularity;
|
||||
bmp_pos++;
|
||||
}
|
||||
else
|
||||
pos = iov[i].iov_len;
|
||||
uint32_t len = pos-start;
|
||||
auto buf = (uint8_t*)iov[i].iov_base+start;
|
||||
while (block_done+len >= dsk.csum_block_size)
|
||||
@@ -881,7 +807,7 @@ bool blockstore_impl_t::verify_clean_padded_checksums(blockstore_op_t *op, uint6
|
||||
{
|
||||
uint32_t offset = clean_loc % dsk.data_block_size;
|
||||
if (from_journal)
|
||||
return verify_padded_checksums(NULL, dyn_data, offset, iov, n_iov, bad_block_cb);
|
||||
return verify_padded_checksums(dyn_data, dyn_data + dsk.clean_entry_bitmap_size, offset, iov, n_iov, bad_block_cb);
|
||||
clean_loc = (clean_loc / dsk.data_block_size) * dsk.data_block_size;
|
||||
if (!dyn_data)
|
||||
{
|
||||
@@ -909,7 +835,7 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
|
||||
void *meta_block = NULL;
|
||||
if (dsk.csum_block_size > dsk.bitmap_granularity)
|
||||
{
|
||||
for (int i = 0; i < rv.size(); i++)
|
||||
for (int i = rv.size()-1; i >= 0 && (rv[i].copy_flags & COPY_BUF_CSUM_FILL); i--)
|
||||
{
|
||||
if (rv[i].copy_flags & COPY_BUF_META_BLOCK)
|
||||
{
|
||||
@@ -919,41 +845,8 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
|
||||
rv[i].buf = NULL;
|
||||
continue;
|
||||
}
|
||||
if (rv[i].copy_flags & COPY_BUF_ZERO)
|
||||
{
|
||||
// Zero read
|
||||
continue;
|
||||
}
|
||||
if (rv[i].copy_flags & COPY_BUF_COALESCED)
|
||||
{
|
||||
// Sub-block shared with another read. Skip
|
||||
continue;
|
||||
}
|
||||
if ((rv[i].copy_flags & COPY_BUF_JOURNAL) && journal.inmemory)
|
||||
{
|
||||
// Do not check journal checksums in-memory
|
||||
continue;
|
||||
}
|
||||
iovec single_iov = {};
|
||||
iovec *iov = NULL;
|
||||
int n_iov = 0;
|
||||
if (rv[i].copy_flags & COPY_BUF_CSUM_FILL)
|
||||
{
|
||||
// Padded, buffer list passed using a 'creepy way'
|
||||
iov = (struct iovec*)((uint8_t*)rv[i].buf + (rv[i].len & 0xFFFFFFFF));
|
||||
n_iov = rv[i].len >> 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not padded, buffer is fully within the input buffer
|
||||
assert(op->buf);
|
||||
assert(rv[i].csum_buf);
|
||||
iov = &single_iov;
|
||||
n_iov = 1;
|
||||
assert(rv[i].offset >= op->offset);
|
||||
assert(rv[i].offset + rv[i].len <= op->offset + op->len);
|
||||
single_iov = { .iov_base = op->buf + rv[i].offset - op->offset, .iov_len = rv[i].len };
|
||||
}
|
||||
struct iovec *iov = (struct iovec*)((uint8_t*)rv[i].buf + (rv[i].len & 0xFFFFFFFF));
|
||||
int n_iov = rv[i].len >> 32;
|
||||
bool ok = true;
|
||||
if (rv[i].copy_flags & COPY_BUF_JOURNAL)
|
||||
{
|
||||
@@ -1051,7 +944,23 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
|
||||
meta_block = NULL;
|
||||
}
|
||||
}
|
||||
release_clean(op);
|
||||
if (PRIV(op)->clean_block_used)
|
||||
{
|
||||
// Release clean data block
|
||||
auto uo_it = used_clean_objects.find(PRIV(op)->clean_block_used);
|
||||
if (uo_it != used_clean_objects.end())
|
||||
{
|
||||
uo_it->second.refs--;
|
||||
if (uo_it->second.refs <= 0)
|
||||
{
|
||||
if (uo_it->second.was_freed)
|
||||
{
|
||||
data_alloc->set(PRIV(op)->clean_block_used, false);
|
||||
}
|
||||
used_clean_objects.erase(uo_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!journal.inmemory)
|
||||
{
|
||||
// Release journal sector usage
|
||||
|
||||
@@ -491,7 +491,7 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
|
||||
if (!exists)
|
||||
{
|
||||
uint64_t space_id = dirty_it->first.oid.inode;
|
||||
if (no_inode_stats.find(dirty_it->first.oid.inode >> (64-POOL_ID_BITS)) != no_inode_stats.end())
|
||||
if (no_inode_stats[dirty_it->first.oid.inode >> (64-POOL_ID_BITS)])
|
||||
space_id = space_id & ~(((uint64_t)1 << (64-POOL_ID_BITS)) - 1);
|
||||
inode_space_stats[space_id] += dsk.data_block_size;
|
||||
used_blocks++;
|
||||
@@ -501,7 +501,7 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
|
||||
else if (IS_DELETE(dirty_it->second.state))
|
||||
{
|
||||
uint64_t space_id = dirty_it->first.oid.inode;
|
||||
if (no_inode_stats.find(dirty_it->first.oid.inode >> (64-POOL_ID_BITS)) != no_inode_stats.end())
|
||||
if (no_inode_stats[dirty_it->first.oid.inode >> (64-POOL_ID_BITS)])
|
||||
space_id = space_id & ~(((uint64_t)1 << (64-POOL_ID_BITS)) - 1);
|
||||
auto & sp = inode_space_stats[space_id];
|
||||
if (sp > dsk.data_block_size)
|
||||
|
||||
@@ -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); };
|
||||
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);
|
||||
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
|
||||
);
|
||||
PRIV(op)->pending_ops = 1;
|
||||
if (!(dirty_it->second.state & BS_ST_INSTANT))
|
||||
{
|
||||
@@ -495,8 +495,9 @@ 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); };
|
||||
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);
|
||||
io_uring_prep_writev(
|
||||
sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free
|
||||
);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
else
|
||||
|
||||
+19
-58
@@ -3,27 +3,6 @@ cmake_minimum_required(VERSION 2.8...3.30)
|
||||
project(vitastor)
|
||||
|
||||
# libvitastor_common.a
|
||||
add_library(vitastor_common STATIC
|
||||
etcd_state_client.cpp
|
||||
msgr_stop.cpp
|
||||
msgr_op.cpp
|
||||
../../json11/json11.cpp
|
||||
osd_ops.cpp
|
||||
pg_states.cpp
|
||||
msgr_encrypt.cpp
|
||||
msgr_handshake.cpp
|
||||
../util/allocator.cpp
|
||||
../util/addr_util.cpp
|
||||
../util/timerfd_manager.cpp
|
||||
../util/str_util.cpp
|
||||
../util/json_util.cpp
|
||||
../util/xxh_x86dispatch.c
|
||||
../util/openssl_util.cpp
|
||||
)
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
target_link_libraries(vitastor_common ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
|
||||
# libvitastor_net.a
|
||||
set(MSGR_RDMA "")
|
||||
if (IBVERBS_LIBRARIES)
|
||||
set(MSGR_RDMA "msgr_rdma.cpp")
|
||||
@@ -32,32 +11,25 @@ set(MSGR_RDMACM "")
|
||||
if (RDMACM_LIBRARIES)
|
||||
set(MSGR_RDMACM "msgr_rdmacm.cpp")
|
||||
endif (RDMACM_LIBRARIES)
|
||||
add_library(vitastor_net STATIC
|
||||
../util/epoll_manager.cpp
|
||||
etcd_state_client_http.cpp
|
||||
messenger.cpp
|
||||
msgr_iothread.cpp
|
||||
msgr_send.cpp
|
||||
msgr_receive.cpp
|
||||
msgr_encrypt.cpp
|
||||
../util/ringloop.cpp
|
||||
http_client.cpp
|
||||
${MSGR_RDMA}
|
||||
${MSGR_RDMACM}
|
||||
add_library(vitastor_common STATIC
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp
|
||||
msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
|
||||
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
|
||||
)
|
||||
target_link_libraries(vitastor_net pthread vitastor_common ${CARES_LIBRARIES})
|
||||
target_compile_options(vitastor_net PUBLIC -fPIC)
|
||||
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client_int.a
|
||||
add_library(vitastor_client_int STATIC
|
||||
# libvitastor_client.so
|
||||
add_library(vitastor_client SHARED
|
||||
cluster_client.cpp
|
||||
cluster_client_real.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
cluster_client_icache.cpp
|
||||
vitastor_c.cpp
|
||||
)
|
||||
target_link_libraries(vitastor_client_int
|
||||
vitastor_net
|
||||
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
|
||||
target_link_libraries(vitastor_client
|
||||
vitastor_common
|
||||
vitastor_cli
|
||||
${LIBURING_LIBRARIES}
|
||||
${IBVERBS_LIBRARIES}
|
||||
@@ -65,16 +37,6 @@ target_link_libraries(vitastor_client_int
|
||||
${OPENSSL_LIBRARIES}
|
||||
${ISAL_CRYPTO_LIBRARIES}
|
||||
)
|
||||
target_compile_options(vitastor_client_int PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client.so
|
||||
add_library(vitastor_client SHARED
|
||||
vitastor_c.cpp
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
|
||||
target_link_libraries(vitastor_client
|
||||
vitastor_client_int
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
|
||||
configure_file(vitastor.pc.in vitastor.pc @ONLY)
|
||||
|
||||
@@ -93,6 +55,9 @@ if (${WITH_FIO})
|
||||
../util/rw_blocking.cpp
|
||||
../util/addr_util.cpp
|
||||
)
|
||||
target_link_libraries(fio_vitastor_sec
|
||||
tcmalloc_minimal
|
||||
)
|
||||
endif (${WITH_FIO})
|
||||
|
||||
# vitastor-nbd
|
||||
@@ -136,15 +101,11 @@ endif (${WITH_QEMU})
|
||||
add_executable(test_cluster_client
|
||||
EXCLUDE_FROM_ALL
|
||||
../test/test_cluster_client.cpp
|
||||
cluster_client.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
cluster_client_icache.cpp
|
||||
../test/mock/messenger.cpp
|
||||
../test/mock/vault.cpp
|
||||
etcd_state_client_mock.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 vitastor_common ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
target_link_libraries(test_cluster_client ${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)
|
||||
add_test(NAME test_cluster_client COMMAND test_cluster_client)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#define TRY_SEND_CONNECTING 1
|
||||
#define TRY_SEND_OK 2
|
||||
|
||||
cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config, std::unique_ptr<etcd_state_client_t> st_cli_ptr)
|
||||
cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config)
|
||||
{
|
||||
wb = new writeback_cache_t();
|
||||
|
||||
@@ -51,26 +51,26 @@ 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, true);
|
||||
msgr.parse_config(config);
|
||||
|
||||
st_cli = std::move(st_cli_ptr);
|
||||
st_cli->on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
st_cli->on_change_osd_state_hook = [this](uint64_t peer_osd) { on_change_osd_state_hook(peer_osd); };
|
||||
st_cli->on_change_pool_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli->on_change_pg_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli->on_change_pg_state_hook = [this](pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary) { on_change_pg_state_hook(pool_id, pg_num, prev_primary); };
|
||||
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]() { 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.tfd = tfd;
|
||||
st_cli.on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
st_cli.on_change_osd_state_hook = [this](uint64_t peer_osd) { on_change_osd_state_hook(peer_osd); };
|
||||
st_cli.on_change_pool_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli.on_change_pg_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli.on_change_pg_state_hook = [this](pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary) { on_change_pg_state_hook(pool_id, pg_num, prev_primary); };
|
||||
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;
|
||||
st_cli.parse_config(config);
|
||||
st_cli.infinite_start = false;
|
||||
if (!config["client_infinite_start"].is_null())
|
||||
{
|
||||
st_cli->infinite_start = config["client_infinite_start"].bool_value();
|
||||
st_cli.infinite_start = config["client_infinite_start"].bool_value();
|
||||
}
|
||||
st_cli->load_global_config();
|
||||
st_cli.load_global_config();
|
||||
}
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
@@ -467,7 +467,7 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
auto etcd_report_interval = config["etcd_report_interval"].uint64_value();
|
||||
if (!etcd_report_interval)
|
||||
etcd_report_interval = 5;
|
||||
client_wait_up_timeout = 1+etcd_report_interval+(st_cli->max_etcd_attempts*(2*st_cli->etcd_quick_timeout)+999)/1000;
|
||||
client_wait_up_timeout = 1+etcd_report_interval+(st_cli.max_etcd_attempts*(2*st_cli.etcd_quick_timeout)+999)/1000;
|
||||
}
|
||||
// log_level
|
||||
log_level = config["log_level"].uint64_value();
|
||||
@@ -481,9 +481,9 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
}
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config, false);
|
||||
st_cli->parse_config(config);
|
||||
st_cli->load_pgs();
|
||||
msgr.parse_config(config);
|
||||
st_cli.parse_config(config);
|
||||
st_cli.load_pgs();
|
||||
}
|
||||
|
||||
osd_num_t cluster_client_t::select_random_osd(const std::vector<osd_num_t> & osds)
|
||||
@@ -492,7 +492,7 @@ osd_num_t cluster_client_t::select_random_osd(const std::vector<osd_num_t> & osd
|
||||
int alive_count = 0;
|
||||
for (auto & osd_num: osds)
|
||||
{
|
||||
if (!st_cli->peer_states[osd_num].is_null())
|
||||
if (!st_cli.peer_states[osd_num].is_null())
|
||||
alive_set[alive_count++] = osd_num;
|
||||
}
|
||||
if (!alive_count)
|
||||
@@ -509,7 +509,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
while (self_tree_metrics.find(cur_id) == self_tree_metrics.end())
|
||||
{
|
||||
self_tree_metrics[cur_id] = metric++;
|
||||
json11::Json cur_placement = st_cli->node_placement[cur_id];
|
||||
json11::Json cur_placement = st_cli.node_placement[cur_id];
|
||||
cur_id = cur_placement["parent"].string_value();
|
||||
}
|
||||
if (cur_id != "")
|
||||
@@ -529,7 +529,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
}
|
||||
else
|
||||
{
|
||||
auto & peer_state = st_cli->peer_states[osd_num];
|
||||
auto & peer_state = st_cli.peer_states[osd_num];
|
||||
if (!peer_state.is_null())
|
||||
{
|
||||
metric = self_tree_metrics[""];
|
||||
@@ -539,7 +539,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
while (seen.find(cur_id) == seen.end())
|
||||
{
|
||||
seen.insert(cur_id);
|
||||
json11::Json cur_placement = st_cli->node_placement[cur_id];
|
||||
json11::Json cur_placement = st_cli.node_placement[cur_id];
|
||||
std::string cur_parent = cur_placement["parent"].string_value();
|
||||
cur_id = (!first || cur_parent != "" ? cur_parent : peer_state["host"].string_value());
|
||||
first = false;
|
||||
@@ -564,7 +564,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
|
||||
void cluster_client_t::on_load_pgs_hook(bool success)
|
||||
{
|
||||
for (auto & pool_item: st_cli->pool_config)
|
||||
for (auto & pool_item: st_cli.pool_config)
|
||||
{
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
@@ -584,7 +584,7 @@ void cluster_client_t::on_load_pgs_hook(bool success)
|
||||
|
||||
void cluster_client_t::on_change_pool_config_hook()
|
||||
{
|
||||
for (auto & pool_item: st_cli->pool_config)
|
||||
for (auto & pool_item: st_cli.pool_config)
|
||||
{
|
||||
if (pg_counts[pool_item.first] != pool_item.second.real_pg_count)
|
||||
{
|
||||
@@ -615,7 +615,7 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
|
||||
void cluster_client_t::on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary)
|
||||
{
|
||||
auto & pg_cfg = st_cli->pool_config[pool_id].pg_config[pg_num];
|
||||
auto & pg_cfg = st_cli.pool_config[pool_id].pg_config[pg_num];
|
||||
if (pg_cfg.cur_primary != prev_primary)
|
||||
{
|
||||
// Repeat this PG operations because an OSD which stopped being primary may not fsync operations
|
||||
@@ -633,8 +633,8 @@ bool cluster_client_t::get_immediate_commit(uint64_t inode)
|
||||
pool_id_t pool_id = INODE_POOL(inode);
|
||||
if (!pool_id)
|
||||
return true;
|
||||
auto pool_it = st_cli->pool_config.find(pool_id);
|
||||
if (pool_it == st_cli->pool_config.end())
|
||||
auto pool_it = st_cli.pool_config.find(pool_id);
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
return true;
|
||||
return pool_it->second.immediate_commit == IMMEDIATE_ALL;
|
||||
}
|
||||
@@ -644,7 +644,7 @@ void cluster_client_t::on_change_osd_state_hook(uint64_t peer_osd)
|
||||
osd_tree_metrics.erase(peer_osd);
|
||||
if (msgr.wanted_peers.find(peer_osd) != msgr.wanted_peers.end())
|
||||
{
|
||||
msgr.connect_peer(peer_osd, st_cli->peer_states[peer_osd]);
|
||||
msgr.connect_peer(peer_osd, st_cli.peer_states[peer_osd]);
|
||||
continue_lists();
|
||||
}
|
||||
}
|
||||
@@ -943,8 +943,8 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
auto pool_it = st_cli->pool_config.find(pool_id);
|
||||
if (pool_it == st_cli->pool_config.end() || pool_it->second.real_pg_count == 0)
|
||||
auto pool_it = st_cli.pool_config.find(pool_id);
|
||||
if (pool_it == st_cli.pool_config.end() || pool_it->second.real_pg_count == 0)
|
||||
{
|
||||
// Pools are loaded, but this one is unknown
|
||||
op->retval = -EINVAL;
|
||||
@@ -1057,7 +1057,7 @@ void cluster_client_t::execute_raw(osd_num_t osd_num, osd_op_t *op)
|
||||
else
|
||||
{
|
||||
if (msgr.wanted_peers.find(osd_num) == msgr.wanted_peers.end())
|
||||
msgr.connect_peer(osd_num, st_cli->peer_states[osd_num]);
|
||||
msgr.connect_peer(osd_num, st_cli.peer_states[osd_num]);
|
||||
raw_ops.emplace(osd_num, op);
|
||||
}
|
||||
}
|
||||
@@ -1162,13 +1162,6 @@ 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)
|
||||
{
|
||||
uint64_t next_inode = 0;
|
||||
@@ -1206,7 +1199,7 @@ resume_2:
|
||||
op->retval = op->len;
|
||||
if (op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->inode));
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->inode));
|
||||
op->retval = op->len / pool_cfg.bitmap_granularity;
|
||||
}
|
||||
if (op->flush_id)
|
||||
@@ -1216,7 +1209,7 @@ resume_2:
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
else if (op->retval != 0 && op->opcode != OSD_OP_SYNC && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
else if (op->retval != 0 && !(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)
|
||||
@@ -1286,7 +1279,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
// Slice the request into individual object stripe requests
|
||||
// Primary OSDs still operate individual stripes, but their size is multiplied by PG minsize in case of EC
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->cur_inode));
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
|
||||
uint64_t first_stripe = (op->offset / pg_block_size) * pg_block_size;
|
||||
@@ -1389,7 +1382,7 @@ bool cluster_client_t::affects_pg(uint64_t inode, uint64_t offset, uint64_t len,
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(inode));
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(inode));
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
|
||||
uint64_t first_stripe = (offset / pg_block_size) * pg_block_size;
|
||||
@@ -1408,7 +1401,7 @@ bool cluster_client_t::affects_pg(uint64_t inode, uint64_t offset, uint64_t len,
|
||||
|
||||
bool cluster_client_t::affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd)
|
||||
{
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(inode));
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(inode));
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
|
||||
uint64_t first_stripe = (offset / pg_block_size) * pg_block_size;
|
||||
@@ -1432,7 +1425,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
init_msgr();
|
||||
}
|
||||
auto part = &op->parts[i];
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto pg_it = pool_cfg.pg_config.find(part->pg_num);
|
||||
if (pg_it != pool_cfg.pg_config.end() &&
|
||||
!pg_it->second.pause && pg_it->second.cur_primary &&
|
||||
@@ -1466,8 +1459,8 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
uint64_t meta_rev = 0;
|
||||
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
|
||||
{
|
||||
auto ino_it = st_cli->inode_config.find(op->cur_inode);
|
||||
if (ino_it != st_cli->inode_config.end())
|
||||
auto ino_it = st_cli.inode_config.find(op->cur_inode);
|
||||
if (ino_it != st_cli.inode_config.end())
|
||||
meta_rev = ino_it->second.mod_revision;
|
||||
}
|
||||
part->op = (osd_op_t){
|
||||
@@ -1501,7 +1494,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
}
|
||||
else if (msgr.wanted_peers.find(primary_osd) == msgr.wanted_peers.end())
|
||||
{
|
||||
msgr.connect_peer(primary_osd, st_cli->peer_states[primary_osd]);
|
||||
msgr.connect_peer(primary_osd, st_cli.peer_states[primary_osd]);
|
||||
return TRY_SEND_CONNECTING;
|
||||
}
|
||||
}
|
||||
@@ -1607,9 +1600,6 @@ 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;
|
||||
@@ -1618,10 +1608,15 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
// Operation failed, retry
|
||||
part->flags |= PART_ERROR;
|
||||
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
|
||||
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
|
||||
op->retval = part->op.reply.hdr.retval;
|
||||
}
|
||||
uint64_t stop_client_id = 0;
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
|
||||
{
|
||||
stop_client_id = part->op.client_id;
|
||||
if (op->retval != -EPIPE || log_level > 0)
|
||||
@@ -1683,6 +1678,13 @@ 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
|
||||
@@ -1694,7 +1696,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
void cluster_client_t::copy_part_bitmap(cluster_op_t *op, cluster_op_part_t *part)
|
||||
{
|
||||
// Copy (OR) bitmap
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->cur_inode));
|
||||
uint32_t pg_block_size = pool_cfg.data_block_size * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "messenger.h"
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
|
||||
@@ -104,6 +104,9 @@ struct vault_load_key_t
|
||||
// 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;
|
||||
|
||||
@@ -176,7 +179,7 @@ class __attribute__((visibility("default"))) cluster_client_t
|
||||
bool msgr_initialized = false;
|
||||
|
||||
public:
|
||||
std::unique_ptr<etcd_state_client_t> st_cli;
|
||||
etcd_state_client_t st_cli;
|
||||
|
||||
osd_messenger_t msgr;
|
||||
void init_msgr();
|
||||
@@ -184,8 +187,7 @@ public:
|
||||
json11::Json::object cli_config, file_config, etcd_global_config;
|
||||
json11::Json::object config;
|
||||
|
||||
static cluster_client_t* create(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config);
|
||||
cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config, std::unique_ptr<etcd_state_client_t> st_cli);
|
||||
cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config);
|
||||
~cluster_client_t();
|
||||
void execute(cluster_op_t *op);
|
||||
void execute_raw(osd_num_t osd_num, osd_op_t *op);
|
||||
@@ -198,7 +200,12 @@ 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);
|
||||
|
||||
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
|
||||
@@ -208,6 +215,7 @@ protected:
|
||||
void vault_destroy();
|
||||
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
|
||||
|
||||
protected:
|
||||
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
|
||||
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
|
||||
|
||||
@@ -250,5 +258,4 @@ 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;
|
||||
};
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
#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)
|
||||
@@ -16,15 +22,24 @@ inode_cache_t::~inode_cache_t()
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (vault_client_cert.empty())
|
||||
vault_client_cert = config["cert"].string_value();
|
||||
vault_client_key = config["vault_client_key"].string_value();
|
||||
if (vault_client_key.empty())
|
||||
vault_client_key = config["pkey"].string_value();
|
||||
vault_ca = config["vault_ca"].string_value();
|
||||
vault_secret_api_path = "/v1/secret/";
|
||||
if (config["vault_secret_api_path"].is_string())
|
||||
@@ -76,14 +91,14 @@ std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
return icache_it->second;
|
||||
}
|
||||
// Fill inode cache
|
||||
auto ino_it = st_cli->inode_config.find(ino);
|
||||
if (ino_it == st_cli->inode_config.end())
|
||||
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())
|
||||
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;
|
||||
@@ -110,11 +125,11 @@ std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
break;
|
||||
}
|
||||
seen.insert(parent_id);
|
||||
ino_it = st_cli->inode_config.find(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())
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
chain_cfg.push_back(NULL);
|
||||
else
|
||||
{
|
||||
@@ -125,7 +140,7 @@ std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
}
|
||||
else if (!icache->other_pool_parent_id)
|
||||
icache->other_pool_parent_id = parent_id;
|
||||
if (ino_it == st_cli->inode_config.end())
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
break;
|
||||
parent_id = ino_it->second.parent_id;
|
||||
}
|
||||
@@ -208,6 +223,113 @@ std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t 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;
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
#define OP_FLUSH_BUFFER 0x02
|
||||
#define OP_IMMEDIATE_COMMIT 0x04
|
||||
|
||||
#define VAULT_KEY_NOT_LOADED 0
|
||||
#define VAULT_KEY_LOADING 1
|
||||
#define VAULT_KEY_LOADED 2
|
||||
#define VAULT_KEY_ERROR 3
|
||||
|
||||
struct cluster_buffer_t
|
||||
{
|
||||
uint8_t *buf;
|
||||
|
||||
@@ -63,14 +63,14 @@ void cluster_client_t::list_inode(inode_t inode, uint64_t min_offset, uint64_t m
|
||||
{
|
||||
init_msgr();
|
||||
pool_id_t pool_id = INODE_POOL(inode);
|
||||
if (!pool_id || st_cli->pool_config.find(pool_id) == st_cli->pool_config.end())
|
||||
if (!pool_id || st_cli.pool_config.find(pool_id) == st_cli.pool_config.end())
|
||||
{
|
||||
if (log_level > 0)
|
||||
fprintf(stderr, "Pool %u does not exist\n", pool_id);
|
||||
pg_callback(-EINVAL, 0, 0, std::set<object_id>());
|
||||
return;
|
||||
}
|
||||
auto pg_stripe_size = st_cli->pool_config.at(pool_id).pg_stripe_size;
|
||||
auto pg_stripe_size = st_cli.pool_config.at(pool_id).pg_stripe_size;
|
||||
if (min_offset)
|
||||
min_offset = (min_offset/pg_stripe_size) * pg_stripe_size;
|
||||
inode_list_t *lst = new inode_list_t();
|
||||
@@ -110,13 +110,13 @@ bool cluster_client_t::continue_listing(inode_list_t *lst)
|
||||
|
||||
bool cluster_client_t::restart_listing(inode_list_t* lst)
|
||||
{
|
||||
auto pool_it = st_cli->pool_config.find(lst->pool_id);
|
||||
auto pool_it = st_cli.pool_config.find(lst->pool_id);
|
||||
// We want listing to be consistent. To achieve it we should:
|
||||
// 1) retry listing of each PG if its state changes
|
||||
// 2) abort listing if PG count changes during listing
|
||||
// 3) ideally, only talk to the primary OSD - this will be done separately
|
||||
// So first we add all PGs without checking their state
|
||||
if (pool_it == st_cli->pool_config.end() ||
|
||||
if (pool_it == st_cli.pool_config.end() ||
|
||||
lst->real_pg_count != pool_it->second.real_pg_count)
|
||||
{
|
||||
for (auto pg: lst->pgs)
|
||||
@@ -136,7 +136,7 @@ bool cluster_client_t::restart_listing(inode_list_t* lst)
|
||||
fprintf(stderr, "PG count in pool %u changed during listing\n", lst->pool_id);
|
||||
}
|
||||
lst->pgs.clear();
|
||||
if (pool_it == st_cli->pool_config.end())
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
{
|
||||
// Unknown pool
|
||||
lst->callback(-EINVAL, 0, 0, std::set<object_id>());
|
||||
@@ -248,7 +248,7 @@ void cluster_client_t::set_list_retry_timeout(int ms, timespec new_time)
|
||||
|
||||
int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
{
|
||||
auto & pool_cfg = st_cli->pool_config.at(pg->lst->pool_id);
|
||||
auto & pool_cfg = st_cli.pool_config.at(pg->lst->pool_id);
|
||||
auto pg_it = pool_cfg.pg_config.find(pg->pg_num);
|
||||
assert(pg->lst->real_pg_count == pool_cfg.real_pg_count);
|
||||
if (pg_it == pool_cfg.pg_config.end() ||
|
||||
@@ -277,7 +277,7 @@ int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
for (auto peer_it = all_peers.begin(); peer_it != all_peers.end(); )
|
||||
{
|
||||
if (*peer_it != pg_it->second.cur_primary &&
|
||||
st_cli->peer_states[*peer_it].is_null())
|
||||
st_cli.peer_states[*peer_it].is_null())
|
||||
{
|
||||
pg->inactive_osds.push_back(*peer_it);
|
||||
all_peers.erase(peer_it++);
|
||||
@@ -298,11 +298,11 @@ int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
if (msgr.osd_peers.find(peer_osd) == msgr.osd_peers.end())
|
||||
{
|
||||
// Initiate connection
|
||||
if (st_cli->peer_states[peer_osd].is_null())
|
||||
if (st_cli.peer_states[peer_osd].is_null())
|
||||
{
|
||||
return LIST_PG_WAIT_ACTIVE;
|
||||
}
|
||||
msgr.connect_peer(peer_osd, st_cli->peer_states[peer_osd]);
|
||||
msgr.connect_peer(peer_osd, st_cli.peer_states[peer_osd]);
|
||||
conn = false;
|
||||
}
|
||||
}
|
||||
@@ -336,7 +336,7 @@ void cluster_client_t::send_list(inode_list_osd_t *cur_list)
|
||||
if (!cur_list->pg->inflight_ops)
|
||||
cur_list->pg->lst->inflight_pgs++;
|
||||
cur_list->pg->inflight_ops++;
|
||||
auto & pool_cfg = st_cli->pool_config[cur_list->pg->lst->pool_id];
|
||||
auto & pool_cfg = st_cli.pool_config[cur_list->pg->lst->pool_id];
|
||||
osd_op_t *op = new osd_op_t();
|
||||
op->op_type = OSD_OP_OUT;
|
||||
// Already checked that it exists above, but anyway
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include "cluster_client.h"
|
||||
#include "cluster_client_impl.h"
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "http_client.h"
|
||||
|
||||
cluster_client_t* cluster_client_t::create(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config)
|
||||
{
|
||||
auto st_cli = new etcd_state_client_http_t(tfd);
|
||||
return new cluster_client_t(ringloop, tfd, config, std::unique_ptr<etcd_state_client_t>(st_cli));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_destroy()
|
||||
{
|
||||
if (vault_http_ctx)
|
||||
{
|
||||
http_destroy(vault_http_cli);
|
||||
http_context_destroy(vault_http_ctx);
|
||||
vault_http_cli = NULL;
|
||||
vault_http_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_load_keys()
|
||||
{
|
||||
if (vault_loading || !vault_key_load_queue.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -88,11 +88,6 @@ 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);
|
||||
@@ -131,7 +126,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
writeback_bytes -= op->len;
|
||||
}
|
||||
writeback_queue_size++;
|
||||
writeback_queue.push_back({ op->inode, new_end });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -166,7 +160,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
{
|
||||
writeback_queue_size++;
|
||||
}
|
||||
writeback_queue.push_back({ op->inode, new_end });
|
||||
}
|
||||
auto new_dirty_it = dirty_buffers.emplace_hint(dirty_it, (object_id){
|
||||
.inode = op->inode,
|
||||
@@ -251,13 +244,12 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
writeback_queue_size--;
|
||||
}
|
||||
}
|
||||
if (!is_del && op->len > 0)
|
||||
if (!is_del)
|
||||
{
|
||||
uint64_t pos = 0, len = op->len, iov_idx = 0;
|
||||
while (iov_idx < op->iov.count)
|
||||
while (len > 0 && 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++;
|
||||
@@ -451,7 +443,7 @@ void writeback_cache_t::start_writebacks(cluster_client_t *cli, int count)
|
||||
started++;
|
||||
assert(writeback_queue_size > 0);
|
||||
writeback_queue_size--;
|
||||
writeback_bytes -= (is_del ? 0 : off - from_it->first.stripe);
|
||||
writeback_bytes -= off - from_it->first.stripe;
|
||||
assert(writeback_queue_size > 0 || !writeback_bytes);
|
||||
flush_buffers(cli, from_it, to_it);
|
||||
}
|
||||
|
||||
+532
-100
@@ -1,14 +1,17 @@
|
||||
// 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__
|
||||
#include "addr_util.h"
|
||||
#include "http_client.h"
|
||||
#endif
|
||||
#include "str_util.h"
|
||||
#include "json_util.h"
|
||||
|
||||
etcd_state_client_t::~etcd_state_client_t()
|
||||
{
|
||||
@@ -17,8 +20,33 @@ etcd_state_client_t::~etcd_state_client_t()
|
||||
delete watch;
|
||||
}
|
||||
watches.clear();
|
||||
etcd_watches_initialised = -1;
|
||||
#ifndef __MOCK__
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_destroy(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (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)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __MOCK__
|
||||
etcd_kv_t etcd_state_client_t::parse_etcd_kv(const json11::Json & kv_json)
|
||||
{
|
||||
etcd_kv_t kv;
|
||||
@@ -52,46 +80,99 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
|
||||
return addrs;
|
||||
}
|
||||
|
||||
std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & username)
|
||||
http_context_t *etcd_state_client_t::get_http_ctx()
|
||||
{
|
||||
auto user_it = user_info.find(username);
|
||||
if (user_it != user_info.end())
|
||||
if (!http_ctx)
|
||||
{
|
||||
return user_it->second;
|
||||
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);
|
||||
}
|
||||
}
|
||||
auto inf = std::make_shared<user_info_t>();
|
||||
inf->name = username;
|
||||
return inf;
|
||||
return http_ctx;
|
||||
}
|
||||
|
||||
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
|
||||
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)
|
||||
{
|
||||
if (user_info->type == user_type_t::ADMIN)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
callback(err, data);
|
||||
http_destroy(http_cli);
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
pick_next_etcd([=]()
|
||||
{
|
||||
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 "+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"
|
||||
"Keep-Alive: timeout="+std::to_string(etcd_keepalive_timeout)+"\r\n"
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
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_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Warning: etcd request failed: %s, retrying %d more times\n",
|
||||
err.c_str(), retries
|
||||
);
|
||||
}
|
||||
if (interval > 0)
|
||||
{
|
||||
// FIXME: Prevent destruction of etcd_state_client if timers or requests are active
|
||||
tfd->set_timer(interval, false, [this, api, payload, timeout, retries, interval, callback](int)
|
||||
{
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
else
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
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 etcd_address)
|
||||
{
|
||||
if (etcd_address.size() > 0)
|
||||
@@ -169,23 +250,19 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
add_etcd_url(ea.string_value());
|
||||
}
|
||||
}
|
||||
if (this->etcd_client_cert != "")
|
||||
if (this->osd_num)
|
||||
{
|
||||
this->etcd_client_cert = config["osd_etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["osd_etcd_client_key"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->etcd_client_cert = config["etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["etcd_client_key"].string_value();
|
||||
}
|
||||
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();
|
||||
this->use_auth = config["use_auth"].bool_value();
|
||||
if (this->etcd_prefix == "")
|
||||
{
|
||||
this->etcd_prefix = "/vitastor";
|
||||
@@ -202,6 +279,7 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
if (this->etcd_keepalive_timeout < 30)
|
||||
this->etcd_keepalive_timeout = 30;
|
||||
}
|
||||
auto old_etcd_ws_keepalive_interval = this->etcd_ws_keepalive_interval;
|
||||
this->etcd_ws_keepalive_interval = config["etcd_ws_keepalive_interval"].uint64_value();
|
||||
if (this->etcd_ws_keepalive_interval <= 0)
|
||||
{
|
||||
@@ -227,9 +305,347 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_min_reload_interval = 50;
|
||||
}
|
||||
if (this->etcd_ws_keepalive_interval != old_etcd_ws_keepalive_interval && ws_keepalive_timer >= 0)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
stop_ws_keepalive();
|
||||
start_ws_keepalive();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_global_config(std::function<void(const std::string & error)> cb)
|
||||
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);
|
||||
}
|
||||
if (selected_etcd_url.addr != "")
|
||||
{
|
||||
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 (this->log_level > 1)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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())
|
||||
{
|
||||
ws_alive = 1;
|
||||
std::string json_err;
|
||||
json11::Json data = json11::Json::parse(msg->body, json_err);
|
||||
if (json_err != "")
|
||||
{
|
||||
fprintf(stderr, "Bad JSON in etcd event: %s, ignoring event\n", json_err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t watch_id = data["result"]["watch_id"].uint64_value();
|
||||
if (data["result"]["created"].bool_value())
|
||||
{
|
||||
if (watch_id == ETCD_CONFIG_WATCH_ID ||
|
||||
watch_id == ETCD_PG_STATE_WATCH_ID ||
|
||||
watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
{
|
||||
etcd_watches_initialised++;
|
||||
}
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Successfully subscribed to etcd at %s, revision %ju/%ju/%ju\n", cur_addr.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
}
|
||||
if (data["result"]["canceled"].bool_value())
|
||||
{
|
||||
// etcd watch canceled, maybe because the revision was compacted
|
||||
if (data["result"]["compact_revision"].uint64_value())
|
||||
{
|
||||
// we may miss events if we proceed
|
||||
// so we should restart from the beginning if we can
|
||||
if (on_reload_hook != NULL)
|
||||
{
|
||||
// check to not trigger on_reload_hook multiple times
|
||||
if (etcd_watch_ws != NULL)
|
||||
{
|
||||
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_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, exiting\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Watch canceled by etcd, reason: %s, exiting\n", data["result"]["cancel_reason"].string_value().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
|
||||
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
|
||||
// Also don't save revision from the initial created: true messages because they always contain the latest revision
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES &&
|
||||
!data["result"]["header"]["revision"].is_null() &&
|
||||
!data["result"]["created"].bool_value())
|
||||
{
|
||||
// Restart watchers from the same revision number as in the last received message,
|
||||
// not from the next one to protect against revision being split into multiple messages,
|
||||
// even though etcd guarantees not to do that **within a single watcher** without fragment=true:
|
||||
// https://etcd.io/docs/v3.5/learning/api_guarantees/#watch-apis
|
||||
// Revision contents are ALWAYS split into separate messages for different watchers though!
|
||||
// So generally we have to resume each watcher from its own revision...
|
||||
// Progress messages may have watch_id=-1 if sent on behalf of multiple watchers though.
|
||||
// And antietcd has an advanced semantic which merges the same revision for all watchers
|
||||
// into one message and just omits watch_id.
|
||||
// So we also have to handle the case where watch_id is -1 or not present (0).
|
||||
auto watch_rev = data["result"]["header"]["revision"].uint64_value();
|
||||
if (!watch_id || watch_id == UINT64_MAX)
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_CONFIG_WATCH_ID)
|
||||
etcd_watch_revision_config = watch_rev;
|
||||
else if (watch_id == ETCD_PG_STATE_WATCH_ID)
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
for (auto & ev: data["result"]["events"].array_items())
|
||||
{
|
||||
auto kv = parse_etcd_kv(ev["kv"]);
|
||||
if (kv.key != "")
|
||||
{
|
||||
changes[kv.key] = kv;
|
||||
}
|
||||
}
|
||||
for (auto & kv: changes)
|
||||
{
|
||||
if (this->log_level > 3)
|
||||
{
|
||||
fprintf(stderr, "Incoming event: %s -> %s\n", kv.first.c_str(), kv.second.value.dump().c_str());
|
||||
}
|
||||
parse_state(kv.second);
|
||||
}
|
||||
// React to changes
|
||||
if (on_change_hook != NULL)
|
||||
{
|
||||
on_change_hook(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
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>
|
||||
tfd->set_timer(etcd_quick_timeout, false, [this](int)
|
||||
{
|
||||
start_etcd_watcher();
|
||||
});
|
||||
}
|
||||
else if (etcd_watches_initialised > 0)
|
||||
{
|
||||
// Connection was live, retry immediately
|
||||
etcd_watches_initialised = 0;
|
||||
start_etcd_watcher();
|
||||
}
|
||||
}
|
||||
});
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
{ "start_revision", etcd_watch_revision_config },
|
||||
{ "watch_id", ETCD_CONFIG_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/osd/state/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/osd/state0") },
|
||||
{ "start_revision", etcd_watch_revision_osd },
|
||||
{ "watch_id", ETCD_OSD_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/pg0") },
|
||||
{ "start_revision", etcd_watch_revision_pg },
|
||||
{ "watch_id", ETCD_PG_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
// FIXME: Do not watch /pg/history/ at all in client code (not in OSD)
|
||||
if (on_start_watcher_hook)
|
||||
{
|
||||
on_start_watcher_hook(etcd_watch_ws);
|
||||
}
|
||||
start_ws_keepalive();
|
||||
}
|
||||
|
||||
void etcd_state_client_t::stop_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer >= 0)
|
||||
{
|
||||
tfd->clear_timer(ws_keepalive_timer);
|
||||
ws_keepalive_timer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer < 0)
|
||||
{
|
||||
ws_keepalive_timer = tfd->set_timer(etcd_ws_keepalive_interval*1000, true, [this](int)
|
||||
{
|
||||
if (!etcd_watch_ws || etcd_watches_initialised < ETCD_TOTAL_WATCHES)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (!ws_alive)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
else
|
||||
{
|
||||
ws_alive = 0;
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "progress_request", json11::Json::object { } }
|
||||
}).dump());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_global_config()
|
||||
{
|
||||
json11::Json::object req = { { "success", json11::Json::array {
|
||||
json11::Json::object {
|
||||
@@ -243,12 +659,22 @@ void etcd_state_client_t::load_global_config(std::function<void(const std::strin
|
||||
} }
|
||||
},
|
||||
} } };
|
||||
etcd_txn(req, etcd_quick_timeout, max_etcd_attempts, 0, [this, cb](std::string err, json11::Json data)
|
||||
etcd_txn(req, etcd_quick_timeout, max_etcd_attempts, 0, [this](std::string err, json11::Json data)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
fprintf(stderr, "Error reading configuration from etcd: %s\n", err.c_str());
|
||||
cb(err);
|
||||
if (infinite_start)
|
||||
{
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_global_config();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
json11::Json config_kv = data["responses"][0]["response_range"]["kvs"][0];
|
||||
@@ -279,17 +705,37 @@ void etcd_state_client_t::load_global_config(std::function<void(const std::strin
|
||||
parse_state(kv);
|
||||
}
|
||||
on_load_config_hook(global_config);
|
||||
cb("");
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
void etcd_state_client_t::load_pgs()
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
uint64_t ms_passed = (tv.tv_sec-etcd_last_reload.tv_sec)*1000 + (tv.tv_nsec-etcd_last_reload.tv_nsec)/1000000;
|
||||
if (ms_passed < etcd_min_reload_interval)
|
||||
{
|
||||
if (load_pgs_timer_id < 0)
|
||||
{
|
||||
load_pgs_timer_id = tfd->set_timer(etcd_min_reload_interval+50-ms_passed, false, [this](int) { load_pgs(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
etcd_last_reload = tv;
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
json11::Json::array txn = {
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pools") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pgs") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
@@ -297,6 +743,12 @@ void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
{ "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/") },
|
||||
@@ -322,13 +774,16 @@ void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
{
|
||||
req["compare"] = checks;
|
||||
}
|
||||
etcd_txn_slow(req, [this, cb](std::string err, json11::Json data)
|
||||
etcd_txn_slow(req, [this](std::string err, json11::Json data)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
// Retry indefinitely
|
||||
fprintf(stderr, "Error loading PGs from etcd: %s\n", err.c_str());
|
||||
cb(err);
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_pgs();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!data["succeeded"].bool_value())
|
||||
@@ -356,9 +811,24 @@ void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
}
|
||||
clean_nonexistent_pgs();
|
||||
on_load_pgs_hook(true);
|
||||
cb("");
|
||||
start_etcd_watcher();
|
||||
});
|
||||
}
|
||||
#else
|
||||
void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_global_config()
|
||||
{
|
||||
json11::Json::object global_config;
|
||||
on_load_config_hook(global_config);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_pgs()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void etcd_state_client_t::reset_pg_exists()
|
||||
{
|
||||
@@ -471,8 +941,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (pc.pg_size < 1 ||
|
||||
pool_item.second["pg_size"].uint64_value() < 3 &&
|
||||
(pc.scheme == POOL_SCHEME_XOR || pc.scheme == POOL_SCHEME_EC) ||
|
||||
// limit is 64 because osd_peering_pg.cpp uses a 64-bit mask for has_roles
|
||||
pool_item.second["pg_size"].uint64_value() > 64)
|
||||
pool_item.second["pg_size"].uint64_value() > 256)
|
||||
{
|
||||
fprintf(stderr, "Pool %u has invalid pg_size, skipping pool\n", pool_id);
|
||||
continue;
|
||||
@@ -794,6 +1263,7 @@ 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 ||
|
||||
@@ -802,6 +1272,7 @@ 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;
|
||||
@@ -814,14 +1285,8 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
else if (key.substr(0, etcd_prefix.length()+11) == etcd_prefix+"/osd/state/")
|
||||
{
|
||||
// <etcd_prefix>/osd/state/%d
|
||||
osd_num_t peer_osd = 0;
|
||||
char null_byte = 0;
|
||||
int scanned = sscanf(key.c_str() + etcd_prefix.length()+11, "%ju%c", &peer_osd, &null_byte);
|
||||
if (scanned != 1 || !peer_osd)
|
||||
{
|
||||
fprintf(stderr, "Bad etcd key %s, ignoring\n", key.c_str());
|
||||
}
|
||||
else
|
||||
osd_num_t peer_osd = std::stoull(key.substr(etcd_prefix.length()+11));
|
||||
if (peer_osd > 0)
|
||||
{
|
||||
if (value.is_object() && value["state"] == "up")
|
||||
{
|
||||
@@ -874,10 +1339,6 @@ 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
|
||||
@@ -893,37 +1354,13 @@ 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/")
|
||||
else if (use_auth && 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);
|
||||
}
|
||||
user_info.erase(key.substr(etcd_prefix.length()+13));
|
||||
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();
|
||||
}
|
||||
user_info[key.substr(etcd_prefix.length()+13)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,12 +1384,7 @@ 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)
|
||||
{
|
||||
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;
|
||||
this->inode_config[cfg.num] = cfg;
|
||||
if (cfg.name != "")
|
||||
{
|
||||
this->inode_by_name[cfg.name] = cfg.num;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#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
|
||||
@@ -111,31 +110,8 @@ struct http_url_t
|
||||
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
|
||||
{
|
||||
@@ -146,13 +122,21 @@ protected:
|
||||
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::set<osd_num_t> seen_peers;
|
||||
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 reset_pg_exists();
|
||||
void clean_nonexistent_pgs();
|
||||
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;
|
||||
@@ -161,6 +145,7 @@ public:
|
||||
int etcd_slow_timeout = 5000;
|
||||
int etcd_min_reload_interval = 1000;
|
||||
bool infinite_start = true;
|
||||
bool use_auth = false;
|
||||
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
|
||||
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
||||
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
||||
@@ -171,17 +156,22 @@ public:
|
||||
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;
|
||||
uint64_t etcd_watch_revision_osd = 0;
|
||||
uint64_t etcd_watch_revision_pg = 0;
|
||||
|
||||
timespec etcd_last_reload = {};
|
||||
int load_pgs_timer_id = -1;
|
||||
std::map<pool_id_t, pool_config_t> pool_config;
|
||||
std::map<osd_num_t, json11::Json> peer_states;
|
||||
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;
|
||||
std::map<std::string, json11::Json> user_info;
|
||||
json11::Json node_placement;
|
||||
|
||||
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
||||
@@ -203,25 +193,25 @@ public:
|
||||
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();
|
||||
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);
|
||||
virtual 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) = 0;
|
||||
virtual void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
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);
|
||||
virtual void etcd_add_watch(json11::Json watch) = 0;
|
||||
virtual std::string get_username() = 0;
|
||||
void load_global_config(std::function<void(const std::string &)> cb);
|
||||
virtual void load_global_config() = 0;
|
||||
void load_pgs(std::function<void(const std::string &)> cb);
|
||||
virtual void load_pgs() = 0;
|
||||
void start_etcd_watcher();
|
||||
void stop_ws_keepalive();
|
||||
void start_ws_keepalive();
|
||||
void load_global_config();
|
||||
void load_pgs();
|
||||
void reset_pg_exists();
|
||||
void clean_nonexistent_pgs();
|
||||
void parse_state(const etcd_kv_t & kv);
|
||||
virtual void parse_config(const json11::Json & config);
|
||||
void parse_config(const json11::Json & config);
|
||||
void insert_inode_config(const inode_config_t & cfg);
|
||||
inode_watch_t* watch_inode(std::string name);
|
||||
void close_watch(inode_watch_t* watch);
|
||||
int address_count();
|
||||
virtual ~etcd_state_client_t();
|
||||
~etcd_state_client_t();
|
||||
|
||||
static uint32_t parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value);
|
||||
static uint32_t parse_scheme(const std::string & scheme_str);
|
||||
|
||||
@@ -1,545 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "addr_util.h"
|
||||
#include "http_client.h"
|
||||
#include "str_util.h"
|
||||
|
||||
etcd_state_client_http_t::etcd_state_client_http_t(timerfd_manager_t *tfd)
|
||||
{
|
||||
this->tfd = tfd;
|
||||
}
|
||||
|
||||
etcd_state_client_http_t::~etcd_state_client_http_t()
|
||||
{
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_destroy(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (keepalive_client)
|
||||
{
|
||||
http_destroy(keepalive_client);
|
||||
keepalive_client = NULL;
|
||||
}
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
if (http_ctx)
|
||||
{
|
||||
http_context_destroy(http_ctx);
|
||||
http_ctx = NULL;
|
||||
}
|
||||
etcd_watches_initialised = -1;
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_add_watch(json11::Json watch)
|
||||
{
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, watch.dump());
|
||||
}
|
||||
}
|
||||
|
||||
std::string etcd_state_client_http_t::get_username()
|
||||
{
|
||||
return http_context_get_ssl_cn(get_http_ctx());
|
||||
}
|
||||
|
||||
http_context_t *etcd_state_client_http_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_http_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)
|
||||
{
|
||||
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_destroy(http_cli);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
pick_next_etcd([=]()
|
||||
{
|
||||
etcd_call_selected(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_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 "+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"
|
||||
"Keep-Alive: timeout="+std::to_string(etcd_keepalive_timeout)+"\r\n"
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
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_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Warning: etcd request failed: %s, retrying %d more times\n",
|
||||
err.c_str(), retries
|
||||
);
|
||||
}
|
||||
if (interval > 0)
|
||||
{
|
||||
// FIXME: Prevent destruction of etcd_state_client if timers or requests are active
|
||||
tfd->set_timer(interval, false, [this, api, payload, timeout, retries, interval, callback](int)
|
||||
{
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
else
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
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_http_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
auto old_etcd_ws_keepalive_interval = this->etcd_ws_keepalive_interval;
|
||||
etcd_state_client_t::parse_config(config);
|
||||
if (this->etcd_ws_keepalive_interval != old_etcd_ws_keepalive_interval && ws_keepalive_timer >= 0)
|
||||
{
|
||||
stop_ws_keepalive();
|
||||
start_ws_keepalive();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_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);
|
||||
}
|
||||
if (selected_etcd_url.addr != "")
|
||||
{
|
||||
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_http_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_http_t::start_etcd_watcher()
|
||||
{
|
||||
pick_next_etcd([this]()
|
||||
{
|
||||
start_etcd_watcher_selected();
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_etcd_watcher_selected()
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
etcd_watches_initialised = 0;
|
||||
ws_alive = 1;
|
||||
if (this->log_level > 1)
|
||||
{
|
||||
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);
|
||||
}
|
||||
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())
|
||||
{
|
||||
ws_alive = 1;
|
||||
std::string json_err;
|
||||
json11::Json data = json11::Json::parse(msg->body, json_err);
|
||||
if (json_err != "")
|
||||
{
|
||||
fprintf(stderr, "Bad JSON in etcd event: %s, ignoring event\n", json_err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t watch_id = data["result"]["watch_id"].uint64_value();
|
||||
if (data["result"]["created"].bool_value())
|
||||
{
|
||||
if (watch_id == ETCD_CONFIG_WATCH_ID ||
|
||||
watch_id == ETCD_PG_STATE_WATCH_ID ||
|
||||
watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
{
|
||||
etcd_watches_initialised++;
|
||||
}
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Successfully subscribed to etcd at %s, revision %ju/%ju/%ju\n", cur_addr.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
}
|
||||
if (data["result"]["canceled"].bool_value())
|
||||
{
|
||||
// etcd watch canceled, maybe because the revision was compacted
|
||||
if (data["result"]["compact_revision"].uint64_value())
|
||||
{
|
||||
// we may miss events if we proceed
|
||||
// so we should restart from the beginning if we can
|
||||
if (on_reload_hook != NULL)
|
||||
{
|
||||
// check to not trigger on_reload_hook multiple times
|
||||
if (etcd_watch_ws != NULL)
|
||||
{
|
||||
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_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, exiting\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Watch canceled by etcd, reason: %s, exiting\n", data["result"]["cancel_reason"].string_value().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
|
||||
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
|
||||
// Also don't save revision from the initial created: true messages because they always contain the latest revision
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES &&
|
||||
!data["result"]["header"]["revision"].is_null() &&
|
||||
!data["result"]["created"].bool_value())
|
||||
{
|
||||
// Restart watchers from the same revision number as in the last received message,
|
||||
// not from the next one to protect against revision being split into multiple messages,
|
||||
// even though etcd guarantees not to do that **within a single watcher** without fragment=true:
|
||||
// https://etcd.io/docs/v3.5/learning/api_guarantees/#watch-apis
|
||||
// Revision contents are ALWAYS split into separate messages for different watchers though!
|
||||
// So generally we have to resume each watcher from its own revision...
|
||||
// Progress messages may have watch_id=-1 if sent on behalf of multiple watchers though.
|
||||
// And antietcd has an advanced semantic which merges the same revision for all watchers
|
||||
// into one message and just omits watch_id.
|
||||
// So we also have to handle the case where watch_id is -1 or not present (0).
|
||||
auto watch_rev = data["result"]["header"]["revision"].uint64_value();
|
||||
if (!watch_id || watch_id == UINT64_MAX)
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_CONFIG_WATCH_ID)
|
||||
etcd_watch_revision_config = watch_rev;
|
||||
else if (watch_id == ETCD_PG_STATE_WATCH_ID)
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
for (auto & ev: data["result"]["events"].array_items())
|
||||
{
|
||||
auto kv = parse_etcd_kv(ev["kv"]);
|
||||
if (kv.key != "")
|
||||
{
|
||||
changes[kv.key] = kv;
|
||||
}
|
||||
}
|
||||
for (auto & kv: changes)
|
||||
{
|
||||
if (this->log_level > 3)
|
||||
{
|
||||
fprintf(stderr, "Incoming event: %s -> %s\n", kv.first.c_str(), kv.second.value.dump().c_str());
|
||||
}
|
||||
parse_state(kv.second);
|
||||
}
|
||||
// React to changes
|
||||
if (on_change_hook != NULL)
|
||||
{
|
||||
on_change_hook(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
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>
|
||||
tfd->set_timer(etcd_quick_timeout, false, [this](int)
|
||||
{
|
||||
start_etcd_watcher();
|
||||
});
|
||||
}
|
||||
else if (etcd_watches_initialised > 0)
|
||||
{
|
||||
// Connection was live, retry immediately
|
||||
etcd_watches_initialised = 0;
|
||||
start_etcd_watcher();
|
||||
}
|
||||
}
|
||||
});
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
{ "start_revision", etcd_watch_revision_config },
|
||||
{ "watch_id", ETCD_CONFIG_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/osd/state/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/osd/state0") },
|
||||
{ "start_revision", etcd_watch_revision_osd },
|
||||
{ "watch_id", ETCD_OSD_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/pg0") },
|
||||
{ "start_revision", etcd_watch_revision_pg },
|
||||
{ "watch_id", ETCD_PG_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
// FIXME: Do not watch /pg/history/ at all in client code (not in OSD)
|
||||
if (on_start_watcher_hook)
|
||||
{
|
||||
on_start_watcher_hook(etcd_watch_ws);
|
||||
}
|
||||
start_ws_keepalive();
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::stop_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer >= 0)
|
||||
{
|
||||
tfd->clear_timer(ws_keepalive_timer);
|
||||
ws_keepalive_timer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer < 0)
|
||||
{
|
||||
ws_keepalive_timer = tfd->set_timer(etcd_ws_keepalive_interval*1000, true, [this](int)
|
||||
{
|
||||
if (!etcd_watch_ws || etcd_watches_initialised < ETCD_TOTAL_WATCHES)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (!ws_alive)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
else
|
||||
{
|
||||
ws_alive = 0;
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "progress_request", json11::Json::object { } }
|
||||
}).dump());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::load_global_config()
|
||||
{
|
||||
etcd_state_client_t::load_global_config([this](const std::string & err)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
fprintf(stderr, "Error reading configuration from etcd: %s\n", err.c_str());
|
||||
if (infinite_start)
|
||||
{
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_global_config();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::load_pgs()
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
uint64_t ms_passed = (tv.tv_sec-etcd_last_reload.tv_sec)*1000 + (tv.tv_nsec-etcd_last_reload.tv_nsec)/1000000;
|
||||
if (ms_passed < etcd_min_reload_interval)
|
||||
{
|
||||
if (load_pgs_timer_id < 0)
|
||||
{
|
||||
load_pgs_timer_id = tfd->set_timer(etcd_min_reload_interval+50-ms_passed, false, [this](int) { load_pgs(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
etcd_last_reload = tv;
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
etcd_state_client_t::load_pgs([this](const std::string & err)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
// Retry indefinitely
|
||||
fprintf(stderr, "Error loading PGs from etcd: %s\n", err.c_str());
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_pgs();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
start_etcd_watcher();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
|
||||
struct http_context_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_http_t: public etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
int ws_keepalive_timer = -1;
|
||||
int ws_alive = 0;
|
||||
bool rand_initialized = false;
|
||||
int etcd_watches_initialised = 0;
|
||||
timespec etcd_last_reload = {};
|
||||
int load_pgs_timer_id = -1;
|
||||
http_co_t *keepalive_client = NULL;
|
||||
http_co_t *etcd_watch_ws = NULL;
|
||||
http_context_t *http_ctx = NULL;
|
||||
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<std::function<void()>> on_resolve_queue;
|
||||
|
||||
void pick_next_etcd(std::function<void()> cb);
|
||||
void pick_next_etcd_on_resolve();
|
||||
void start_etcd_watcher();
|
||||
void start_etcd_watcher_selected();
|
||||
void stop_ws_keepalive();
|
||||
void start_ws_keepalive();
|
||||
http_context_t *get_http_ctx();
|
||||
public:
|
||||
etcd_state_client_http_t(timerfd_manager_t *tfd);
|
||||
void 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) override;
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
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 etcd_add_watch(json11::Json watch) override;
|
||||
std::string get_username() override;
|
||||
void load_global_config() override;
|
||||
void load_pgs() override;
|
||||
void parse_config(const json11::Json & config) override;
|
||||
~etcd_state_client_http_t();
|
||||
};
|
||||
@@ -1,210 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "etcd_state_client_mock.h"
|
||||
#include "str_util.h"
|
||||
|
||||
etcd_state_client_mock_t::etcd_state_client_mock_t()
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_add_watch(json11::Json watch)
|
||||
{
|
||||
}
|
||||
|
||||
std::string etcd_state_client_mock_t::get_username()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::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_state_client_mock_t::pause()
|
||||
{
|
||||
paused = true;
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::resume()
|
||||
{
|
||||
paused = false;
|
||||
auto queue = std::move(this->queue);
|
||||
for (auto& req: queue)
|
||||
{
|
||||
etcd_call(req.api, req.payload, req.timeout, req.retries, req.interval, req.callback);
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::set(const std::string& key, json11::Json data, uint64_t mod_revision, uint64_t lease_id)
|
||||
{
|
||||
if (!mod_revision)
|
||||
mod_revision = ++this->mod_revision;
|
||||
this->data[key] = (etcd_mock_key_data_t){ .value = data.dump(), .mod_revision = mod_revision, .lease_id = lease_id };
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_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 (paused)
|
||||
{
|
||||
queue.push_back({ api, payload, timeout, retries, interval, callback });
|
||||
return;
|
||||
}
|
||||
printf("+ etcd: %s\n", api.c_str());
|
||||
if (api == "/kv/txn")
|
||||
{
|
||||
bool ok = true;
|
||||
for (auto& check: payload["compare"].array_items())
|
||||
{
|
||||
auto key = base64_decode(check["key"].string_value());
|
||||
etcd_mock_key_data_t *key_data = data.find(key) != data.end() ? &data.at(key) : NULL;
|
||||
auto target = check["target"].string_value();
|
||||
auto res = check["result"].string_value();
|
||||
assert(res == "LESS" || res == "");
|
||||
bool less = res == "LESS";
|
||||
if (target == "MOD")
|
||||
{
|
||||
uint64_t rev = check["mod_revision"].uint64_value();
|
||||
assert(!less || rev);
|
||||
ok = ok && (less ? (!key_data || key_data->mod_revision < rev) : (key_data && key_data->mod_revision == rev));
|
||||
}
|
||||
else if (target == "CREATE")
|
||||
{
|
||||
uint64_t rev = check["create_revision"].uint64_value();
|
||||
assert(rev == 0 && !less);
|
||||
ok = ok && !key_data;
|
||||
}
|
||||
else if (target == "VERSION")
|
||||
{
|
||||
uint64_t rev = check["version"].uint64_value();
|
||||
assert(rev == 0 && !less);
|
||||
ok = ok && !key_data;
|
||||
}
|
||||
else if (target == "LEASE")
|
||||
{
|
||||
assert(!less);
|
||||
uint64_t lease_id = check["lease"].uint64_value();
|
||||
ok = ok && key_data && key_data->lease_id == lease_id;
|
||||
}
|
||||
else
|
||||
assert(0);
|
||||
}
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
bool has_mod = false;
|
||||
for (auto& op: payload[ok ? "success" : "failure"].array_items())
|
||||
{
|
||||
auto& obj = op.object_items();
|
||||
has_mod = has_mod || obj.find("request_put") != obj.end() ||
|
||||
obj.find("request_delete_range") != obj.end();
|
||||
}
|
||||
if (has_mod)
|
||||
{
|
||||
mod_revision++;
|
||||
}
|
||||
json11::Json::array responses;
|
||||
for (auto& op_ptr: payload[ok ? "success" : "failure"].array_items())
|
||||
{
|
||||
auto& op = op_ptr.object_items();
|
||||
if (op.find("request_range") != op.end())
|
||||
{
|
||||
json11::Json::array kvs;
|
||||
auto req = op.at("request_range");
|
||||
auto key = base64_decode(req["key"].string_value());
|
||||
auto range_end = base64_decode(req["range_end"].string_value());
|
||||
auto begin_it = range_end.empty() ? data.find(key) : data.lower_bound(key);
|
||||
auto end_it = range_end.empty() ? (begin_it == data.end() ? begin_it : std::next(begin_it)) : data.lower_bound(range_end);
|
||||
for (auto it = begin_it; it != end_it; it++)
|
||||
{
|
||||
printf("\\- get: %s = %s, rev %ju\n", it->first.c_str(), it->second.value.c_str(), it->second.mod_revision);
|
||||
kvs.push_back(json11::Json::object {
|
||||
{ "key", base64_encode(it->first) },
|
||||
{ "value", base64_encode(it->second.value) },
|
||||
{ "mod_revision", it->second.mod_revision },
|
||||
});
|
||||
}
|
||||
responses.push_back(json11::Json::object {
|
||||
{ "response_range", json11::Json::object{ { "header", json11::Json::object{ { "revision", mod_revision } } }, { "kvs", kvs } } },
|
||||
});
|
||||
}
|
||||
else if (op.find("request_put") != op.end())
|
||||
{
|
||||
auto req = op.at("request_put");
|
||||
auto key = base64_decode(req["key"].string_value());
|
||||
auto value = base64_decode(req["value"].string_value());
|
||||
auto lease_id = req["lease"].uint64_value();
|
||||
printf("\\- put: %s = %s, rev %ju, lease %ju\n", key.c_str(), value.c_str(), mod_revision, lease_id);
|
||||
data[key] = {
|
||||
.value = value,
|
||||
.mod_revision = mod_revision,
|
||||
.lease_id = lease_id,
|
||||
};
|
||||
std::string err;
|
||||
json11::Json json_value = json11::Json::parse(value, err);
|
||||
if (err != "")
|
||||
{
|
||||
fprintf(stderr, "Invalid JSON in etcd key %s during test: %s\n", key.c_str(), value.c_str());
|
||||
exit(1);
|
||||
}
|
||||
changes[key] = { .key = key, .value = json_value, .mod_revision = mod_revision };
|
||||
responses.push_back(json11::Json::object {
|
||||
{ "response_put", json11::Json::object{ { "header", json11::Json::object{ { "revision", mod_revision } } } } },
|
||||
});
|
||||
}
|
||||
else if (op.find("request_delete_range") != op.end())
|
||||
{
|
||||
auto req = op.at("request_delete_range");
|
||||
auto key = base64_decode(req["key"].string_value());
|
||||
auto range_end = base64_decode(req["range_end"].string_value());
|
||||
uint64_t n_del = 0;
|
||||
for (auto it = data.lower_bound(key); it != data.end() && (range_end == "" || it->first < range_end); )
|
||||
{
|
||||
auto & key = it->first;
|
||||
printf("\\- del: %s\n", key.c_str());
|
||||
changes[key] = { .key = key, .mod_revision = mod_revision };
|
||||
n_del++;
|
||||
data.erase(it++);
|
||||
}
|
||||
responses.push_back(json11::Json::object {
|
||||
{ "response_delete_range", json11::Json::object{ { "header", json11::Json::object{ { "revision", mod_revision } } }, { "deleted", n_del } } },
|
||||
});
|
||||
}
|
||||
}
|
||||
callback("", json11::Json::object{
|
||||
{ "header", json11::Json::object{ { "revision", mod_revision } } },
|
||||
{ "succeeded", ok },
|
||||
{ "responses", responses }
|
||||
});
|
||||
// Push changes to watcher
|
||||
if (changes.size())
|
||||
{
|
||||
for (auto & kv: changes)
|
||||
parse_state(kv.second);
|
||||
if (on_change_hook != NULL)
|
||||
on_change_hook(changes);
|
||||
}
|
||||
}
|
||||
else if (api == "/lease/grant")
|
||||
{
|
||||
uint64_t lease_id = (((uint64_t)lrand48()) << 32) | lrand48();
|
||||
leases[lease_id] = payload["TTL"].uint64_value();
|
||||
callback("", json11::Json::object{ { "ID", std::to_string(lease_id) } });
|
||||
}
|
||||
else
|
||||
callback("Unsupported", json11::Json());
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::load_global_config()
|
||||
{
|
||||
etcd_state_client_t::load_global_config([this](const std::string & err) {});
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::load_pgs()
|
||||
{
|
||||
etcd_state_client_t::load_pgs([this](const std::string & err) {});
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
|
||||
struct etcd_mock_key_data_t
|
||||
{
|
||||
std::string value;
|
||||
uint64_t mod_revision;
|
||||
uint64_t lease_id;
|
||||
};
|
||||
|
||||
struct etcd_mock_request_t
|
||||
{
|
||||
std::string api;
|
||||
json11::Json payload;
|
||||
int timeout;
|
||||
int retries;
|
||||
int interval;
|
||||
std::function<void(std::string, json11::Json)> callback;
|
||||
};
|
||||
|
||||
struct etcd_state_client_mock_t: public etcd_state_client_t
|
||||
{
|
||||
uint64_t mod_revision = 0;
|
||||
bool paused = false;
|
||||
std::vector<etcd_mock_request_t> queue;
|
||||
public:
|
||||
std::map<uint64_t, uint64_t> leases;
|
||||
std::map<std::string, etcd_mock_key_data_t> data;
|
||||
std::string username;
|
||||
etcd_state_client_mock_t();
|
||||
void set(const std::string& key, json11::Json data, uint64_t mod_revision = 0, uint64_t lease_id = 0);
|
||||
void pause();
|
||||
void resume();
|
||||
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) override;
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_add_watch(json11::Json watch) override;
|
||||
std::string get_username() override;
|
||||
void load_global_config() override;
|
||||
void load_pgs() override;
|
||||
};
|
||||
+161
-87
@@ -16,6 +16,106 @@
|
||||
#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();
|
||||
@@ -75,7 +175,12 @@ void osd_messenger_t::init()
|
||||
}
|
||||
if (ringloop && iothread_count > 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
keepalive_timer_id = tfd->set_timer(1000, true, [this](int)
|
||||
{
|
||||
@@ -117,7 +222,7 @@ void osd_messenger_t::init()
|
||||
.opcode = OSD_OP_PING,
|
||||
},
|
||||
};
|
||||
op->callback = [this](osd_op_t *op)
|
||||
op->callback = [this, cl](osd_op_t *op)
|
||||
{
|
||||
auto cl_it = clients.find(op->client_id);
|
||||
if (cl_it == clients.end())
|
||||
@@ -126,7 +231,6 @@ 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;
|
||||
@@ -169,7 +273,14 @@ osd_messenger_t::~osd_messenger_t()
|
||||
{
|
||||
stop_client(clients.begin()->first, true);
|
||||
}
|
||||
destroy_iothreads();
|
||||
if (iothreads.size())
|
||||
{
|
||||
for (auto iot: iothreads)
|
||||
{
|
||||
delete iot;
|
||||
}
|
||||
iothreads.clear();
|
||||
}
|
||||
#ifdef WITH_RDMA
|
||||
for (auto rdma_context: rdma_contexts)
|
||||
{
|
||||
@@ -197,56 +308,8 @@ osd_messenger_t::~osd_messenger_t()
|
||||
destroy_tls();
|
||||
}
|
||||
|
||||
static int parse_proto_checksums(const json11::Json & val, int default_value)
|
||||
void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
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())
|
||||
{
|
||||
@@ -277,27 +340,55 @@ void osd_messenger_t::parse_config(const json11::Json & config, bool init)
|
||||
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
|
||||
this->rdma_max_msg = 129*1024;
|
||||
#endif
|
||||
this->max_cipher_pool_size = config["max_cipher_pool_size"].uint64_value();
|
||||
if (!this->max_cipher_pool_size)
|
||||
this->max_cipher_pool_size = 256;
|
||||
if (config["proto_checksums"].is_null())
|
||||
this->use_proto_checksums = MSGR_CSUM_PAYLOAD;
|
||||
else if (config["proto_checksums"].is_bool())
|
||||
this->use_proto_checksums = config["proto_checksums"].bool_value() ? MSGR_CSUM_FULL : 0;
|
||||
else if (config["proto_checksums"].string_value() != "")
|
||||
this->use_proto_checksums = config["proto_checksums"].string_value() == "full" ? MSGR_CSUM_FULL : MSGR_CSUM_PAYLOAD;
|
||||
else
|
||||
this->use_proto_checksums = 0;
|
||||
if (!osd_num)
|
||||
{
|
||||
tls_cert = config["cert"].string_value();
|
||||
tls_key = config["pkey"].string_value();
|
||||
osd_tls_ca = config["osd_ca"].string_value();
|
||||
tls_cert = config["tls_cert"].string_value();
|
||||
tls_key = config["tls_key"].string_value();
|
||||
osd_tls_ca = config["osd_tls_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();
|
||||
tls_cert = config["osd_tls_cert"].string_value();
|
||||
tls_key = config["osd_tls_key"].string_value();
|
||||
osd_tls_ca = config["osd_tls_ca"].string_value();
|
||||
client_tls_ca = config["client_tls_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() || !ringloop;
|
||||
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();
|
||||
// OSD public & cluster networks
|
||||
this->osd_networks.clear();
|
||||
if (config["osd_network"].is_string())
|
||||
@@ -638,11 +729,6 @@ 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);
|
||||
@@ -670,21 +756,6 @@ 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;
|
||||
@@ -693,6 +764,14 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
delete op;
|
||||
return;
|
||||
}
|
||||
if (use_proto_checksums)
|
||||
{
|
||||
auto peer_csums = config["features"]["proto_checksums"].uint64_value();
|
||||
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;
|
||||
}
|
||||
#ifdef WITH_RDMA
|
||||
if (!use_rdmacm && cl->rdma_conn && config["rdma_address"].is_string())
|
||||
{
|
||||
@@ -800,11 +879,6 @@ 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;
|
||||
|
||||
+72
-27
@@ -25,7 +25,6 @@
|
||||
#include "msgr_op.h"
|
||||
#include "timerfd_manager.h"
|
||||
#include "addr_util.h"
|
||||
#include "msgr_handshake.h"
|
||||
#include <ringloop.h>
|
||||
|
||||
#define CL_READ_HDR 1
|
||||
@@ -41,8 +40,7 @@
|
||||
|
||||
#define MSGR_CSUM_PAYLOAD 1
|
||||
#define MSGR_CSUM_FULL 2
|
||||
#define MSGR_CSUM_GCM 4
|
||||
#define MSGR_CSUM_NEG 8
|
||||
#define MSGR_CSUM_NEG 4
|
||||
|
||||
#define VITASTOR_CONFIG_PATH "/etc/vitastor/vitastor.conf"
|
||||
|
||||
@@ -51,8 +49,6 @@
|
||||
#define AES_256_GCM_KEY_SIZE 32
|
||||
#define AES_256_GCM_IV_SIZE 12
|
||||
|
||||
#define MAX_SIMPLE_PAYLOAD_SIZE 1048576
|
||||
|
||||
struct msgr_sendp_t
|
||||
{
|
||||
osd_op_t *op;
|
||||
@@ -69,7 +65,13 @@ 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;
|
||||
// Standard TLS record header. We are only interested in the record size
|
||||
struct __attribute__((__packed__)) msgr_tls_record_hdr_t
|
||||
{
|
||||
uint8_t content_type;
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
};
|
||||
|
||||
struct osd_client_t
|
||||
{
|
||||
@@ -93,12 +95,21 @@ struct osd_client_t
|
||||
msgr_rdma_connection_t *rdma_conn = NULL;
|
||||
#endif
|
||||
|
||||
SSL *ssl_cli = NULL;
|
||||
BIO *write_to_ssl = NULL;
|
||||
// FIXME: use custom bio to avoid 1 more memory copy?
|
||||
BIO *read_from_ssl = NULL;
|
||||
uint8_t *ssl_out_buf = NULL;
|
||||
size_t ssl_out_buf_size = 0, ssl_out_buf_cap = 0;
|
||||
int ssl_handshake_pending = 0;
|
||||
msgr_tls_record_hdr_t ssl_read_record;
|
||||
size_t ssl_read_header_size = 0;
|
||||
bool ssl_more_to_buffer = false;
|
||||
|
||||
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_secret, peer_secret;
|
||||
std::vector<uint8_t> my_key, peer_key;
|
||||
uint64_t my_iv_ctr = 0, peer_iv_ctr = 0;
|
||||
uint64_t my_iv_ctr, peer_iv_ctr;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
isal_gcm_key_data my_key_isal, peer_key_isal;
|
||||
isal_gcm_context_data *enc_ctx = NULL;
|
||||
@@ -183,7 +194,43 @@ 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;
|
||||
@@ -201,9 +248,11 @@ struct __attribute__((visibility("default"))) osd_messenger_t
|
||||
{
|
||||
protected:
|
||||
friend class copy_op_reader_t;
|
||||
friend class ssl_op_reader_t;
|
||||
friend class gcm_op_reader_t;
|
||||
friend class get_op_reader_t;
|
||||
friend class copy_op_writer_t;
|
||||
friend class ssl_op_writer_t;
|
||||
friend class gcm_op_writer_t;
|
||||
friend class get_op_writer_t;
|
||||
|
||||
@@ -241,13 +290,18 @@ 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;
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
EVP_KDF_CTX *kdf_ctx = NULL;
|
||||
X509 *tls_cert_obj = NULL;
|
||||
X509 *osd_tls_ca_obj = NULL;
|
||||
X509 *client_tls_ca_obj = NULL;
|
||||
std::string tls_cn;
|
||||
|
||||
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 finalize_tls_handshake(osd_client_t *cl);
|
||||
bool derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer);
|
||||
|
||||
std::vector<msgr_iothread_t*> iothreads;
|
||||
@@ -269,14 +323,11 @@ protected:
|
||||
|
||||
public:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_i *ringloop = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
bool has_sendmsg_zc = false;
|
||||
// osd_num_t is only for logging and asserts
|
||||
uint64_t next_client_id = 1;
|
||||
// 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;
|
||||
osd_num_t osd_num;
|
||||
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;
|
||||
@@ -288,13 +339,11 @@ public:
|
||||
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 init_iothreads();
|
||||
void parse_config(const json11::Json & config, bool init);
|
||||
void parse_config(const json11::Json & config);
|
||||
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);
|
||||
@@ -303,11 +352,9 @@ 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);
|
||||
@@ -318,14 +365,13 @@ public:
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
bool is_rdma_enabled();
|
||||
json11::Json connect_rdma(uint64_t client_id, std::string rdma_address, uint64_t client_max_msg);
|
||||
bool connect_rdma(uint64_t client_id, std::string rdma_address, uint64_t client_max_msg);
|
||||
#endif
|
||||
#ifdef WITH_RDMACM
|
||||
bool is_use_rdmacm();
|
||||
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);
|
||||
@@ -351,7 +397,7 @@ protected:
|
||||
|
||||
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);
|
||||
template<typename T> size_t 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);
|
||||
@@ -371,7 +417,6 @@ protected:
|
||||
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();
|
||||
|
||||
+234
-103
@@ -7,6 +7,8 @@
|
||||
#include <isa-l_crypto/isal_crypto_api.h>
|
||||
#endif
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "str_util.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "messenger.h"
|
||||
@@ -14,9 +16,14 @@
|
||||
#include "http_client.h"
|
||||
#include "openssl_util.h"
|
||||
|
||||
#include <openssl/kdf.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#define MSGR_HSP_HS 1
|
||||
#define MSGR_HSP_SEND 2
|
||||
#define MSGR_HSP_RECV 4
|
||||
|
||||
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
|
||||
{
|
||||
#ifndef WITH_ISAL_CRYPTO
|
||||
@@ -44,10 +51,9 @@ op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::start(osd_client_t *cl, uint8_t *key, uint64_t start_offset, size_t block_size)
|
||||
void op_aes_xts_encrypt_t::start(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;
|
||||
@@ -92,28 +98,6 @@ void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
|
||||
#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,
|
||||
@@ -127,7 +111,7 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
|
||||
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);
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
@@ -160,7 +144,7 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, tmp);
|
||||
encrypted = true;
|
||||
copy_or_gcm(cl, out, tmp, max_out);
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in-1;
|
||||
offset += max_in;
|
||||
@@ -170,8 +154,6 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, 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;
|
||||
@@ -183,8 +165,6 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
|
||||
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;
|
||||
@@ -223,10 +203,9 @@ op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
|
||||
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)
|
||||
void op_aes_xts_decrypt_t::start(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;
|
||||
@@ -297,23 +276,6 @@ void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
|
||||
#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)
|
||||
@@ -361,8 +323,6 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, 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)
|
||||
@@ -375,18 +335,7 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
|
||||
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
|
||||
if (out)
|
||||
decrypt_block(in, out);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
@@ -399,8 +348,6 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
|
||||
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;
|
||||
@@ -425,39 +372,37 @@ void osd_messenger_t::op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf,
|
||||
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);
|
||||
cl->xts_enc_ctx->start(cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
|
||||
}
|
||||
size_t old_out = done_enc;
|
||||
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;
|
||||
}
|
||||
if (cl->write_csum_state && done_enc > old_out)
|
||||
XXH3_64bits_update(cl->write_csum_state, enc_buf+old_out, done_enc-old_out);
|
||||
}
|
||||
|
||||
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);
|
||||
size_t old_in = done_enc;
|
||||
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;
|
||||
}
|
||||
if (cl->read_csum_state && done_enc > old_in)
|
||||
XXH3_64bits_update(cl->read_csum_state, enc_buf+old_in, done_enc-old_in);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
|
||||
@@ -473,7 +418,7 @@ void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
|
||||
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->xts_dec_ctx->start(enc->key_chain, enc->chain_size,
|
||||
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
|
||||
cl->read_op->req.rw.offset, enc->bitmap_granularity);
|
||||
}
|
||||
@@ -548,17 +493,84 @@ void osd_messenger_t::op_encrypt_free(osd_client_t* cl)
|
||||
}
|
||||
}
|
||||
|
||||
struct tls_secrets_t
|
||||
{
|
||||
std::vector<uint8_t> client_secret;
|
||||
std::vector<uint8_t> server_secret;
|
||||
};
|
||||
|
||||
// Sadly we have to use a global variable to capture TLS 1.3 secrets
|
||||
static std::mutex logged_secrets_mu;
|
||||
static std::map<const SSL*, tls_secrets_t> logged_secrets;
|
||||
|
||||
static void openssl_key_log(const SSL *ssl, const char *line)
|
||||
{
|
||||
// Format: <CLIENT|SERVER>_TRAFFIC_SECRET_0 <server_random> <secret>
|
||||
bool is_client_secret = !strncmp(line, "CLIENT_TRAFFIC_SECRET_0 ", strlen("CLIENT_TRAFFIC_SECRET_0 "));
|
||||
bool is_server_secret = !strncmp(line, "SERVER_TRAFFIC_SECRET_0 ", strlen("SERVER_TRAFFIC_SECRET_0 "));
|
||||
if (!is_client_secret && !is_server_secret)
|
||||
return;
|
||||
const char *hex = strchr(line+strlen("CLIENT_TRAFFIC_SECRET_0 "), ' ');
|
||||
if (!hex)
|
||||
return;
|
||||
hex++;
|
||||
size_t len = strlen(hex);
|
||||
logged_secrets_mu.lock();
|
||||
auto & secrets = logged_secrets[ssl];
|
||||
logged_secrets_mu.unlock();
|
||||
auto & secret = is_client_secret ? secrets.client_secret : secrets.server_secret;
|
||||
secret.resize(len/2);
|
||||
fromhexstr(hex, len, secret.data(), secret.size());
|
||||
}
|
||||
|
||||
static bool derive_kdf(EVP_KDF_CTX* kdf_ctx, const uint8_t* insecret, size_t insecret_len,
|
||||
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *key, size_t size)
|
||||
{
|
||||
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);
|
||||
if (salt)
|
||||
params[n++] = OSSL_PARAM_construct_octet_string("salt", (void*)salt, 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)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
if (EVP_KDF_derive(kdf_ctx, key, size, NULL) <= 0)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer)
|
||||
{
|
||||
if (!cl->hs_result.shared_secret.size())
|
||||
std::vector<uint8_t> old_my = cl->my_key, old_peer = cl->peer_key;
|
||||
if (!cl->my_secret.size() || !cl->peer_secret.size())
|
||||
{
|
||||
cl->hs_result = cl->hs->get_result();
|
||||
if (handshake_hook)
|
||||
assert(cl->ssl_cli);
|
||||
logged_secrets_mu.lock();
|
||||
auto & secrets = logged_secrets[cl->ssl_cli];
|
||||
cl->my_secret = std::move(cl->is_incoming ? secrets.client_secret : secrets.server_secret);
|
||||
cl->peer_secret = std::move(!cl->is_incoming ? secrets.client_secret : secrets.server_secret);
|
||||
logged_secrets.erase(cl->ssl_cli);
|
||||
logged_secrets_mu.unlock();
|
||||
SSL_free(cl->ssl_cli);
|
||||
cl->ssl_cli = NULL;
|
||||
cl->gcm_enabled = true;
|
||||
cl->write_to_ssl = NULL;
|
||||
cl->read_from_ssl = NULL;
|
||||
if (cl->my_secret.size() < 32 || cl->peer_secret.size() < 32)
|
||||
{
|
||||
handshake_hook(cl);
|
||||
fprintf(stderr, "Client %ju error: failed to capture TLS handshake results\n", cl->client_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -566,9 +578,10 @@ bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool upd
|
||||
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(),
|
||||
ok = ok && derive_kdf(kdf_ctx, cl->my_secret.data(), cl->my_secret.size(),
|
||||
old_my.size() ? old_my.data() : NULL, old_my.size(),
|
||||
cl->is_incoming ? "server key" : "client key", cl->my_key.data(), len);
|
||||
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);
|
||||
@@ -577,9 +590,10 @@ bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool upd
|
||||
}
|
||||
if (update_peer || !old_peer.size())
|
||||
{
|
||||
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
|
||||
ok = ok && derive_kdf(kdf_ctx, cl->peer_secret.data(), cl->peer_secret.size(),
|
||||
old_peer.size() ? old_peer.data() : NULL, old_peer.size(),
|
||||
!cl->is_incoming ? "server key" : "client key", cl->peer_key.data(), len);
|
||||
!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);
|
||||
@@ -593,45 +607,147 @@ void osd_messenger_t::init_tls()
|
||||
{
|
||||
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
|
||||
{
|
||||
// Initialize TLS context
|
||||
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");
|
||||
fprintf(stderr, "Vitastor OSD TLS requires osd_tls_cert, osd_tls_key, osd_tls_ca, client_tls_ca\n");
|
||||
else
|
||||
fprintf(stderr, "Vitastor transport encryption requires cert, pkey and osd_ca options\n");
|
||||
fprintf(stderr, "Vitastor client TLS requires tls_cert, tls_key and osd_tls_ca\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))
|
||||
ssl_ctx = SSL_CTX_new(TLS_method());
|
||||
if (!ssl_ctx)
|
||||
{
|
||||
fprintf(stderr, "Error: %s\n", hs_ctx->get_error().c_str());
|
||||
init_err:
|
||||
fprintf(stderr, "OpenSSL initialization failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
// Always use TLS 1.3 with AES-256-GCM
|
||||
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||
SSL_CTX_set_ciphersuites(ssl_ctx, "TLS_AES_256_GCM_SHA384");
|
||||
SSL_CTX_set_keylog_callback(ssl_ctx, openssl_key_log);
|
||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
bool ok = SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||
ok = ok && (osd_tls_ca_obj = openssl_load_cert(osd_tls_ca));
|
||||
ok = ok && X509_STORE_add_cert(SSL_CTX_get_cert_store(ssl_ctx), osd_tls_ca_obj);
|
||||
if (osd_num)
|
||||
{
|
||||
// OSD uses 2 separate root certificates to distinguish between clients and peer OSDs
|
||||
ok = ok && (client_tls_ca_obj = openssl_load_cert(client_tls_ca));
|
||||
ok = ok && X509_STORE_add_cert(SSL_CTX_get_cert_store(ssl_ctx), client_tls_ca_obj);
|
||||
}
|
||||
ok = ok && openssl_ctx_use_cert(ssl_ctx, tls_cert, tls_cn);
|
||||
ok = ok && openssl_ctx_use_key(ssl_ctx, tls_key);
|
||||
EVP_KDF *kdf;
|
||||
ok = ok && (kdf = EVP_KDF_fetch(NULL, "hkdf", NULL));
|
||||
ok = ok && (kdf_ctx = EVP_KDF_CTX_new(kdf));
|
||||
if (kdf)
|
||||
EVP_KDF_free(kdf);
|
||||
if (!ok)
|
||||
{
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
ssl_ctx = NULL;
|
||||
goto init_err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_tls_client(osd_client_t *cl)
|
||||
{
|
||||
if (gcm_enabled)
|
||||
if (!tls_cert.empty())
|
||||
{
|
||||
cl->gcm_enabled = true;
|
||||
cl->hs = hs_ctx->create();
|
||||
cl->hs->init(cl->is_incoming);
|
||||
if (cl->hs->out_size())
|
||||
cl->write_to_ssl = BIO_new(BIO_s_mem());
|
||||
cl->read_from_ssl = BIO_new(BIO_s_mem());
|
||||
cl->ssl_cli = SSL_new(ssl_ctx);
|
||||
cl->ssl_handshake_pending = MSGR_HSP_HS;
|
||||
if (!cl->ssl_cli)
|
||||
{
|
||||
if (cl->write_state == 0)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
fprintf(stderr, "OpenSSL initialization failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
|
||||
exit(1);
|
||||
}
|
||||
if (cl->is_incoming)
|
||||
{
|
||||
SSL_set_accept_state(cl->ssl_cli);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_set_connect_state(cl->ssl_cli);
|
||||
}
|
||||
SSL_set_bio(cl->ssl_cli, cl->write_to_ssl, cl->read_from_ssl);
|
||||
bool ok = do_tls_handshake(cl);
|
||||
assert(ok);
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_messenger_t::do_tls_handshake(osd_client_t *cl, bool from_recv)
|
||||
{
|
||||
if (!(cl->ssl_handshake_pending & MSGR_HSP_HS))
|
||||
return true;
|
||||
int r = SSL_do_handshake(cl->ssl_cli);
|
||||
if (r > 0)
|
||||
{
|
||||
// Server-side OpenSSL treats handshake as finalized only when receiving
|
||||
// the first message, so we transmit 1 byte after connecting and only then
|
||||
// finalize the handshake
|
||||
cl->ssl_handshake_pending = MSGR_HSP_SEND|MSGR_HSP_RECV;
|
||||
if (cl->write_state == 0 && from_recv)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
r = SSL_get_error(cl->ssl_cli, r);
|
||||
if (r != 0 && r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
fprintf(stderr, "Client %ju TLS handshake error: %s, stopping client\n", cl->client_id, ERR_error_string(ERR_get_error(), NULL));
|
||||
cl->io_error = true;
|
||||
return false;
|
||||
}
|
||||
if (from_recv && cl->write_state == 0 && openssl_bio_nonempty(cl->read_from_ssl))
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool osd_messenger_t::finalize_tls_handshake(osd_client_t *cl)
|
||||
{
|
||||
if (cl->ssl_handshake_pending)
|
||||
return true;
|
||||
// Capture secrets and switch to direct AES-256-GCM encryption
|
||||
if (!derive_aes_keys(cl, true, true))
|
||||
{
|
||||
cl->io_error = true;
|
||||
return false;
|
||||
}
|
||||
if (cl->read_op)
|
||||
{
|
||||
assert(!cl->read_op_pos);
|
||||
delete cl->read_op;
|
||||
cl->read_op = NULL;
|
||||
}
|
||||
if (cl->write_op)
|
||||
{
|
||||
assert(!cl->write_op_pos);
|
||||
cl->write_ops.insert(cl->write_ops.begin(), cl->write_op);
|
||||
cl->write_op = NULL;
|
||||
}
|
||||
if (cl->write_state == 0)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
// Switched to direct AES-GCM, stop SSL callers
|
||||
return false;
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_tls()
|
||||
@@ -655,9 +771,24 @@ void osd_messenger_t::destroy_tls()
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
#endif
|
||||
if (hs_ctx)
|
||||
if (osd_tls_ca_obj)
|
||||
{
|
||||
delete hs_ctx;
|
||||
hs_ctx = NULL;
|
||||
X509_free(osd_tls_ca_obj);
|
||||
osd_tls_ca_obj = NULL;
|
||||
}
|
||||
if (client_tls_ca_obj)
|
||||
{
|
||||
X509_free(client_tls_ca_obj);
|
||||
client_tls_ca_obj = NULL;
|
||||
}
|
||||
if (ssl_ctx)
|
||||
{
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
ssl_ctx = NULL;
|
||||
}
|
||||
if (kdf_ctx)
|
||||
{
|
||||
EVP_KDF_CTX_free(kdf_ctx);
|
||||
kdf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,11 @@
|
||||
#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;
|
||||
@@ -35,7 +32,7 @@ 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 start(uint8_t *key, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
@@ -46,7 +43,6 @@ 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;
|
||||
@@ -65,7 +61,7 @@ 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 start(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);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,810 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// 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;
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// 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);
|
||||
};
|
||||
+4
-50
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "messenger.h"
|
||||
#include "msgr_op.h"
|
||||
|
||||
osd_op_t::~osd_op_t()
|
||||
@@ -24,6 +23,10 @@ osd_op_t::~osd_op_t()
|
||||
// So we don't reuse it, but free it every time
|
||||
free(buf);
|
||||
}
|
||||
if (enc_buf)
|
||||
{
|
||||
free(enc_buf);
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_op_t::is_recovery_related()
|
||||
@@ -39,52 +42,3 @@ bool osd_op_t::is_recovery_related()
|
||||
req.hdr.opcode == OSD_OP_SEC_SYNC &&
|
||||
(req.sec_sync.flags & OSD_OP_RECOVERY_RELATED);
|
||||
}
|
||||
|
||||
void osd_messenger_t::measure_exec(osd_op_t *cur_op)
|
||||
{
|
||||
// Measure execution latency
|
||||
if (cur_op->req.hdr.opcode > OSD_OP_MAX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!cur_op->tv_end.tv_sec)
|
||||
{
|
||||
clock_gettime(CLOCK_REALTIME, &cur_op->tv_end);
|
||||
}
|
||||
uint64_t len = 0;
|
||||
if (cur_op->req.hdr.opcode == OSD_OP_READ ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_WRITE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SCRUB)
|
||||
{
|
||||
// req.rw.len is internally set to the full object size for scrubs
|
||||
len = cur_op->req.rw.len;
|
||||
}
|
||||
else if (cur_op->req.hdr.opcode == OSD_OP_SEC_READ ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
|
||||
{
|
||||
len = cur_op->req.sec_rw.len;
|
||||
}
|
||||
inc_op_stats(stats, cur_op->req.hdr.opcode, cur_op->tv_begin, cur_op->tv_end, len);
|
||||
if (cur_op->is_recovery_related())
|
||||
{
|
||||
inc_op_stats(recovery_stats, cur_op->req.hdr.opcode, cur_op->tv_begin, cur_op->tv_end, len);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::inc_op_stats(osd_op_stats_t & stats, uint64_t opcode, timespec & tv_begin, timespec & tv_end, uint64_t len)
|
||||
{
|
||||
uint64_t usecs = (
|
||||
(tv_end.tv_sec - tv_begin.tv_sec)*1000000 +
|
||||
(tv_end.tv_nsec - tv_begin.tv_nsec)/1000
|
||||
);
|
||||
stats.op_stat_count[opcode]++;
|
||||
if (!stats.op_stat_count[opcode])
|
||||
{
|
||||
stats.op_stat_count[opcode] = 1;
|
||||
stats.op_stat_sum[opcode] = 0;
|
||||
stats.op_stat_bytes[opcode] = 0;
|
||||
}
|
||||
stats.op_stat_sum[opcode] += usecs;
|
||||
stats.op_stat_bytes[opcode] += len;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,6 @@ 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;
|
||||
@@ -182,10 +181,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;
|
||||
size_t bmp_data = 0;
|
||||
unsigned bmp_data = 0;
|
||||
uint8_t *bitmap_buf = NULL;
|
||||
void *rmw_buf = NULL;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
uint8_t *enc_buf = NULL;
|
||||
uint64_t csum = 0; // network layer checksum
|
||||
osd_primary_op_data_t* op_data = NULL;
|
||||
std::function<void(osd_op_t*)> callback;
|
||||
|
||||
@@ -507,7 +507,7 @@ int msgr_rdma_connection_t::connect(msgr_rdma_address_t *dest)
|
||||
return 0;
|
||||
}
|
||||
|
||||
json11::Json osd_messenger_t::connect_rdma(uint64_t client_id, std::string rdma_address, uint64_t client_max_msg)
|
||||
bool osd_messenger_t::connect_rdma(uint64_t client_id, std::string rdma_address, uint64_t client_max_msg)
|
||||
{
|
||||
// Try to connect to the peer using RDMA
|
||||
msgr_rdma_address_t addr;
|
||||
@@ -523,7 +523,7 @@ json11::Json osd_messenger_t::connect_rdma(uint64_t client_id, std::string rdma_
|
||||
{
|
||||
if (log_level > 0)
|
||||
fprintf(stderr, "No RDMA context for peer %ju, using only TCP\n", client_id);
|
||||
return json11::Json();
|
||||
return false;
|
||||
}
|
||||
msgr_rdma_connection_t *rdma_conn = msgr_rdma_connection_t::create(selected_ctx, rdma_max_send, rdma_max_recv, rdma_max_sge, client_max_msg);
|
||||
if (rdma_conn)
|
||||
@@ -542,14 +542,11 @@ json11::Json osd_messenger_t::connect_rdma(uint64_t client_id, std::string rdma_
|
||||
// Remember connection, but switch to RDMA only after sending the configuration response
|
||||
cl->rdma_conn = rdma_conn;
|
||||
cl->peer_state = PEER_RDMA_CONNECTING;
|
||||
return json11::Json::object{
|
||||
{"rdma_address", rdma_conn->addr.to_string()},
|
||||
{"rdma_max_msg", rdma_conn->max_msg},
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return json11::Json();
|
||||
return false;
|
||||
}
|
||||
|
||||
static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
|
||||
@@ -593,9 +590,7 @@ 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_done_pos
|
||||
? rc->send_out_size-rc->send_out_pos
|
||||
: rc->send_done_pos-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);
|
||||
if (dst_len > rc->max_msg)
|
||||
dst_len = rc->max_msg;
|
||||
copied = copy_ops_to(cl, dst, dst_len);
|
||||
@@ -610,7 +605,7 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
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,
|
||||
@@ -735,12 +730,9 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
if (rc->send_done_pos == rc->send_out_size)
|
||||
rc->send_done_pos = 0;
|
||||
assert(rc->send_done_pos < rc->send_out_size);
|
||||
while (osd_op_t *op = cl->send_free_ops.front())
|
||||
while (cl->send_free_ops.front())
|
||||
{
|
||||
if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
delete cl->send_free_ops.front();
|
||||
cl->send_free_ops.pop_front();
|
||||
}
|
||||
cl->send_free_ops.pop_front();
|
||||
@@ -754,16 +746,3 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
}
|
||||
} while (event_count > 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+258
-145
@@ -4,16 +4,21 @@
|
||||
#define _XOPEN_SOURCE
|
||||
#include <limits.h>
|
||||
#include "messenger.h"
|
||||
#include "msgr_iothread.h"
|
||||
#include "openssl_util.h"
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#define RDR_GCM 1
|
||||
#define RDR_XTS 2
|
||||
#define RDR_NO_CSUM 4
|
||||
|
||||
#define MSGR_HSP_HS 1
|
||||
#define MSGR_HSP_SEND 2
|
||||
#define MSGR_HSP_RECV 4
|
||||
|
||||
class msgr_op_reader_t
|
||||
{
|
||||
public:
|
||||
@@ -89,6 +94,196 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class ssl_op_reader_t: public msgr_op_reader_t
|
||||
{
|
||||
osd_messenger_t* msgr;
|
||||
osd_client_t* cl;
|
||||
size_t from;
|
||||
|
||||
uint8_t *curbuf;
|
||||
size_t bufsize;
|
||||
size_t done;
|
||||
|
||||
bool read_ssl(void *buf, size_t & len)
|
||||
{
|
||||
int ok = SSL_read_ex(cl->ssl_cli, buf, len, &len);
|
||||
if (ok > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
len = 0;
|
||||
ok = SSL_get_error(cl->ssl_cli, ok);
|
||||
if (ok == SSL_ERROR_ZERO_RETURN)
|
||||
{
|
||||
fprintf(stderr, "Client %ju TLS disconnected\n", cl->client_id);
|
||||
cl->io_error = true;
|
||||
return false;
|
||||
}
|
||||
else if (ok != 0 && ok != SSL_ERROR_WANT_WRITE && ok != SSL_ERROR_WANT_READ)
|
||||
{
|
||||
fprintf(stderr, "Client %ju TLS read error: %s. Disconnecting client\n", cl->client_id, ERR_error_string(ERR_get_error(), NULL));
|
||||
cl->io_error = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
ssl_op_reader_t(osd_messenger_t* msgr, osd_client_t* cl, uint8_t *curbuf, size_t bufsize):
|
||||
msgr(msgr), cl(cl), from(cl->read_op_pos), curbuf(curbuf), bufsize(bufsize), done(0)
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
from = cl->read_op_pos;
|
||||
}
|
||||
|
||||
void buffer_encrypted()
|
||||
{
|
||||
if (cl->ssl_read_header_size < sizeof(msgr_tls_record_hdr_t))
|
||||
{
|
||||
size_t h = bufsize-done;
|
||||
if (bufsize-done <= sizeof(msgr_tls_record_hdr_t)-cl->ssl_read_header_size)
|
||||
{
|
||||
// Less than record header or just record header
|
||||
memcpy(((uint8_t*)&cl->ssl_read_record) + cl->ssl_read_header_size, curbuf+done, h);
|
||||
cl->ssl_read_header_size += h;
|
||||
if (cl->ssl_read_header_size == sizeof(msgr_tls_record_hdr_t))
|
||||
cl->ssl_read_record.size = ntohs(cl->ssl_read_record.size);
|
||||
int r = BIO_write(cl->write_to_ssl, curbuf+done, h);
|
||||
assert(r == h);
|
||||
done += h;
|
||||
return;
|
||||
}
|
||||
// Record header and at least some data - copy both to BIO in a one BIO_write() call
|
||||
h = sizeof(msgr_tls_record_hdr_t)-cl->ssl_read_header_size;
|
||||
memcpy(((uint8_t*)&cl->ssl_read_record) + cl->ssl_read_header_size, curbuf+done, h);
|
||||
cl->ssl_read_header_size = sizeof(msgr_tls_record_hdr_t);
|
||||
cl->ssl_read_record.size = ntohs(cl->ssl_read_record.size);
|
||||
size_t n = h + cl->ssl_read_record.size;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
int r = BIO_write(cl->write_to_ssl, curbuf+done, n);
|
||||
assert(r == n);
|
||||
done += n;
|
||||
cl->ssl_read_record.size -= (n - h);
|
||||
if (!cl->ssl_read_record.size)
|
||||
cl->ssl_read_header_size = 0;
|
||||
return;
|
||||
}
|
||||
// Continued TLS data - buffer it to BIO
|
||||
size_t n = cl->ssl_read_record.size;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
int r = BIO_write(cl->write_to_ssl, curbuf+done, n);
|
||||
assert(r == n);
|
||||
done += n;
|
||||
cl->ssl_read_record.size -= n;
|
||||
if (!cl->ssl_read_record.size)
|
||||
cl->ssl_read_header_size = 0;
|
||||
}
|
||||
|
||||
bool read(uint8_t *dst, size_t dst_len, int flags) override
|
||||
{
|
||||
if (from >= dst_len)
|
||||
{
|
||||
// Skip
|
||||
from -= dst_len;
|
||||
return true;
|
||||
}
|
||||
if (done >= bufsize)
|
||||
return false;
|
||||
size_t n = dst_len-from;
|
||||
if (!(flags & RDR_GCM) || !cl->ssl_cli)
|
||||
{
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
if (flags & RDR_XTS)
|
||||
{
|
||||
msgr->op_decrypted_copy_buf(cl, curbuf, bufsize, dst, dst_len, from, done);
|
||||
n = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
|
||||
{
|
||||
// data may be skipped if dst == NULL but checksum is still calculated
|
||||
XXH3_64bits_update(cl->read_csum_state, curbuf+done, n);
|
||||
}
|
||||
// Here, dst == NULL is allowed
|
||||
if (dst != NULL)
|
||||
memcpy(dst+from, curbuf+done, n);
|
||||
done += n;
|
||||
}
|
||||
cl->read_op_pos += n;
|
||||
from += n;
|
||||
if (from < dst_len)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here, dst == NULL is not allowed
|
||||
assert(dst != NULL);
|
||||
buffer_again:
|
||||
buffer_encrypted();
|
||||
if (cl->ssl_handshake_pending)
|
||||
{
|
||||
if (!msgr->do_tls_handshake(cl, true))
|
||||
return false;
|
||||
if (cl->ssl_handshake_pending & MSGR_HSP_RECV)
|
||||
{
|
||||
uint8_t first_byte = 0;
|
||||
size_t b = 1;
|
||||
if (!read_ssl(&first_byte, b))
|
||||
return false;
|
||||
if (!b)
|
||||
{
|
||||
if (done < bufsize)
|
||||
goto buffer_again;
|
||||
return false;
|
||||
}
|
||||
cl->ssl_handshake_pending &= ~MSGR_HSP_RECV;
|
||||
if (!msgr->finalize_tls_handshake(cl))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!read_ssl(dst+from, n))
|
||||
{
|
||||
if (done < bufsize)
|
||||
goto buffer_again;
|
||||
return false;
|
||||
}
|
||||
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
|
||||
{
|
||||
XXH3_64bits_update(cl->read_csum_state, dst+from, n);
|
||||
}
|
||||
cl->read_op_pos += n;
|
||||
from += n;
|
||||
if (from < dst_len)
|
||||
{
|
||||
if (done < bufsize)
|
||||
goto buffer_again;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
from = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool finish() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t get_done()
|
||||
{
|
||||
return done;
|
||||
}
|
||||
};
|
||||
|
||||
class gcm_op_reader_t: public msgr_op_reader_t
|
||||
{
|
||||
osd_messenger_t* msgr;
|
||||
@@ -132,12 +327,6 @@ public:
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (cl->peer_iv_ctr >= AES_256_GCM_MAX_IV_CTR)
|
||||
{
|
||||
// Rotate key every 2^32 messages
|
||||
bool ok = msgr->derive_aes_keys(cl, false, true);
|
||||
assert(ok);
|
||||
}
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_init_256(&cl->peer_key_isal, cl->dec_ctx, cl->peer_key.data() + AES_256_GCM_KEY_SIZE, NULL, 0);
|
||||
if (r != 0)
|
||||
@@ -170,17 +359,41 @@ public:
|
||||
if (done >= bufsize)
|
||||
return false;
|
||||
size_t n = dst_len-from;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
if (flags & RDR_XTS)
|
||||
if (!(flags & RDR_GCM))
|
||||
{
|
||||
msgr->op_decrypted_copy_buf(cl, curbuf, bufsize, dst, dst_len, from, done);
|
||||
n = 0;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
if (flags & RDR_XTS)
|
||||
{
|
||||
msgr->op_decrypted_copy_buf(cl, curbuf, bufsize, dst, dst_len, from, done);
|
||||
n = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
|
||||
{
|
||||
// data may be skipped if dst == NULL but checksum is still calculated
|
||||
XXH3_64bits_update(cl->read_csum_state, curbuf+done, n);
|
||||
}
|
||||
// Here, dst == NULL is allowed
|
||||
if (dst != NULL)
|
||||
memcpy(dst+from, curbuf+done, n);
|
||||
done += n;
|
||||
}
|
||||
cl->read_op_pos += n;
|
||||
from += n;
|
||||
if (from < dst_len)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (flags & RDR_GCM)
|
||||
else
|
||||
{
|
||||
// Here, dst == NULL is not allowed
|
||||
assert(dst != NULL);
|
||||
size_t n = dst_len-from;
|
||||
if (n > bufsize-done)
|
||||
n = bufsize-done;
|
||||
#ifdef WITH_ISAL_CRYPTO
|
||||
int r = isal_aes_gcm_dec_256_update(&cl->peer_key_isal, cl->dec_ctx, dst+from, curbuf+done, n);
|
||||
assert(!r);
|
||||
@@ -199,24 +412,12 @@ public:
|
||||
XXH3_64bits_update(cl->read_csum_state, dst+from, n);
|
||||
}
|
||||
done += n;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cl->read_csum_state && !(flags & RDR_NO_CSUM))
|
||||
from += n;
|
||||
cl->read_op_pos += n;
|
||||
if (from < dst_len)
|
||||
{
|
||||
// data may be skipped if dst == NULL but checksum is still calculated
|
||||
XXH3_64bits_update(cl->read_csum_state, curbuf+done, n);
|
||||
return false;
|
||||
}
|
||||
// Here, dst == NULL is allowed
|
||||
if (dst != NULL)
|
||||
memcpy(dst+from, curbuf+done, n);
|
||||
done += n;
|
||||
}
|
||||
cl->read_op_pos += n;
|
||||
from += n;
|
||||
if (from < dst_len)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
from = 0;
|
||||
return true;
|
||||
@@ -353,9 +554,9 @@ public:
|
||||
from -= dst_len;
|
||||
return true;
|
||||
}
|
||||
if ((flags & RDR_GCM) && cl->gcm_enabled)
|
||||
if ((flags & RDR_GCM) && (cl->ssl_cli || cl->gcm_enabled))
|
||||
{
|
||||
// Can't inplace read encrypted data
|
||||
// Can't inplace read TLS/GCM data
|
||||
return false;
|
||||
}
|
||||
if (cl->recv_list.size() >= IOV_MAX)
|
||||
@@ -575,56 +776,29 @@ void osd_messenger_t::handle_immediate_ops()
|
||||
|
||||
bool osd_messenger_t::handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize)
|
||||
{
|
||||
if (cl->gcm_enabled)
|
||||
size_t done;
|
||||
if (cl->ssl_cli)
|
||||
{
|
||||
if (cl->hs)
|
||||
done = handle_buffer_with<ssl_op_reader_t>(cl, curbuf, bufsize);
|
||||
if (done > 0 && done < bufsize && !cl->ssl_cli && cl->gcm_enabled)
|
||||
{
|
||||
ssize_t done = cl->hs->handle(curbuf, bufsize);
|
||||
if (done < 0)
|
||||
{
|
||||
fprintf(stderr, "Client %ju handshake failed: %s\n", cl->client_id, cl->hs->get_error().c_str());
|
||||
stop_client(cl->client_id);
|
||||
return false;
|
||||
}
|
||||
if (cl->hs->done() && !derive_aes_keys(cl, true, true))
|
||||
{
|
||||
stop_client(cl->client_id);
|
||||
return false;
|
||||
}
|
||||
curbuf += done;
|
||||
bufsize -= done;
|
||||
if (cl->hs->out_size())
|
||||
{
|
||||
if (cl->write_state == 0)
|
||||
{
|
||||
cl->write_state = CL_WRITE_READY;
|
||||
write_ready_clients.push_back(cl->client_id);
|
||||
}
|
||||
}
|
||||
if (cl->hs->done() && !cl->hs->out_size())
|
||||
{
|
||||
// Delete hs when done and nothing to send
|
||||
delete cl->hs;
|
||||
cl->hs = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (done < bufsize)
|
||||
{
|
||||
fprintf(stderr, "Client %ju extra data after handshake\n", cl->client_id);
|
||||
stop_client(cl->client_id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
done += handle_buffer_with<gcm_op_reader_t>(cl, curbuf+done, bufsize-done);
|
||||
}
|
||||
return handle_buffer_with<gcm_op_reader_t>(cl, curbuf, bufsize);
|
||||
}
|
||||
return handle_buffer_with<copy_op_reader_t>(cl, curbuf, bufsize);
|
||||
else if (cl->gcm_enabled)
|
||||
{
|
||||
done = handle_buffer_with<gcm_op_reader_t>(cl, curbuf, bufsize);
|
||||
}
|
||||
else
|
||||
{
|
||||
done = handle_buffer_with<copy_op_reader_t>(cl, curbuf, bufsize);
|
||||
}
|
||||
assert(!done || done == bufsize);
|
||||
return !!done;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool osd_messenger_t::handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size_t bufsize)
|
||||
size_t osd_messenger_t::handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size_t bufsize)
|
||||
{
|
||||
T rdr(this, cl, curbuf, bufsize);
|
||||
// Reset OSD ping state
|
||||
@@ -658,13 +832,12 @@ bool osd_messenger_t::handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size
|
||||
if (cl->io_error)
|
||||
{
|
||||
stop_client(cl->client_id);
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(rdr.get_done() == bufsize);
|
||||
return true;
|
||||
return rdr.get_done();
|
||||
}
|
||||
|
||||
bool osd_messenger_t::handle_hdr(osd_client_t *cl)
|
||||
@@ -720,42 +893,18 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
|
||||
{
|
||||
osd_op_t *cur_op = cl->read_op;
|
||||
cl->read_op_size = 0;
|
||||
if (!osd_num)
|
||||
{
|
||||
if (log_level > 1)
|
||||
fprintf(stderr, "Error: operation received from an OSD peer %ju, stopping\n", cl->client_id);
|
||||
return false;
|
||||
}
|
||||
else if (cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
|
||||
if (cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
|
||||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
|
||||
{
|
||||
if (cur_op->req.sec_rw.attr_len > 0)
|
||||
{
|
||||
if (cur_op->req.sec_rw.attr_len > clean_entry_bitmap_size)
|
||||
{
|
||||
if (log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Error: peer %ju secondary write request attr_len too large (%u > %u bytes), stopping\n", cl->client_id,
|
||||
cur_op->req.sec_rw.attr_len, clean_entry_bitmap_size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (cur_op->req.sec_rw.attr_len > sizeof(cur_op->bmp_data))
|
||||
if (cur_op->req.sec_rw.attr_len > sizeof(unsigned))
|
||||
cur_op->bitmap = cur_op->rmw_buf = malloc_or_die(cur_op->req.sec_rw.attr_len);
|
||||
else
|
||||
cur_op->bitmap = &cur_op->bmp_data;
|
||||
}
|
||||
if (cur_op->req.sec_rw.len > 0)
|
||||
{
|
||||
if (cur_op->req.sec_rw.len > bs_block_size)
|
||||
{
|
||||
if (log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Error: peer %ju secondary write request size too large (%u > %u bytes), stopping\n", cl->client_id,
|
||||
cur_op->req.sec_rw.len, bs_block_size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_rw.len);
|
||||
}
|
||||
cl->read_op_size = cur_op->req.sec_rw.len + cur_op->req.sec_rw.attr_len;
|
||||
@@ -765,15 +914,6 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
|
||||
{
|
||||
if (cur_op->req.sec_stab.len > 0)
|
||||
{
|
||||
if (cur_op->req.sec_stab.len > MAX_SIMPLE_PAYLOAD_SIZE)
|
||||
{
|
||||
if (log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Error: peer %ju stabilize request size too large (%lu > %u bytes), stopping\n", cl->client_id,
|
||||
cur_op->req.sec_stab.len, MAX_SIMPLE_PAYLOAD_SIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_stab.len);
|
||||
}
|
||||
cl->read_op_size = cur_op->req.sec_stab.len;
|
||||
@@ -782,15 +922,6 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
|
||||
{
|
||||
if (cur_op->req.sec_read_bmp.len > 0)
|
||||
{
|
||||
if (cur_op->req.sec_read_bmp.len > MAX_SIMPLE_PAYLOAD_SIZE)
|
||||
{
|
||||
if (log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Error: peer %ju sec_read_bmp request size too large (%lu > %u bytes), stopping\n", cl->client_id,
|
||||
cur_op->req.sec_read_bmp.len, MAX_SIMPLE_PAYLOAD_SIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_read_bmp.len);
|
||||
}
|
||||
cl->read_op_size = cur_op->req.sec_read_bmp.len;
|
||||
@@ -799,15 +930,6 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
|
||||
{
|
||||
if (cur_op->req.rw.len > 0)
|
||||
{
|
||||
if (cur_op->req.rw.len > max_write_request_size)
|
||||
{
|
||||
if (log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Error: peer %ju write request size too large (%u > %u bytes), stopping\n", cl->client_id,
|
||||
cur_op->req.rw.len, max_write_request_size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.rw.len);
|
||||
}
|
||||
cl->read_op_size = cur_op->req.rw.len;
|
||||
@@ -816,15 +938,6 @@ bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
|
||||
{
|
||||
if (cur_op->req.show_conf.json_len > 0)
|
||||
{
|
||||
if (cur_op->req.show_conf.json_len > MAX_SIMPLE_PAYLOAD_SIZE)
|
||||
{
|
||||
if (log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Error: peer %ju show_config request length too large (%lu > %u bytes), stopping\n", cl->client_id,
|
||||
cur_op->req.show_conf.json_len, MAX_SIMPLE_PAYLOAD_SIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cur_op->buf = malloc_or_die(cur_op->req.show_conf.json_len+1);
|
||||
((uint8_t*)cur_op->buf)[cur_op->req.show_conf.json_len] = 0;
|
||||
}
|
||||
@@ -853,7 +966,7 @@ bool osd_messenger_t::allocate_reply_buffers(osd_client_t *cl, osd_op_t *op)
|
||||
cl->client_id, expected_size, op->bitmap_len, op->reply.hdr.retval, bmp_len);
|
||||
return false;
|
||||
}
|
||||
if (op->reply.hdr.retval >= 0 && bmp_len > 0)
|
||||
if (bmp_len > 0)
|
||||
{
|
||||
assert(op->bitmap);
|
||||
cl->read_op_size += bmp_len;
|
||||
@@ -921,7 +1034,7 @@ bool osd_messenger_t::op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr)
|
||||
{
|
||||
if (!rdr.read((uint8_t*)op->bitmap, op->req.sec_rw.attr_len, RDR_GCM))
|
||||
return false;
|
||||
if (!rdr.read((uint8_t*)op->buf, op->req.sec_rw.len, cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0))
|
||||
if (!rdr.read((uint8_t*)op->buf, op->req.sec_rw.len, 0))
|
||||
return false;
|
||||
}
|
||||
else if (op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
|
||||
@@ -937,7 +1050,7 @@ bool osd_messenger_t::op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr)
|
||||
}
|
||||
else if (op->req.hdr.opcode == OSD_OP_WRITE)
|
||||
{
|
||||
if (!rdr.read((uint8_t*)op->buf, op->req.rw.len, cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0))
|
||||
if (!rdr.read((uint8_t*)op->buf, op->req.rw.len, 0))
|
||||
return false;
|
||||
}
|
||||
else if (op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)
|
||||
@@ -961,7 +1074,7 @@ switched_type:
|
||||
if (op->reply.hdr.retval > 0)
|
||||
{
|
||||
for (int i = 0; i < op->iov.count; i++)
|
||||
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, (cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0)))
|
||||
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, 0))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -975,7 +1088,7 @@ switched_type:
|
||||
if (op->reply.hdr.retval > 0)
|
||||
{
|
||||
for (int i = 0; i < op->iov.count; i++)
|
||||
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, (op->enc ? RDR_XTS : 0) | (cl->proto_csum_status == MSGR_CSUM_GCM ? RDR_GCM : 0)))
|
||||
if (!rdr.read((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len, (op->enc ? RDR_XTS : 0)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user