Compare commits

..
Author SHA1 Message Date
Vitaliy Filippov ee0bae573f Remove n_subops=0 (TODO also needs OSD unit tests) 2026-05-21 13:31:32 +03:00
244 changed files with 2946 additions and 26934 deletions
-270
View File
@@ -234,60 +234,6 @@ jobs:
echo ""
done
test_etcd_fail_https:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_etcd_fail_https_antietcd:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_snapshot_https:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_interrupted_rebalance:
runs-on: ubuntu-latest
needs: build
@@ -360,78 +306,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
@@ -774,42 +648,6 @@ jobs:
echo ""
done
test_snapshot_chain_encrypted:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_snapshot_chain_enc_gcm:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: TEST_NAME=enc_gcm ENCRYPTED=1 VITASTOR_CFG=',"proto_checksums":"gcm"' /root/vitastor/tests/test_snapshot_chain.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_snapshot_chain:
runs-on: ubuntu-latest
needs: build
@@ -1368,24 +1206,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
@@ -1404,24 +1224,6 @@ jobs:
echo ""
done
test_checksum_xxhash:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_checksum:
runs-on: ubuntu-latest
needs: build
@@ -1656,24 +1458,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 +1494,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
@@ -2286,39 +2052,3 @@ jobs:
echo ""
done
test_write_encrypted:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_write_encrypted.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_write_encrypted_ec:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
-8
View File
@@ -38,14 +38,6 @@ for my $line (<>)
{
$test_name .= '_antietcd';
}
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
{
$test_name .= '_https';
}
elsif ($1 eq 'ENCRYPTED')
{
$test_name .= '_encrypted';
}
elsif ($1 eq 'OLD')
{
$test_name =~ s/^test_/test_old_/s;
-1
View File
@@ -3,4 +3,3 @@
package-lock.json
fio
qemu
node_modules
+1 -1
View File
@@ -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.12")
include(CTest)
-1
View File
@@ -62,7 +62,6 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
- [Прочие параметры OSD](docs/config/osd.ru.md)
- [Параметры мониторов](docs/config/monitor.ru.md)
- [Безопасность](docs/config/security.ru.md)
- [Настройки пулов](docs/config/pool.ru.md)
- [Метаданные образов в etcd](docs/config/inode.ru.md)
- Использование
-1
View File
@@ -62,7 +62,6 @@ Read more details in the documentation. You can start from here: [Quick Start](d
- [OSD Disk Layout](docs/config/layout-osd.en.md)
- [OSD Runtime Parameters](docs/config/osd.en.md)
- [Monitor](docs/config/monitor.en.md)
- [Security](docs/config/security.en.md)
- [Pool configuration](docs/config/pool.en.md)
- [Image metadata in etcd](docs/config/inode.en.md)
- Usage
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.15
VITASTOR_VERSION ?= v3.0.12
all: build push
+1 -1
View File
@@ -49,7 +49,7 @@ spec:
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: vitalif/vitastor-csi:v3.0.15
image: vitalif/vitastor-csi:v3.0.12
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -121,7 +121,7 @@ spec:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
image: vitalif/vitastor-csi:v3.0.15
image: vitalif/vitastor-csi:v3.0.12
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -5,7 +5,7 @@ package vitastor
const (
vitastorCSIDriverName = "csi.vitastor.io"
vitastorCSIDriverVersion = "3.0.15"
vitastorCSIDriverVersion = "3.0.12"
)
// Config struct fills the parameters of request or user input
+1 -1
View File
@@ -1,4 +1,4 @@
vitastor (3.0.15-1) unstable; urgency=medium
vitastor (3.0.12-1) unstable; urgency=medium
* Bugfixes
+1 -1
View File
@@ -3,7 +3,7 @@ Section: admin
Priority: optional
Maintainer: Vitaliy Filippov <vitalif@yourcmc.ru>
Build-Depends: debhelper, g++ (>= 8), libstdc++6 (>= 8),
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev, libc-ares-dev,
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
node-bindings <!nocheck>, node-gyp, node-nan
Standards-Version: 4.5.0
-1
View File
@@ -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
+6
View File
@@ -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)
+8 -6
View File
@@ -12,18 +12,20 @@ ARG REL=
WORKDIR /root
RUN set -e -x; \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
if [ "$REL" = "buster" ]; then \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
fi; \
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
RUN apt-get update && \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
apt-get -y build-dep fio && \
apt-get --download-only source fio
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.15
VITASTOR_VERSION ?= v3.0.12
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
+1 -1
View File
@@ -4,7 +4,7 @@
#
# Desired Vitastor version
VITASTOR_VERSION=v3.0.15
VITASTOR_VERSION=v3.0.12
# Additional arguments for all containers
# For example, you may want to specify a custom logging driver here
+3
View File
@@ -1 +1,4 @@
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
ETCD_NAME=""
ETCD_IP=""
ETCD_INITIAL_CLUSTER=""
-1
View File
@@ -38,4 +38,3 @@ In the future, additional configuration methods may be added:
- [OSD Disk Layout](config/layout-osd.en.md)
- [OSD Runtime Parameters](config/osd.en.md)
- [Monitor](config/monitor.en.md)
- [Security Parameters](config/security.en.md)
-1
View File
@@ -41,4 +41,3 @@
- [Дисковые параметры OSD](config/layout-osd.ru.md)
- [Прочие параметры OSD](config/osd.ru.md)
- [Параметры мониторов](config/monitor.ru.md)
- [Параметры безопасности](config/security.ru.md)
+2 -8
View File
@@ -198,14 +198,8 @@ put a modified value into etcd key /vitastor/config/global.
- Type: string
- Default: none
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
Note that enabled data checksums either increase memory usage or reduce
performance. Check details in [csum_block_size](#csum_block_size) description.
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
enable data checksums.
## csum_block_size
+2 -6
View File
@@ -209,12 +209,8 @@ journal_block_size и meta_block_size. Однако на данный момен
- Тип: строка
- Значение по умолчанию: none
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
чтобы включить контрольные суммы данных.
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
Следует понимать, что контрольные суммы в зависимости от размера блока их
расчёта либо увеличивают потребление памяти, либо снижают производительность.
-3
View File
@@ -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:
```
-3
View File
@@ -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`:
-306
View File
@@ -1,306 +0,0 @@
[Documentation](../../README.md#documentation) → [Configuration](../config.en.md) → Security Parameters
-----
[Читать на русском](security.ru.md)
# Security Parameters
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
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)
- [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)
- [vault_client_key](#vault_client_key)
- [vault_ca](#vault_ca)
- [vault_timeout_ms](#vault_timeout_ms)
- [vault_error_timeout_sec](#vault_error_timeout_sec)
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
## 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.
## 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).
## etcd_client_key
- Type: string
Private key for etcd_client_cert.
## 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).
## osd_etcd_client_key
- Type: string
Private key for osd_etcd_client_cert.
## 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.
## 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.
## vault_url
- Type: string
Vault base URL.
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
Encryption is performed by the client, OSDs don't have access to decrypted data.
Encryption keys may be stored in etcd or, for the increased security level, in an external
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
instance.
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
In that case, only key IDs are stored in etcd.
## vault_secret_api_path
- Type: string
- Default: /v1/secret/
Vault v1 secret API mount path to use.
## vault_client_cert
- Type: string
Client TLS certificate to use for Vault connections if you don't want to use the common Vitastor
client certificate [cert](#cert) which is also used for Vault connections by default.
## vault_client_key
- Type: string
Private key for the vault_client_cert certificate.
## vault_ca
- Type: string
Trusted TLS CA to verify Vault server certificate. May be path to a file,
directory or just a PEM string with certificate.
## vault_timeout_ms
- Type: integer
- Default: 5000
Timeout for Vault requests in milliseconds.
## vault_error_timeout_sec
- Type: integer
- Default: 60
Time (in seconds) to wait before retrying after receiving an error from Vault.
## vault_refresh_leeway_sec
- Type: integer
- Default: 60
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
in case of system clock drift.
-312
View File
@@ -1,312 +0,0 @@
[Документация](../../README-ru.md#документация) → [Конфигурация](../config.ru.md) → Параметры безопасности
-----
[Read in English](security.en.md)
# Параметры безопасности
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
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)
- [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)
- [vault_client_key](#vault_client_key)
- [vault_ca](#vault_ca)
- [vault_timeout_ms](#vault_timeout_ms)
- [vault_error_timeout_sec](#vault_error_timeout_sec)
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
## use_perms
- Тип: булево (да/нет)
- Значение по умолчанию: false
Включает клиентские привилегии в кластере Vitastor, в том числе во встроенном в мониторе Antietcd.
Требует настроенного шифрования протокола. Также обратите внимание, что отдельно установленный Antietcd
требует отдельной настройки привилегий (подробности смотрите в [документации безопасности](../intro/security.ru.md)).
## cert
- Тип: строка
Клиентский сертификат текущего пользователя Vitastor. Требуется для шифрования протокола Vitastor.
Должен быть подписан [client_ca](#client_ca). Также по умолчанию используется как клиентский
сертификат для подключения к etcd/Antietcd и Vault.
## pkey
- Тип: строка
Закрытый ключ текущего пользователя Vitastor.
## 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.
## osd_etcd_client_cert
- Тип: строка
Клиентский TLS сертификат для подключений от Vitastor OSD к etcd/Antietcd, если вы не хотите
использовать общий сертификат OSD [osd_cert](#osd_cert).
## osd_etcd_client_key
- Тип: строка
Закрытый ключ для сертификата osd_etcd_client_cert.
## mon_etcd_client_cert
- Тип: строка
Клиентский TLS сертификат для подключений от мониторов Vitastor к etcd/Antietcd - требуется, если
вы не используете встроенный в монитор Antietcd. Если вы используете его, то монитор и так имеет
прямой доступ к данным Antietcd и не требует никаких соединений.
## 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, учитываемое
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
## vault_url
- Тип: строка
Базовый адрес Vault.
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
В этом случае, только ID ключей хранятся в etcd.
## vault_secret_api_path
- Тип: строка
- Значение по умолчанию: /v1/secret/
Путь к API секретов v1 для использования клиентами.
## vault_client_cert
- Тип: строка
Клиентский TLS сертификат для подключений к Vault на тот случай, если вы не хотите использовать
общий сертификат клиента Vitastor [cert](#cert), используемый для подключений к Vault по умолчанию.
## vault_client_key
- Тип: строка
Закрытый ключ для сертификата vault_client_cert.
## vault_ca
- Тип: строка
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
Может быть путём к файлу, директории или просто строкой с сертификатом в
формате PEM.
## vault_timeout_ms
- Тип: целое число
- Значение по умолчанию: 5000
Максимально время выполнения Vault-запросов в миллисекундах.
## vault_error_timeout_sec
- Тип: целое число
- Значение по умолчанию: 60
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
## vault_refresh_leeway_sec
- Тип: целое число
- Значение по умолчанию: 60
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
-2
View File
@@ -44,8 +44,6 @@
{{../../config/monitor.en.md|indent=2}}
{{../../config/security.en.md|indent=2}}
{{../../config/pool.en.md|indent=2}}
{{../../config/inode.en.md|indent=2}}
-2
View File
@@ -44,8 +44,6 @@
{{../../config/monitor.ru.md|indent=2}}
{{../../config/security.ru.md|indent=2}}
{{../../config/pool.ru.md|indent=2}}
{{../../config/inode.ru.md|indent=2}}
+4 -14
View File
@@ -233,21 +233,11 @@
type: string
default: none
info: |
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
Note that enabled data checksums either increase memory usage or reduce
performance. Check details in [csum_block_size](#csum_block_size) description.
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
enable data checksums.
info_ru: |
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
чтобы включить контрольные суммы данных.
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
Следует понимать, что контрольные суммы в зависимости от размера блока их
расчёта либо увеличивают потребление памяти, либо снижают производительность.
+1 -1
View File
@@ -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)
{
-6
View File
@@ -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`:
-5
View File
@@ -1,5 +0,0 @@
{
"dependencies": {
"yaml": "^2.8.2"
}
}
-9
View File
@@ -1,9 +0,0 @@
# Security Parameters
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
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-----".
-11
View File
@@ -1,11 +0,0 @@
# Параметры безопасности
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
OSD, мониторами и клиентами.
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
поддерживает онлайн-изменение.
Все параметры сертификатов и закрытых ключей могут быть путём к файлу или просто
строкой с сертификатом в формате PEM. В последнем случае строка должна начинаться с
"-----BEGIN CERTIFICATE-----" или "-----BEGIN PRIVATE KEY-----".
-276
View File
@@ -1,276 +0,0 @@
- 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).
info_ru: |
Клиентский TLS сертификат для подключений от клиентов Vitastor к etcd/Antietcd, если вы не хотите
использовать общий клиентский сертификат [cert](#cert).
- name: etcd_client_key
type: string
info: Private key for etcd_client_cert.
info_ru: Закрытый ключ для сертификата etcd_client_cert.
- 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).
info_ru: |
Клиентский TLS сертификат для подключений от Vitastor OSD к etcd/Antietcd, если вы не хотите
использовать общий сертификат OSD [osd_cert](#osd_cert).
- name: osd_etcd_client_key
type: string
info: Private key for osd_etcd_client_cert.
info_ru: Закрытый ключ для сертификата osd_etcd_client_cert.
- 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 и не требует никаких соединений.
- 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, учитываемое
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
- name: vault_url
type: string
info: |
Vault base URL.
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
Encryption is performed by the client, OSDs don't have access to decrypted data.
Encryption keys may be stored in etcd or, for the increased security level, in an external
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
instance.
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
In that case, only key IDs are stored in etcd.
info_ru: |
Базовый адрес Vault.
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
В этом случае, только ID ключей хранятся в etcd.
- name: vault_secret_api_path
type: string
default: /v1/secret/
info: Vault v1 secret API mount path to use.
info_ru: Путь к API секретов v1 для использования клиентами.
- name: vault_client_cert
type: string
info: |
Client TLS certificate to use for Vault connections if you don't want to use the common Vitastor
client certificate [cert](#cert) which is also used for Vault connections by default.
info_ru: |
Клиентский TLS сертификат для подключений к Vault на тот случай, если вы не хотите использовать
общий сертификат клиента Vitastor [cert](#cert), используемый для подключений к Vault по умолчанию.
- name: vault_client_key
type: string
info: Private key for the vault_client_cert certificate.
info_ru: Закрытый ключ для сертификата vault_client_cert.
- name: vault_ca
type: string
info: |
Trusted TLS CA to verify Vault server certificate. May be path to a file,
directory or just a PEM string with certificate.
info_ru: |
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
Может быть путём к файлу, директории или просто строкой с сертификатом в
формате PEM.
- name: vault_timeout_ms
type: int
default: 5000
info: Timeout for Vault requests in milliseconds.
info_ru: Максимально время выполнения Vault-запросов в миллисекундах.
- name: vault_error_timeout_sec
type: int
default: 60
info: |
Time (in seconds) to wait before retrying after receiving an error from Vault.
info_ru: |
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
- name: vault_refresh_leeway_sec
type: int
default: 60
info: |
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
in case of system clock drift.
info_ru: |
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
+2 -2
View File
@@ -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.12`
2. Install scripts to the host system: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
3. Reload udev rules: \
`udevadm control --reload-rules`
4. Enable the vitastor-host service: \
+2 -2
View File
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
Инструкция по установке максимально простая.
1. Скачайте Docker-образ желаемой версии: \
`docker pull vitalif/vitastor:v3.0.15`
`docker pull vitalif/vitastor:v3.0.12`
2. Установите скрипты в хост-систему командой: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
3. Перезагрузите правила udev: \
`udevadm control --reload-rules`
4. Включите сервис vitastor-host: \
+1 -1
View File
@@ -15,7 +15,7 @@
- gcc and g++ 8 or newer, clang 10 or newer, or other compiler with C++11 plus
designated initializers support from C++20
- CMake
- jerasure, c-ares headers and libraries
- jerasure headers and libraries
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
## Basic instructions
+1 -1
View File
@@ -15,7 +15,7 @@
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
назначенных инициализаторов (designated initializers) из C++20
- CMake
- Заголовки и библиотеки jerasure, c-ares
- Заголовки и библиотеки jerasure
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
## Базовая инструкция
-2
View File
@@ -41,8 +41,6 @@
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
- [S3](../installation/s3.en.md)
- [TLS support for etcd connections](../config/security.en.md)
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
## Plugins and tools
-2
View File
@@ -43,8 +43,6 @@
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
- [S3](../installation/s3.ru.md)
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
## Драйверы и инструменты
+2 -6
View File
@@ -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`
+9 -7
View File
@@ -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`,
-657
View File
@@ -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
```
-662
View File
@@ -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
```
+7 -21
View File
@@ -125,31 +125,18 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
## create
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
Create an image. Options:
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
If parent is not a snapshot, it must be a read-only image.
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
Pool must be specified if there is more than one pool.
```
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
```
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
Options:
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
By default, the image retains its old encryption key when taking a snapshot.
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
@@ -164,7 +151,6 @@ You should resize file system in the image, if present, before shrinking it.
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
## dd
+8 -22
View File
@@ -127,32 +127,19 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
## create
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
Создать образ. Опции:
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
```
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
```
Создать снимок образа `<image>` (можно использовать любую форму команды).
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
Опции:
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
клиентов, если пишущий клиент максимум 1.
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
@@ -169,7 +156,6 @@ vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
## dd
+8 -49
View File
@@ -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');
@@ -19,7 +18,7 @@ class AntiEtcdAdapter
cluster = cluster ? (''+(cluster||'')).split(/,+/) : [];
cluster = Object.keys(cluster.reduce((a, url) =>
{
a[url.toLowerCase().replace(/^(https?:\/\/)?(.*?)(\/.*)?$/, (m, m1, m2) => (m1||'http://')+m2)] = true;
a[url.toLowerCase().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '')] = true;
return a;
}, {}));
const cfg_port = config.antietcd_port;
@@ -27,18 +26,7 @@ class AntiEtcdAdapter
is_local['0.0.0.0'] = true;
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(':', 2)).filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
if (selected.length > 1)
{
console.error('More than 1 etcd_address matches local IPs, please specify port');
@@ -47,45 +35,16 @@ class AntiEtcdAdapter
else if (selected.length == 1)
{
const antietcd_config = {
ip: selected[0].ip,
port: selected[0].port,
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'),
ip: selected[0][0],
port: selected[0][1],
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
node_id: cluster[selected[0].idx].replace(/^(https?:\/\/)/, ''), // same as in <cluster> below
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
cluster_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)
{
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;
}
for (const key in config)
{
if (key.substr(0, 9) === 'antietcd_')
@@ -210,7 +169,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);
if (res.error)
{
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
+6 -27
View File
@@ -1,9 +1,7 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
const fs = require('fs');
const http = require('http');
const https = require('https');
const WebSocket = require('ws');
const { b64, local_ips } = require('./utils.js');
@@ -17,30 +15,11 @@ class EtcdAdapter
this.ws = null;
this.ws_alive = false;
this.ws_keepalive_timer = null;
this.opts = {};
}
parse_config(config)
{
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
if (config.mon_etcd_client_cert || config.etcd_client_cert)
{
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
if (this.opts.cert.substr(0, 5) != '-----')
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
}
if (config.mon_etcd_client_key || config.etcd_client_key)
{
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
if (this.opts.key.substr(0, 5) != '-----')
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
}
if (config.etcd_ca)
{
this.opts.ca = config.etcd_ca;
if (this.opts.ca.substr(0, 5) != '-----')
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
}
}
parse_etcd_addresses(addrs)
@@ -60,7 +39,7 @@ class EtcdAdapter
for (let url of addrs)
{
let scheme = 'http';
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
const slash = url.indexOf('/');
const colon = url.indexOf(':');
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
@@ -151,7 +130,7 @@ class EtcdAdapter
}
ok(false);
}, this.mon.config.etcd_mon_timeout);
this.ws = new WebSocket(base+'/watch', this.opts);
this.ws = new WebSocket(base+'/watch');
this.ws_used_url = cur_addr;
const fail = () =>
{
@@ -293,7 +272,7 @@ class EtcdAdapter
{
throw new Error(MON_STOPPED);
}
const res = await POST(base+path, body, timeout, this.opts);
const res = await POST(base+path, body, timeout);
if (this.mon.stopped)
{
throw new Error(MON_STOPPED);
@@ -319,7 +298,7 @@ class EtcdAdapter
}
}
function POST(url, body, timeout, opts)
function POST(url, body, timeout)
{
return new Promise(ok =>
{
@@ -331,10 +310,10 @@ function POST(url, body, timeout, opts)
req = null;
ok({ error: 'timeout' });
}, timeout) : null;
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
let req = http.request(url, { method: 'POST', headers: {
'Content-Type': 'application/json',
'Content-Length': body_text.length,
}, ...(opts||{}) }, (res) =>
} }, (res) =>
{
if (!req)
{
+1 -22
View File
@@ -16,7 +16,6 @@ const etcd_allow = new RegExp('^'+[
'config/pools',
'config/osd/[1-9]\\d*',
'config/pgs', // old name
'config/user/.*',
'pg/config',
'config/inode/[1-9]\\d*/[1-9]\\d*',
'osd/state/[1-9]\\d*',
@@ -46,14 +45,7 @@ const etcd_tree = {
config_path: "/etc/vitastor/vitastor.conf",
etcd_prefix: "/vitastor",
// etcd connection - configurable online
etcd_address: "http://10.0.115.10:2379/v3",
etcd_client_cert: "",
etcd_client_key: "",
osd_etcd_client_cert: "",
osd_etcd_client_key: "",
mon_etcd_client_cert: "",
mon_etcd_client_key: "",
etcd_ca: "",
etcd_address: "10.0.115.10:2379/v3",
// mon
etcd_mon_ttl: 5, // min: 1
etcd_mon_timeout: 1000, // ms. min: 0
@@ -209,8 +201,6 @@ const etcd_tree = {
primary_affinity_tags?: 'nvme' | [ 'nvme', ... ],
// scrub interval
scrub_interval?: '30d',
// users allowed to create images in this pool
creator_group?: '',
},
...
}, */
@@ -227,21 +217,10 @@ const etcd_tree = {
parent_id?: <inode_t>,
readonly?: boolean,
deleted?: boolean,
enc_key?: string,
owner?: string,
owner_group?: string,
reader_group?: string,
}
}
}, */
inode: {},
/* user: {
<username>: {
type: 'osd'|'mon'|'admin'|'client',
groups: string[],
},
}, */
user: {},
},
osd: {
state: {
+2 -3
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor-mon",
"version": "3.0.15",
"version": "3.0.12",
"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"
},
+46 -379
View File
@@ -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");
}
-539
View File
@@ -1,539 +0,0 @@
// AntiEtcd authentication filter for Vitastor
// (c) Vitaliy Filippov, 2026
// License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
// Permissions are based on:
// 1. Users.
// Stored in /vitastor/config/user/<username>.
// Has 2 properties:
// - type, one of: admin, client.
// admin has full access to all images and also to cluster config.
// client has r/w access to owned images and r/o access to images with reader_group.
// - groups, a list of group names the user is included in.
// 2. Images.
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
// - owner (user name)
// - owner_group (group name)
// - reader_group
// 3. Certificates.
// - osd, mon use their own trusted certificates.
const { X509Certificate } = require('node:crypto');
const static_perms = {
invalid: {
keys: {},
prefixes: {},
},
osd: {
keys: { '/pg/config': false },
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
},
mon: {
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
prefixes: {
'/config/': false, '/osd/': false, '/mon/': true, '/pg/history/': true,
'/pgstats/': false, '/inode/stats/': true, '/pool/stats/': true,
},
},
admin: {
keys: { '/stats': false },
prefixes: {
'/config/': true, '/osd/': true, '/index/': true, '/pg/history/': true,
'/mon/': false, '/pg/': false, '/pgstats/': false, '/inode/stats/': false, '/pool/stats/': false,
},
},
client: {
keys: { '/config/global': false, '/config/node_placement': false, '/config/pools': false, '/pg/config': false },
prefixes: { '/osd/state/': false, '/pg/state/': false, '/index/maxid/': false },
},
};
const api_perms = {
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
admin: { maintenance_status: true },
client: { maintenance_status: true },
};
class VitastorAuthFilter
{
constructor(antietcd)
{
this.cfg = antietcd.cfg;
this.antietcd = antietcd;
this.prefix = this.cfg.vitastor_prefix || '/vitastor';
this.prefix_parts = this.prefix.split('/');
}
async init()
{
if (!this.cfg.cert || !this.cfg.key || !this.cfg.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;
path = path instanceof Array ? path : path.split('/');
for (const p of path)
{
if (!cur.children)
{
return null;
}
cur = cur.children[p];
if (!cur)
{
return null;
}
}
if (decode)
{
return this._decode(path, cur.value);
}
return cur;
}
_decode(path, cur)
{
if (!cur)
{
return null;
}
if (cur)
{
try
{
cur = JSON.parse(cur);
}
catch (e)
{
console.warn('Invalid JSON in '+(path instanceof Array ? path.join('/') : path)+': '+e);
}
}
return cur;
}
// userInfo: { name: string, type: string, perms: static_perms[type], groups: { [string]: true } }
_check_compare(check, userInfo, checked)
{
let key = String(check.key);
if (key.substr(0, this.prefix.length) !== this.prefix)
{
return false;
}
key = key.substr(this.prefix.length);
if (key in userInfo.perms.keys)
{
return true;
}
for (const pfx in userInfo.perms.prefixes)
{
if (key.substr(0, pfx.length) == pfx)
{
return true;
}
}
if (userInfo.type == 'client')
{
// Image permissions
if (key.substr(0, 14) == '/config/inode/')
{
// Allowed to check that a key does not exist
if (check.target == 'VERSION' && check.version == 0)
{
checked['M'+key] = true;
return true;
}
else if (check.target == 'MOD')
{
const data = this._get(check.key);
if (!data || data.mod_revision != check.mod_revision)
{
// Break check to trigger CAS failure
check.mod_revision = '18446744073709551615'; // UINT64_MAX
return true;
}
const inode = this._decode(check.key, data.value);
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
checked['M'+key] = true;
return true;
}
}
return false;
}
if (key.substr(0, 13) == '/index/image/')
{
// Allowed to check that a key does not exist
if (check.target == 'VERSION' && check.version == 0)
{
checked['M'+key] = true;
return true;
}
else if (check.target == 'MOD')
{
let data = this._get(check.key);
if (!data || data.mod_revision != check.mod_revision)
{
// Break check to trigger CAS failure
check.mod_revision = '18446744073709551615'; // UINT64_MAX
return true;
}
data = this._decode(check.key, data.value);
if (data)
{
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
checked['M'+key] = true;
return true;
}
}
}
return false;
}
if (key.substr(0, 13) == '/index/maxid/')
{
const pool_id = key.substr(13);
const pool_cfg = this._get([ ...this.prefix_parts, 'config', 'pools' ], true);
if (!pool_cfg || !pool_cfg[pool_id] || !pool_cfg[pool_id].creator_group || !userInfo.groups[pool_cfg[pool_id].creator_group])
{
return false;
}
if (check.target == 'VERSION' && check.version == 0)
{
checked['I'+parseInt(key.substr(13))+'_0'] = true;
return true;
}
else if (check.target == 'MOD')
{
const data = this._get(check.key);
if (!data || data.mod_revision != check.mod_revision)
{
// Break check to trigger CAS failure
check.mod_revision = '18446744073709551615'; // UINT64_MAX
return true;
}
checked['I'+parseInt(key.substr(13))+'_'+data.value] = true;
return true;
}
return false;
}
}
return false;
}
_check_read(kv, userInfo)
{
let key = String(kv.key);
if (key.substr(0, this.prefix.length) !== this.prefix)
{
return false;
}
key = key.substr(this.prefix.length);
if (key in userInfo.perms.keys)
{
return true;
}
for (const pfx in userInfo.perms.prefixes)
{
if (key.substr(0, pfx.length) == pfx)
{
return true;
}
}
if (userInfo.type == 'client')
{
// Image permissions
if (key.substr(0, 14) == '/config/inode/')
{
const inode = this._decode(kv.key, kv.value);
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
return true;
}
return false;
}
if (key.substr(0, 13) == '/index/image/')
{
const data = this._decode(kv.key, kv.value);
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
return true;
}
return false;
}
if (key.substr(0, 13) == '/inode/stats/')
{
const [ pool_id, id ] = key.substr(13).split('/');
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', pool_id, id ], true);
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
return true;
}
return false;
}
}
return false;
}
_check_write(put, userInfo, checked)
{
let key = String(put.key);
if (key.substr(0, this.prefix.length) !== this.prefix)
{
return false;
}
key = key.substr(this.prefix.length);
if (userInfo.perms.keys[key])
{
return true;
}
for (const pfx in userInfo.perms.prefixes)
{
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
{
return true;
}
}
if (checked && userInfo.type == 'client')
{
if (key.substr(0, 13) == '/index/maxid/' &&
checked['I'+parseInt(key.substr(13))+'_'+(put.value-1)])
{
// Allowed to increment maxid
return true;
}
if (checked['M'+key])
{
// Allowed to modify known images with CAS checks
return true;
}
}
return false;
}
_check_req(req, userInfo, checked)
{
let r;
if ((r = (req.request_range || req.requestRange)))
{
// All range queries are allowed, but responses are filtered - it's simpler
}
else if ((r = (req.request_put || req.requestPut)))
{
if (!this._check_write(r, userInfo, checked))
return false;
}
else if ((r = (req.request_delete_range || req.requestDeleteRange)))
{
if (!r.range_end || r.range_end === r.key)
{
if (!this._check_write({ key: r.key }, userInfo))
return false;
}
else
{
// All keys in range must satisfy prefix
r.range_end = String(r.range_end);
if (r.key.length != r.range_end.length ||
r.key[r.key.length-1] != '/' ||
r.range_end[r.range_end.length-1] != '0')
{
return false;
}
let key = r.key.substr(this.prefix.length);
let found = false;
for (const pfx in userInfo.perms.prefixes)
{
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
{
found = true;
break;
}
}
if (!found)
return false;
}
}
return true;
}
_get_user(context)
{
if (context.user_type === 'osd' || context.user_type === 'mon')
{
return {
name: context.user_type,
type: context.user_type,
perms: static_perms[context.user_type],
};
}
if (!context.username)
{
return {};
}
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
if (!userInfo)
{
userInfo = { type: 'client' };
}
else if (userInfo.type !== 'client' && userInfo.type !== 'admin')
{
userInfo.type = 'client';
}
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
userInfo.name = context.username;
if (userInfo.groups instanceof Array)
{
userInfo.groups = userInfo.groups.reduce((a, c) => { a[c] = true; return a; }, {});
}
else
{
userInfo.groups = {};
}
return userInfo;
}
filter_api(context, api/*, data*/)
{
let type = 'client';
if (context.user_type === 'osd' || context.user_type === 'mon')
{
type = context.user_type;
}
else if (context.username)
{
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
if (userInfo && userInfo.type === 'admin')
{
type = 'admin';
}
}
return api_perms[type] && api_perms[type][api];
}
filter_txn(context, txn)
{
const userInfo = this._get_user(context);
if (!userInfo)
{
return null;
}
const checked = {};
if (txn.compare)
{
for (const check of txn.compare)
{
if (!this._check_compare(check, userInfo, checked))
return null;
}
}
// Special transactions:
// 1. create image: create config/inode and index/image, increment index/maxid/<pool> (with CAS)
// 2. create snapshot: same as create image but also rename previous to @snap
if (txn.success)
{
for (const req of txn.success)
{
if (!this._check_req(req, userInfo, checked))
return null;
}
}
if (txn.failure)
{
for (const req of txn.failure)
{
if (!this._check_req(req, userInfo, null))
return null;
}
}
return txn;
}
filter_txn_response(context, txn, res)
{
if (!res.responses)
{
return;
}
const userInfo = this._get_user(context);
if (!userInfo)
{
for (const resp of res.responses)
{
if (resp.response_range && resp.response_range.kvs)
{
resp.response_range.kvs = [];
}
}
return;
}
for (const resp of res.responses)
{
if (resp.response_range && resp.response_range.kvs)
{
resp.response_range.kvs = resp.response_range.kvs.filter(kv => this._check_read(kv, userInfo));
}
}
}
filter_watch_message(context, msg)
{
if (!msg.result || !msg.result.events)
{
return;
}
const userInfo = this._get_user(context);
if (!userInfo)
{
msg.result.events = [];
return;
}
msg.result.events = msg.result.events.filter(ev => this._check_read(ev.kv, userInfo));
}
}
module.exports = VitastorAuthFilter;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor",
"version": "3.0.15",
"version": "3.0.12",
"description": "Low-level native bindings to Vitastor client library",
"main": "index.js",
"keywords": [
+10 -45
View File
@@ -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;
}
+1 -1
View File
@@ -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.12'
LOG = logging.getLogger(__name__)
+1 -1
View File
@@ -11,7 +11,7 @@ WORKDIR /root
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
RUN dnf -y install epel-release dnf-plugins-core
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
+2 -3
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.15
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.15.el10.tar.gz
Source0: vitastor-3.0.12.el10.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
@@ -16,7 +16,6 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+1 -1
View File
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
+2 -3
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.15
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.15.el7.tar.gz
Source0: vitastor-3.0.12.el7.tar.gz
BuildRequires: gperftools-devel
BuildRequires: devtoolset-9-gcc-c++
@@ -17,7 +17,6 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake3
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+1 -1
View File
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
+2 -3
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.15
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.15.el8.tar.gz
Source0: vitastor-3.0.12.el8.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-toolset-9-gcc-c++
@@ -16,7 +16,6 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+1 -1
View File
@@ -10,7 +10,7 @@ WORKDIR /root
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
RUN dnf -y install epel-release dnf-plugins-core
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
+2 -3
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.15
Version: 3.0.12
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.15.el9.tar.gz
Source0: vitastor-3.0.12.el9.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
@@ -16,7 +16,6 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+1 -11
View File
@@ -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.12")
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
add_link_options(-fno-omit-frame-pointer)
if (${WITH_ASAN})
@@ -69,21 +69,11 @@ pkg_check_modules(ISAL libisal)
if (ISAL_LIBRARIES)
add_definitions(-DWITH_ISAL)
endif (ISAL_LIBRARIES)
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
if (ISAL_CRYPTO_LIBRARIES)
add_definitions(-DWITH_ISAL_CRYPTO)
endif (ISAL_CRYPTO_LIBRARIES)
pkg_check_modules(RDMACM librdmacm)
if (RDMACM_LIBRARIES)
add_definitions(-DWITH_RDMACM)
endif (RDMACM_LIBRARIES)
find_package(OpenSSL REQUIRED)
add_definitions(-DWITH_OPENSSL)
pkg_check_modules(CARES REQUIRED libcares)
include_directories(${CARES_INCLUDE_DIRS})
if (${WITH_SYSTEM_LIBURING})
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
include_directories(${LIBURING_INCLUDE_DIRS})
+11 -19
View File
@@ -83,17 +83,13 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
{
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
}
else if (config["data_csum_type"] == "xxh3_32")
{
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
}
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
{
data_csum_type = BLOCKSTORE_CSUM_NONE;
}
else
{
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
}
csum_block_size = parse_size(config["csum_block_size"]);
discard_on_start = config.find("discard_on_start") != config.end() &&
@@ -521,7 +517,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 +528,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 +541,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;
}
+1 -2
View File
@@ -16,7 +16,6 @@
#define BLOCKSTORE_CSUM_NONE 0
// Lower byte of checksum type is its length
#define BLOCKSTORE_CSUM_CRC32C 0x104
#define BLOCKSTORE_CSUM_XXH3_32 0x204
#define MOCK_DATA_FD 1000
#define MOCK_META_FD 1001
@@ -84,7 +83,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)
{
+21 -32
View File
@@ -289,41 +289,30 @@ resume_1:
{
init_fsync_data();
}
if (compact_info.do_delete)
if (bs->log_level > 10)
{
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;
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);
}
else
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
if (!bitmap_copied)
{
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);
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);
overwrite_start = overwrite_end = 0;
if (read_vec.size() > 0)
{
@@ -636,7 +625,7 @@ int journal_flusher_co::check_and_punch_checksums()
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;
}
+53 -162
View File
@@ -12,7 +12,6 @@
#include "blockstore_heap.h"
#include "../util/allocator.h"
#include "../util/crc32c.h"
#include "../util/xxh_x86dispatch.h"
#include "../util/malloc_or_die.h"
#define BS_HEAP_FREE_MVCC 1
@@ -23,7 +22,7 @@
#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
@@ -66,19 +65,19 @@ uint32_t blockstore_heap_t::get_simple_entry_size()
uint32_t blockstore_heap_t::get_big_entry_size()
{
return sizeof(heap_big_write_t) + dsk->clean_entry_bitmap_size*2 +
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
(!dsk->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
}
uint32_t blockstore_heap_t::get_big_intent_entry_size()
{
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
}
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
{
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
}
@@ -93,7 +92,7 @@ uint32_t blockstore_heap_t::get_csum_size(heap_entry_t *wr)
uint32_t blockstore_heap_t::get_csum_size(uint32_t entry_type, uint32_t offset, uint32_t len)
{
if (!dsk->csum_block_size)
if (!dsk->data_csum_type)
{
return 0;
}
@@ -123,8 +122,6 @@ 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();
@@ -218,24 +215,15 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
big().block_num = location / heap->dsk->data_block_size;
}
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
uint32_t heap_entry_t::calc_crc32c()
{
auto old_checksum = checksum;
checksum = 0;
uint32_t res = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
res = (uint32_t)XXH3_64bits(this, size);
else
res = ::crc32c(0, (uint8_t*)this, size);
checksum = old_checksum;
auto old_crc32c = crc32c;
crc32c = 0;
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
crc32c = old_crc32c;
return res;
}
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
{
return calc_checksum(heap->dsk);
}
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
{
uint64_t pg_num = 0;
@@ -376,11 +364,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)
@@ -390,12 +381,12 @@ corrupted_object:
goto corrupted_object;
}
// Verify crc
uint32_t expected_checksum = wr->calc_checksum(this);
if (wr->checksum != expected_checksum)
uint32_t expected_crc32c = wr->calc_crc32c();
if (wr->crc32c != expected_crc32c)
{
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). ",
wr->inode, wr->stripe, wr->version, wr->lsn,
block_num, block_offset, expected_checksum, wr->checksum);
block_num, block_offset, expected_crc32c, wr->crc32c);
goto corrupted_object;
}
// Verify offset & len
@@ -569,7 +560,7 @@ void blockstore_heap_t::finish_load()
size_t s = 0, e, n = postponed_items.size();
for (e = 1; e <= n; e++)
{
if (e >= n || postponed_items[e]->entry.inode != postponed_items[s]->entry.inode ||
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);
@@ -692,14 +683,10 @@ int blockstore_heap_t::mark_used_blocks()
}
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 +696,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();
@@ -749,6 +731,7 @@ void blockstore_heap_t::init_erase_bad_entry(heap_list_item_t *li)
inf.garbage_space -= (li->entry.is_garbage() ? li->entry.size : 0);
});
recheck_modified_blocks.insert(li->block_num);
unlink_list_item(li);
}
bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li)
@@ -803,8 +786,6 @@ bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_
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;
}
}
@@ -818,8 +799,6 @@ bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_
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;
@@ -828,8 +807,6 @@ bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_
{
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;
}
}
@@ -900,7 +877,6 @@ void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *ba
auto prev = li->prev;
assert(li->entry.type() == bad_wr->type());
init_erase_bad_entry(li);
unlink_list_item(li);
li = prev;
}
}
@@ -1093,11 +1069,7 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
len = wr->big_intent().len;
else
assert(0);
uint32_t real_csum = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
real_csum = (uint32_t)XXH3_64bits(data, len);
else
real_csum = crc32c(0, data, len);
uint32_t real_csum = crc32c(0, data, len);
if (set)
{
*wr_csum = real_csum;
@@ -1147,26 +1119,11 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
return prev_crc;
}
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
{
uint32_t cur_len = 0;
while (size > 0)
{
uint8_t *data = next(pos, cur_len);
assert(data);
cur_len = (cur_len < size ? cur_len : size);
XXH3_64bits_update(xxh3_state, data, cur_len);
pos += cur_len;
size -= cur_len;
}
}
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
{
bool res = true;
XXH3_state_t* xxh3_state = NULL;
uint32_t pos = start;
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
uint32_t block_crc = 0;
@@ -1182,90 +1139,43 @@ 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 (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
uint32_t zeropad = pos-prev;
while (zeropad > 0)
{
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
zeropad -= zerolen;
}
}
else
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
}
if (pos > prev && prev > 0 && pos < block_end)
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
prev = pos;
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
pos += dsk->bitmap_granularity;
if (pos > prev)
{
isset = true;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
xxh3_iter(xxh3_state, next, prev, pos-prev);
}
else
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
}
prev = pos;
}
}
else
{
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
}
else
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
pos = (end > block_end ? block_end : end);
isset = true;
}
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
{
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
XXH3_64bits_reset(xxh3_state);
}
if (set)
{
*block_csums = block_crc;
}
else if (isset && block_crc != *block_csums)
{
res = false;
if (bad_block_cb)
{
bad_block_cb(blk_start, *block_csums, block_crc);
res = false;
}
else
break;
return false;
}
block_end += dsk->csum_block_size;
block_crc = 0;
block_csums++;
}
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
{
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
XXH3_freeState(xxh3_state);
xxh3_state = NULL;
}
return res;
}
@@ -1627,12 +1537,12 @@ 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);
li->block_num = block_num;
new_wr->size = wr_size;
new_wr->checksum = new_wr->calc_checksum(this);
new_wr->crc32c = new_wr->calc_crc32c();
return 0;
}
@@ -1661,22 +1571,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;
});
@@ -1704,7 +1602,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->csum_block_size)
if (dsk->data_csum_type)
{
memset(wr->get_checksums(this), 0, get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true, offset, len);
@@ -1733,7 +1631,7 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->csum_block_size)
if (dsk->data_csum_type)
memset(wr->get_checksums(this), 0, get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true);
*obj_ptr = wr;
@@ -1771,7 +1669,7 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->csum_block_size)
if (dsk->data_csum_type)
{
if (checksums)
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
@@ -1818,7 +1716,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
new_wr->set_big_location(this, compact_location);
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
if (dsk->csum_block_size && new_csums)
if (dsk->data_csum_type && new_csums)
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
});
}
@@ -1837,7 +1735,7 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
*modified_block = block_num;
memcpy(wr->get_int_bitmap(this), new_bitmap, dsk->clean_entry_bitmap_size);
memcpy(wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
wr->checksum = wr->calc_checksum(dsk);
wr->crc32c = wr->calc_crc32c();
return 0;
}
@@ -2140,10 +2038,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 +2090,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 +2268,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 +2279,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 +2316,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 +2358,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 +2446,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 +2513,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);
+5 -7
View File
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
{
uint16_t size;
uint16_t entry_type;
uint32_t checksum;
uint32_t crc32c;
uint64_t lsn;
uint64_t inode;
uint64_t stripe;
@@ -69,8 +69,7 @@ struct __attribute__((__packed__)) heap_entry_t
uint32_t *get_checksum(blockstore_heap_t *heap);
uint64_t big_location(blockstore_heap_t *heap);
void set_big_location(blockstore_heap_t *heap, uint64_t location);
uint32_t calc_checksum(blockstore_heap_t *heap);
uint32_t calc_checksum(blockstore_disk_t *dsk);
uint32_t calc_crc32c();
};
struct __attribute__((__packed__)) heap_small_write_t
@@ -81,7 +80,7 @@ struct __attribute__((__packed__)) heap_small_write_t
uint32_t offset;
uint32_t len;
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
};
struct __attribute__((__packed__)) heap_big_write_t
@@ -99,7 +98,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
uint32_t offset;
uint32_t len;
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
};
struct __attribute__((__packed__)) heap_list_item_t
@@ -220,7 +219,6 @@ 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::set<uint32_t> recheck_modified_blocks;
std::deque<heap_entry_t*> recheck_queue;
std::map<heap_entry_t*, heap_recheck_state_t> recheck_states;
@@ -363,7 +361,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
+2 -5
View File
@@ -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;
+52 -61
View File
@@ -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
{
@@ -144,7 +163,7 @@ resume_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 +175,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 +194,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 +211,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 +232,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,7 +246,7 @@ 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);
// asynchronous recheck
@@ -316,14 +329,13 @@ 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"); };
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
submitted++;
bs->ringloop->submit();
resume_5:
@@ -333,27 +345,6 @@ do_fsync:
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(),
+1 -2
View File
@@ -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();
-113
View File
@@ -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;
}
-39
View File
@@ -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;
};
+5 -1
View File
@@ -28,7 +28,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);
@@ -53,6 +52,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();
+5 -12
View File
@@ -29,12 +29,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 +60,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 +71,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();
@@ -115,7 +109,6 @@ int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
PRIV(op)->lsn = 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;
+27 -34
View File
@@ -180,16 +180,13 @@ enospc:
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;
}
@@ -300,6 +297,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 +317,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:
{
+5 -8
View File
@@ -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)
+1 -1
View File
@@ -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();
+22 -53
View File
@@ -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++)
@@ -665,7 +634,6 @@ resume_2:
}
// All done
flusher->active_flushers--;
copy_count = 0; // used by is_mutated()...
wait_state = 0;
goto resume_0;
}
@@ -847,21 +815,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 +952,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 +1088,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 +1118,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 +1349,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 +1377,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)
-2
View File
@@ -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();
+1 -7
View File
@@ -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)
+3 -8
View File
@@ -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
View File
@@ -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
+1 -3
View File
@@ -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();
+54 -145
View File
@@ -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
+2 -2
View File
@@ -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)
+18 -63
View File
@@ -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,48 +11,28 @@ 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 msgr_iothread.cpp ../util/addr_util.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)
# libvitastor_client_int.a
add_library(vitastor_client_int STATIC
cluster_client.cpp
cluster_client_real.cpp
cluster_client_list.cpp
cluster_client_wb.cpp
cluster_client_icache.cpp
)
target_link_libraries(vitastor_client_int
vitastor_net
vitastor_cli
${LIBURING_LIBRARIES}
${IBVERBS_LIBRARIES}
${RDMACM_LIBRARIES}
${OPENSSL_LIBRARIES}
${ISAL_CRYPTO_LIBRARIES}
)
target_compile_options(vitastor_client_int PUBLIC -fPIC)
target_link_libraries(vitastor_common pthread)
target_compile_options(vitastor_common PUBLIC -fPIC)
# libvitastor_client.so
add_library(vitastor_client SHARED
cluster_client.cpp
cluster_client_list.cpp
cluster_client_wb.cpp
vitastor_c.cpp
)
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
target_link_libraries(vitastor_client
vitastor_client_int
vitastor_common
vitastor_cli
${LIBURING_LIBRARIES}
${IBVERBS_LIBRARIES}
${RDMACM_LIBRARIES}
)
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
configure_file(vitastor.pc.in vitastor.pc @ONLY)
@@ -136,15 +95,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 msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp
)
target_link_libraries(test_cluster_client vitastor_common ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
target_link_libraries(test_cluster_client ${LIBURING_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)
+109 -155
View File
@@ -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,31 +51,32 @@ 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->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();
scrap_buffer_size = SCRAP_BUFFER_SIZE;
scrap_buffer = malloc_or_die(scrap_buffer_size);
}
cluster_client_t::~cluster_client_t()
{
vault_destroy();
if (retry_timeout_id >= 0)
{
tfd->clear_timer(retry_timeout_id);
@@ -93,6 +94,7 @@ cluster_client_t::~cluster_client_t()
{
ringloop->unregister_consumer(&consumer);
}
free(scrap_buffer);
delete wb;
wb = NULL;
}
@@ -467,7 +469,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();
@@ -479,11 +481,9 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
self_tree_metrics.clear();
client_hostname = new_hostname;
}
// vault
vault_parse_config();
msgr.parse_config(config, 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)
{
@@ -607,15 +607,12 @@ void cluster_client_t::on_change_pool_config_hook()
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
}
}
inode_cache.clear();
inode_cache_children.clear();
vault_keys.clear();
continue_ops();
}
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 +630,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 +641,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();
}
}
@@ -676,10 +673,6 @@ bool cluster_client_t::flush()
{
if (!ringloop)
{
if (vault_loading)
{
return false;
}
if (wb->writeback_queue.size())
{
wb->start_writebacks(this, 0);
@@ -702,7 +695,7 @@ bool cluster_client_t::flush()
sync_done = true;
};
execute(sync);
while (!sync_done || vault_loading)
while (!sync_done)
{
ringloop->loop();
if (!sync_done)
@@ -943,8 +936,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;
@@ -965,40 +958,10 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
{
op->flags |= OP_IMMEDIATE_COMMIT;
}
bool searched = false;
std::shared_ptr<inode_cache_t> icache;
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
{
if (!searched)
{
icache = inode_cache_get(op->inode);
searched = true;
}
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
{
op->retval = -EINVAL;
auto cb = std::move(op->callback);
cb(op);
return false;
}
if (icache && icache->op_enc)
{
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
}
else
op->enc.reset();
}
else
op->enc.reset();
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
{
if (!searched)
{
icache = inode_cache_get(op->inode);
searched = true;
}
if (icache && icache->readonly)
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
{
op->retval = -EROFS;
auto cb = std::move(op->callback);
@@ -1009,39 +972,33 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
op->deoptimise_snapshot = false;
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
{
if (!searched)
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end())
{
icache = inode_cache_get(op->inode);
searched = true;
}
if (icache)
{
for (auto & parent: icache->chain)
int chain_size = 0;
while (ino_it != st_cli.inode_config.end() && ino_it->second.parent_id)
{
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
// Check for loops - FIXME check it in etcd_state_client
if (ino_it->second.parent_id == op->inode ||
chain_size > st_cli.inode_config.size())
{
op->retval = -EINVAL;
auto cb = std::move(op->callback);
cb(op);
return false;
}
if (INODE_POOL(ino_it->second.parent_id) == INODE_POOL(ino_it->first) &&
wb->has_inode(ino_it->second.parent_id))
{
// Deoptimise reads - we have dirty data for one of the parent layer(s).
op->deoptimise_snapshot = true;
break;
}
chain_size++;
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
}
}
}
if (icache && icache->err_code)
{
if (icache->err_code == EPERM)
{
op->retval = -EPERM;
auto cb = std::move(op->callback);
cb(op);
return false;
}
else if (icache->err_code == EAGAIN)
{
key_wait_ops.push_back(op);
return false;
}
}
return true;
}
@@ -1057,7 +1014,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);
}
}
@@ -1171,33 +1128,31 @@ resume_2:
}
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
{
uint64_t next_inode = 0;
auto icache = inode_cache_get(op->cur_inode);
if (icache)
// Check parent inode
auto ino_it = st_cli.inode_config.find(op->cur_inode);
// Skip parents from the same pool
int skipped = 0;
while (!op->deoptimise_snapshot &&
ino_it != st_cli.inode_config.end() && ino_it->second.parent_id &&
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
{
if (icache->has_parent_loop)
// Check for loops - FIXME check it in etcd_state_client
if (ino_it->second.parent_id == op->inode ||
skipped > st_cli.inode_config.size())
{
op->retval = -EINVAL;
erase_op(op);
return 1;
}
if (op->deoptimise_snapshot)
{
if (icache->chain.size() > 1)
next_inode = icache->chain[1];
}
else
{
if (icache->other_pool_parent_id)
next_inode = icache->other_pool_parent_id;
}
skipped++;
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
}
if (next_inode)
if (ino_it != st_cli.inode_config.end() &&
ino_it->second.parent_id &&
ino_it->second.parent_id != op->inode)
{
// Continue reading from the parent inode
icache = inode_cache_get(next_inode);
op->cur_inode = next_inode;
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
op->cur_inode = ino_it->second.parent_id;
op->parts.clear();
op->done_count = 0;
goto resume_0;
@@ -1206,7 +1161,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)
@@ -1248,7 +1203,7 @@ resume_2:
return 0;
}
static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov)
static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov, void *scrap, int scrap_len)
{
int left = size;
while (left > 0 && iov_idx < op->iov.count)
@@ -1256,7 +1211,7 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
int cur_left = op->iov.buf[iov_idx].iov_len - iov_pos;
if (cur_left < left)
{
if (skip == 0)
if (!skip)
{
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, cur_left);
}
@@ -1266,7 +1221,7 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
}
else
{
if (skip == 0)
if (!skip)
{
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, left);
}
@@ -1275,10 +1230,16 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
}
}
assert(left == 0);
if (skip == 1)
if (skip && scrap_len > 0)
{
// data read into a NULL buffer will be discarded by messenger
iov.push_back(NULL, size);
// All skipped ranges are read into the same useless buffer
left = size;
while (left > 0)
{
int cur_left = scrap_len < left ? scrap_len : left;
iov.push_back(scrap, cur_left);
left -= cur_left;
}
}
}
@@ -1286,7 +1247,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;
@@ -1298,11 +1259,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
// Allocate memory for the bitmap
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
unsigned bitmap_mem = object_bitmap_size +
op->parts.size() * pg_data_size *
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
// read chain_info - max 4 bytes per block
+ (op->enc ? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*op->len/pool_cfg.bitmap_granularity : 0));
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
if (!op->bitmap_buf || op->bitmap_buf_size < bitmap_mem)
{
op->bitmap_buf = realloc_or_die(op->bitmap_buf, bitmap_mem);
@@ -1344,10 +1301,10 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
{
begin = cur;
// Just advance iov_idx & iov_pos
add_iov(cur-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
add_iov(cur-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
}
else
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
}
skip_prev = skip;
prev = cur;
@@ -1358,11 +1315,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
if (skip_prev)
{
// Just advance iov_idx & iov_pos
add_iov(end-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
add_iov(end-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
end = prev;
}
else
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
if (end == begin)
{
op->done_count++;
@@ -1371,7 +1328,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
}
else if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_DELETE)
{
add_iov(end-begin, 0, op, iov_idx, iov_pos, op->parts[i].iov);
add_iov(end-begin, false, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
}
op->parts[i].parent = op;
op->parts[i].offset = begin;
@@ -1389,7 +1346,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 +1365,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 +1389,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 &&
@@ -1457,17 +1414,14 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
osd_client_t *cl = peer_it->second;
part->flags |= PART_SENT|PART_VALID;
op->inflight_count++;
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
// read chain_info - max 4 bytes per block
+ (op->opcode == OSD_OP_READ && op->enc
? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*pool_cfg.data_block_size/pool_cfg.bitmap_granularity
: 0));
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
);
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){
@@ -1481,7 +1435,6 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
.inode = op->cur_inode,
.offset = part->offset,
.len = part->len,
.flags = op->opcode == OSD_OP_READ && op->enc && !op->deoptimise_snapshot ? OSD_OP_RETURN_CHAIN : 0,
.meta_revision = meta_rev,
.version = op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE ? op->version : 0,
} },
@@ -1489,7 +1442,6 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
? pg_bitmap_size : 0),
.enc = op->enc,
.callback = cb ? cb : [this, part](osd_op_t *op_part)
{
handle_op_part(part);
@@ -1501,7 +1453,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 +1559,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 +1567,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)
@@ -1694,7 +1648,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
);
+7 -62
View File
@@ -4,8 +4,7 @@
#pragma once
#include "messenger.h"
#include "etcd_state_client_http.h"
#include "../util/robin_hood.h"
#include "etcd_state_client.h"
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
#define DEFAULT_CLIENT_MAX_DIRTY_OPS 1024
@@ -72,7 +71,6 @@ protected:
cluster_op_t *prev = NULL, *next = NULL;
int prev_wait = 0;
uint64_t flush_id = 0;
std::shared_ptr<osd_op_enc_t> enc;
friend class cluster_client_t;
friend class writeback_cache_t;
};
@@ -82,33 +80,14 @@ struct inode_list_osd_t;
struct inode_list_pg_t;
class writeback_cache_t;
struct inode_cache_t
{
std::vector<inode_t> chain; // only parents from the same pool
uint8_t *key_data = NULL;
osd_op_enc_t *op_enc = NULL;
bool readonly = false;
bool has_parent_loop = false;
inode_t other_pool_parent_id = 0;
int err_code = 0;
~inode_cache_t();
};
struct vault_load_key_t
{
int key_state = 0;
std::string key;
};
// FIXME: Split into public and private interfaces
class __attribute__((visibility("default"))) cluster_client_t
{
timerfd_manager_t *tfd = NULL;
ring_loop_t *ringloop = NULL;
// config:
std::map<pool_id_t, uint64_t> pg_counts;
std::map<pool_pg_num_t, osd_num_t> pg_primary;
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
uint64_t client_max_dirty_bytes = 0;
uint64_t client_max_dirty_ops = 0;
@@ -120,23 +99,12 @@ class __attribute__((visibility("default"))) cluster_client_t
uint64_t client_max_writeback_iodepth = 0;
std::string conf_hostname;
std::string vault_url;
std::string vault_client_cert;
std::string vault_client_key;
std::string vault_ca;
std::string vault_secret_api_path;
uint64_t vault_timeout_ms = 0;
uint64_t vault_error_timeout_sec = 0;
uint64_t vault_refresh_leeway_sec = 0;
int log_level = 0;
int client_retry_interval = 50; // ms
int client_eio_retry_interval = 1000; // ms
bool client_retry_enospc = true;
int client_wait_up_timeout = 16; // sec (for listings)
// state:
std::string client_hostname;
std::map<std::string, int> self_tree_metrics;
std::map<osd_num_t, int> osd_tree_metrics;
@@ -144,28 +112,15 @@ class __attribute__((visibility("default"))) cluster_client_t
int retry_timeout_id = -1;
int retry_timeout_duration = 0;
std::vector<cluster_op_t*> offline_ops;
std::vector<cluster_op_t*> key_wait_ops;
cluster_op_t *op_queue_head = NULL, *op_queue_tail = NULL;
writeback_cache_t *wb = NULL;
std::set<osd_num_t> dirty_osds;
uint64_t dirty_bytes = 0, dirty_ops = 0;
// inodes require some extra state for read/write, it's stored here.
// moreover, robin_hood access is slightly faster than std::map :)
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
http_context_t *vault_http_ctx = NULL;
http_co_t *vault_http_cli = NULL;
bool vault_loading = false;
std::string vault_token;
bool vault_auth_error = false;
timespec vault_token_expire = {};
std::vector<std::string> vault_key_load_queue;
std::map<std::string, vault_load_key_t> vault_keys;
void *scrap_buffer = NULL;
unsigned scrap_buffer_size = 0;
bool pgs_loaded = false;
std::map<pool_id_t, uint64_t> pg_counts;
ring_consumer_t consumer;
std::vector<std::function<void(void)>> on_ready_hooks;
int list_retry_timeout_id = -1;
@@ -176,7 +131,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 +139,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);
@@ -201,13 +155,6 @@ public:
protected:
void continue_ops(int time_passed = 0);
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
void vault_parse_config();
bool vault_check_token();
void vault_load_keys();
void vault_destroy();
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
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);
@@ -217,7 +164,6 @@ protected:
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
void on_change_osd_state_hook(uint64_t peer_osd);
void on_change_node_placement_hook();
void on_change_inode_hook(uint64_t inode, bool removed);
void execute_internal(cluster_op_t *op);
void execute_cas(cluster_op_t *op);
@@ -234,7 +180,6 @@ protected:
void erase_op(cluster_op_t *op);
void calc_wait(cluster_op_t *op);
void inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *next, int inc);
void continue_lists();
bool continue_listing(inode_list_t *lst);
bool restart_listing(inode_list_t* lst);
-245
View File
@@ -1,245 +0,0 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdexcept>
#include <assert.h>
#include "cluster_client_impl.h"
#include "str_util.h"
inode_cache_t::~inode_cache_t()
{
if (key_data)
{
free(key_data);
key_data = NULL;
op_enc = NULL;
}
}
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())
vault_secret_api_path = config["vault_secret_api_path"].string_value();
vault_timeout_ms = config["vault_timeout_ms"].uint64_value();
if (!vault_timeout_ms)
vault_timeout_ms = 5000;
vault_error_timeout_sec = config["vault_error_timeout_sec"].uint64_value();
if (!vault_error_timeout_sec)
vault_error_timeout_sec = 60;
vault_refresh_leeway_sec = config["vault_refresh_leeway_sec"].uint64_value();
if (!vault_refresh_leeway_sec)
vault_refresh_leeway_sec = 60;
}
// FIXME: Rework client API by adding open/close and cache inode information in the "FD" (maybe)
void cluster_client_t::on_change_inode_hook(uint64_t inode, bool removed)
{
std::vector<inode_t> children = { inode };
for (size_t i = 0; i < children.size(); i++)
{
auto it = inode_cache_children.lower_bound(std::make_pair(children[i], (inode_t)0));
while (it != inode_cache_children.end() && it->first == children[i])
{
children.push_back(it->second);
it++;
}
}
for (auto & inode: children)
{
auto it = inode_cache.find(inode);
if (it != inode_cache.end())
{
auto icache = it->second;
for (auto & parent: icache->chain)
{
inode_cache_children.erase(std::make_pair(parent, inode));
}
inode_cache.erase(it);
}
}
}
std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
{
auto icache_it = inode_cache.find(ino);
if (icache_it != inode_cache.end())
{
return icache_it->second;
}
// Fill inode cache
auto ino_it = st_cli->inode_config.find(ino);
if (ino_it == st_cli->inode_config.end())
{
inode_cache[ino] = NULL;
return NULL;
}
auto pool_it = st_cli->pool_config.find(INODE_POOL(ino));
if (pool_it == st_cli->pool_config.end())
{
inode_cache[ino] = NULL;
return NULL;
}
auto & inode_cfg = ino_it->second;
auto & pool_cfg = pool_it->second;
std::shared_ptr<inode_cache_t> icache = std::make_shared<inode_cache_t>();
icache->readonly = inode_cfg.readonly;
icache->chain.push_back(ino);
std::vector<inode_config_t*> chain_cfg;
// FIXME: Allow unencrypted read & write when all chain is encrypted with the same key
int enc_key_count = !inode_cfg.enc_key.empty() ? 1 : 0;
if (inode_cfg.parent_id)
{
// Check for loops and cache the chain
robin_hood::unordered_flat_set<inode_t> seen;
seen.insert(ino);
uint64_t parent_id = inode_cfg.parent_id;
while (parent_id)
{
if (seen.find(parent_id) != seen.end())
{
icache->has_parent_loop = true;
break;
}
seen.insert(parent_id);
ino_it = st_cli->inode_config.find(parent_id);
if (INODE_POOL(parent_id) == INODE_POOL(ino))
{
icache->chain.push_back(parent_id);
if (ino_it == st_cli->inode_config.end())
chain_cfg.push_back(NULL);
else
{
chain_cfg.push_back(&ino_it->second);
if (!ino_it->second.enc_key.empty())
enc_key_count++;
}
}
else if (!icache->other_pool_parent_id)
icache->other_pool_parent_id = parent_id;
if (ino_it == st_cli->inode_config.end())
break;
parent_id = ino_it->second.parent_id;
}
}
// Check external keys and wait for loading, if required
if (enc_key_count)
{
for (size_t i = 0; i <= chain_cfg.size(); i++)
{
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
{
auto & ik = vault_keys[inode_cfg.enc_key];
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
{
icache->err_code = EPERM;
enc_key_count = 0;
}
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
{
ik.key_state = VAULT_KEY_LOADING;
vault_key_load_queue.push_back(inode_cfg.enc_key);
vault_load_keys();
icache->err_code = EAGAIN;
enc_key_count = 0;
}
else if (ik.key_state == VAULT_KEY_LOADING)
{
icache->err_code = EAGAIN;
enc_key_count = 0;
}
else
{
assert(ik.key_state == VAULT_KEY_LOADED);
}
}
}
}
// Generate encryption key chain, if applicable
if (enc_key_count)
{
uint8_t *key_data = (uint8_t*)malloc_or_die(
AES_256_XTS_KEY_SIZE * enc_key_count +
sizeof(uint8_t*) * icache->chain.size() +
sizeof(osd_op_enc_t)
);
uint8_t **keys = (uint8_t**)(key_data + AES_256_XTS_KEY_SIZE * enc_key_count);
osd_op_enc_t *enc = (osd_op_enc_t*)((uint8_t*)keys + sizeof(uint8_t*)*icache->chain.size());
size_t key_pos = 0;
for (size_t i = 0; i <= chain_cfg.size(); i++)
{
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
if (cfg && !cfg->enc_key.empty())
{
const auto & key = cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX
? vault_keys.at(cfg->enc_key).key
: cfg->enc_key;
assert(key_pos < AES_256_XTS_KEY_SIZE * enc_key_count);
assert(key.size() == 2*AES_256_XTS_KEY_SIZE);
keys[i] = key_data + key_pos;
fromhexstr(key, AES_256_XTS_KEY_SIZE, key_data + key_pos);
key_pos += AES_256_XTS_KEY_SIZE;
}
else
keys[i] = NULL;
}
enc->key_chain = keys;
enc->chain_size = icache->chain.size();
enc->read_chain_bitmap_pos = pool_cfg.data_block_size/pool_cfg.bitmap_granularity/8;
enc->bitmap_granularity = pool_cfg.bitmap_granularity;
icache->key_data = key_data;
icache->op_enc = enc;
}
inode_cache[ino] = icache;
for (auto & parent: icache->chain)
{
if (parent != ino)
inode_cache_children.insert(std::make_pair(parent, ino));
}
return icache;
}
void cluster_client_t::vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data)
{
vault_loading = false;
auto & k = vault_keys[key_id];
if (err != "")
{
k.key_state = VAULT_KEY_ERROR;
fprintf(stderr, "Vault %s%s%s request failed: %s\n", vault_url.c_str(),
vault_secret_api_path.c_str(), key_id.c_str()+strlen(VAULT_KEY_PREFIX), err.c_str());
}
else
{
auto hexkey = data["data"]["key"].string_value();
if (hexkey.empty() || !ishexstr(hexkey) || hexkey.size() != 2*AES_256_XTS_KEY_SIZE)
{
k.key_state = VAULT_KEY_ERROR;
fprintf(stderr, "Vault /v1/secret/%s request failed: 'key' is empty or has invalid format\n", key_id.c_str());
}
else
{
k.key_state = VAULT_KEY_LOADED;
k.key = hexkey;
}
}
if (vault_key_load_queue.empty())
{
auto ops = std::move(key_wait_ops);
for (cluster_op_t *op: ops)
inode_cache.erase(op->inode);
for (cluster_op_t *op: ops)
execute_internal(op);
}
else
vault_load_keys();
}
+1 -5
View File
@@ -5,6 +5,7 @@
#include "cluster_client.h"
#define SCRAP_BUFFER_SIZE 4*1024*1024
#define PART_SENT 1
#define PART_DONE 2
#define PART_ERROR 4
@@ -17,11 +18,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;
+10 -10
View File
@@ -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
-125
View File
@@ -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);
}
);
}
-2
View File
@@ -131,7 +131,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 +165,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,
+525 -218
View File
@@ -1,14 +1,14 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#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 +17,28 @@ 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_close(etcd_watch_ws);
etcd_watch_ws = NULL;
}
if (keepalive_client)
{
http_close(keepalive_client);
keepalive_client = 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,93 +72,146 @@ 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)
void etcd_state_client_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
int timeout, std::function<void(std::string, json11::Json)> callback)
{
auto user_it = user_info.find(username);
if (user_it != user_info.end())
std::string etcd_api_path;
int pos = etcd_address.find('/');
if (pos >= 0)
{
return user_it->second;
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
}
auto inf = std::make_shared<user_info_t>();
inf->name = username;
return inf;
std::string req = payload.dump();
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
"Host: "+etcd_address+"\r\n"
"Content-Type: application/json\r\n"
"Content-Length: "+std::to_string(req.size())+"\r\n"
"Connection: close\r\n"
"\r\n"+req;
auto http_cli = http_init(tfd);
auto cb = [http_cli, callback](const http_response_t *response)
{
std::string err;
json11::Json data;
response->parse_json_response(err, data);
callback(err, data);
http_close(http_cli);
};
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
}
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(std::string api, json11::Json payload, int timeout,
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
{
if (user_info->type == user_type_t::ADMIN)
if (!etcd_addresses.size() && !etcd_local.size())
{
return true;
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
exit(1);
}
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)
pick_next_etcd();
std::string etcd_address = selected_etcd_address;
std::string etcd_api_path;
int pos = etcd_address.find('/');
if (pos >= 0)
{
return write ? (cache_it->second.perm == user_perm_t::OWNER) : (cache_it->second.perm != user_perm_t::DENY);
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
}
auto inode_it = inode_config.find(inode_num);
if (inode_it == inode_config.end())
std::string req = payload.dump();
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
"Host: "+etcd_address+"\r\n"
"Content-Type: application/json\r\n"
"Content-Length: "+std::to_string(req.size())+"\r\n"
"Connection: 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 = selected_etcd_address](const http_response_t *response)
{
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);
}
void etcd_state_client_t::add_etcd_url(std::string etcd_address)
{
if (etcd_address.size() > 0)
{
if (!local_ips.size())
std::string err;
json11::Json data;
response->parse_json_response(err, data);
if (err != "")
{
// Fill local_ips
for (auto & ip: getifaddr_list(std::vector<addr_mask_t>(), true))
local_ips.insert(ip);
}
std::string etcd_api_path;
bool ssl = false;
if (etcd_address.substr(0, 8) == "https://")
{
ssl = true;
etcd_address = etcd_address.substr(8);
}
else if (etcd_address.substr(0, 7) == "http://")
etcd_address = etcd_address.substr(7);
auto pos = etcd_address.find('/');
if (pos != std::string::npos)
{
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
if (cur_addr == selected_etcd_address)
selected_etcd_address = "";
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
etcd_api_path = "/v3";
pos = etcd_address.find(':');
auto check_addr = (pos != std::string::npos ? etcd_address.substr(0, pos) : etcd_address);
bool is_local = local_ips.find(check_addr) != local_ips.end();
auto & to = (is_local ? etcd_local : etcd_addresses);
check_addr = (ssl ? "https://" : "http://") + etcd_address + etcd_api_path;
size_t i;
callback(err, data);
};
if (!keepalive_client)
{
keepalive_client = http_init(tfd);
}
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
}
void etcd_state_client_t::add_etcd_url(std::string addr)
{
if (addr.length() > 0)
{
if (strtolower(addr.substr(0, 7)) == "http://")
addr = addr.substr(7);
else if (strtolower(addr.substr(0, 8)) == "https://")
{
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
exit(1);
}
if (!local_ips.size())
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
std::string check_addr;
int pos = addr.find('/');
int pos2 = addr.find(':');
if (pos2 >= 0)
check_addr = addr.substr(0, pos2);
else if (pos >= 0)
check_addr = addr.substr(0, pos);
else
check_addr = addr;
if (pos == std::string::npos)
addr += "/v3";
bool local = false;
int i;
for (i = 0; i < local_ips.size(); i++)
{
if (local_ips[i] == check_addr)
{
local = true;
break;
}
}
auto & to = local ? this->etcd_local : this->etcd_addresses;
for (i = 0; i < to.size(); i++)
{
if (to[i] == check_addr)
if (to[i] == addr)
break;
}
if (i >= to.size())
{
to.push_back(check_addr);
// Check if it's a domain name
sockaddr_storage ss;
bool is_name = !is_local && !string_to_addr(etcd_address, true, 0, &ss);
auto & to_addr = (is_local ? etcd_local_addr_urls : (is_name ? etcd_name_urls : etcd_nonlocal_addr_urls));
to_addr.push_back((http_url_t){ .ssl = ssl, .addr = etcd_address, .hostname = etcd_address, .path = etcd_api_path });
}
to.push_back(addr);
}
}
@@ -146,9 +219,6 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
{
this->etcd_local.clear();
this->etcd_addresses.clear();
this->etcd_local_addr_urls.clear();
this->etcd_nonlocal_addr_urls.clear();
this->etcd_name_urls.clear();
if (config["etcd_address"].is_string())
{
std::string ea = config["etcd_address"].string_value();
@@ -169,22 +239,6 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
add_etcd_url(ea.string_value());
}
}
if (this->etcd_client_cert != "")
{
this->etcd_client_cert = config["etcd_client_cert"].string_value();
this->etcd_client_key = config["etcd_client_key"].string_value();
}
else if (this->osd_num)
{
this->etcd_client_cert = config["osd_cert"].string_value();
this->etcd_client_key = config["osd_pkey"].string_value();
}
else
{
this->etcd_client_cert = config["cert"].string_value();
this->etcd_client_key = config["pkey"].string_value();
}
this->etcd_ca = config["etcd_ca"].string_value();
this->etcd_prefix = config["etcd_prefix"].string_value();
if (this->etcd_prefix == "")
{
@@ -202,6 +256,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 +282,294 @@ 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()
{
if (selected_etcd_address != "")
return;
if (addresses_to_try.size() == 0)
{
// Prefer local etcd, if any
for (int i = 0; i < etcd_local.size(); i++)
addresses_to_try.push_back(etcd_local[i]);
std::vector<int> ns;
for (int i = 0; i < etcd_addresses.size(); i++)
ns.push_back(i);
if (!rand_initialized)
{
timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
rand_initialized = true;
}
while (ns.size())
{
int i = lrand48() % ns.size();
addresses_to_try.push_back(etcd_addresses[ns[i]]);
ns.erase(ns.begin()+i, ns.begin()+i+1);
}
}
selected_etcd_address = addresses_to_try[0];
addresses_to_try.erase(addresses_to_try.begin(), addresses_to_try.begin()+1);
}
void etcd_state_client_t::start_etcd_watcher()
{
if (!etcd_addresses.size() && !etcd_local.size())
{
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
exit(1);
}
pick_next_etcd();
std::string etcd_address = selected_etcd_address;
std::string etcd_api_path;
int pos = etcd_address.find('/');
if (pos >= 0)
{
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
}
etcd_watches_initialised = 0;
ws_alive = 1;
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
}
if (this->log_level > 1)
{
fprintf(stderr, "Trying to connect to etcd websocket at %s, watch from revision %ju/%ju/%ju\n", etcd_address.c_str(),
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
}
etcd_watch_ws = open_websocket(tfd, etcd_address, etcd_api_path+"/watch", etcd_slow_timeout,
[this, cur_addr = selected_etcd_address](const http_response_t *msg)
{
if (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_ws = NULL;
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;
addresses_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_address)
selected_etcd_address = "";
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
}
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_address.c_str());
}
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
}
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 +583,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 +629,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 +667,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 +698,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 +735,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 +865,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;
@@ -549,8 +942,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
pc.used_for_app = "fs:"+pc.used_for_app;
else
pc.used_for_app = pool_item.second["used_for_app"].as_string();
// Create group permission
pc.creator_group = pool_item.second["creator_group"].string_value();
// Local Read Configuration
std::string local_reads = pool_item.second["local_reads"].string_value();
if (local_reads == "nearest")
@@ -814,14 +1205,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,15 +1259,37 @@ 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
{
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
inode_t parent_inode_num = value["parent_id"].uint64_value();
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
{
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
if (!parent_pool_id)
parent_inode_num |= pool_id << (64-POOL_ID_BITS);
else if (parent_pool_id >= POOL_ID_MAX)
{
fprintf(
stderr, "Inode %ju/%ju parent_pool value is invalid, ignoring parent setting\n",
inode_num >> (64-POOL_ID_BITS), inode_num & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)
);
parent_inode_num = 0;
}
else
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
}
insert_inode_config((inode_config_t){
.num = inode_num,
.name = value["name"].string_value(),
.size = value["size"].uint64_value(),
.parent_id = parent_inode_num,
.readonly = value["readonly"].bool_value(),
.deleted = value["deleted"].bool_value(),
.meta = value["meta"],
.mod_revision = kv.mod_revision,
});
}
}
}
@@ -893,38 +1300,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
if (on_change_node_placement_hook)
on_change_node_placement_hook();
}
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
{
// <etcd_prefix>/config/user/<username>
auto name = key.substr(etcd_prefix.length()+13);
auto & inf = user_info[name];
if (!value.is_object())
{
if (inf)
{
inf->type = user_type_t::CLIENT;
inf->groups.clear();
inf->perm_cache.clear();
}
user_info.erase(name);
}
else
{
if (!inf)
{
inf = std::make_shared<user_info_t>();
inf->name = name;
}
inf->type = value["type"] == "admin" ? user_type_t::ADMIN : user_type_t::CLIENT;
inf->groups.clear();
for (auto & group: value["groups"].array_items())
{
if (group.string_value() != "")
inf->groups.insert(group.string_value());
}
inf->perm_cache.clear();
}
}
}
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
@@ -947,12 +1322,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;
@@ -1008,10 +1378,6 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
new_cfg["parent_pool"] = (uint64_t)INODE_POOL(cfg->parent_id);
new_cfg["parent_id"] = (uint64_t)INODE_NO_POOL(cfg->parent_id);
}
if (!cfg->enc_key.empty())
{
new_cfg["enc_key"] = cfg->enc_key;
}
if (cfg->readonly)
{
new_cfg["readonly"] = true;
@@ -1020,18 +1386,6 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
{
new_cfg["deleted"] = true;
}
if (!cfg->owner.empty())
{
new_cfg["owner"] = cfg->owner;
}
if (!cfg->owner_group.empty())
{
new_cfg["owner_group"] = cfg->owner_group;
}
if (!cfg->reader_group.empty())
{
new_cfg["reader_group"] = cfg->reader_group;
}
if (cfg->meta.is_object())
{
new_cfg["meta"] = cfg->meta;
@@ -1039,53 +1393,6 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
return new_cfg;
}
inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision)
{
inode_t parent_inode_num = value["parent_id"].uint64_value();
if (parent_inode_num && !INODE_POOL(parent_inode_num))
{
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
if (!parent_pool_id)
parent_inode_num = INODE_WITH_POOL(INODE_POOL(inode_num), parent_inode_num);
else if (parent_pool_id >= POOL_ID_MAX)
{
fprintf(
stderr, "Inode %u/%ju parent_pool value is invalid, ignoring parent setting\n",
INODE_POOL(inode_num), INODE_NO_POOL(inode_num)
);
parent_inode_num = 0;
}
else
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
}
std::string enc_key;
if (!value["enc_key"].is_null())
{
enc_key = value["enc_key"].string_value();
if (enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) != VAULT_KEY_PREFIX &&
(enc_key.size() != 2*AES_256_XTS_KEY_SIZE || !ishexstr(enc_key)))
{
enc_key = "";
fprintf(stderr, "Inode %u/%ju has invalid enc_key, should be %u bit hex string or Vault key reference\n",
INODE_POOL(inode_num), INODE_NO_POOL(inode_num), AES_256_XTS_KEY_SIZE);
}
}
return (inode_config_t){
.num = inode_num,
.name = value["name"].string_value(),
.size = value["size"].uint64_value(),
.parent_id = parent_inode_num,
.readonly = value["readonly"].bool_value(),
.deleted = value["deleted"].bool_value(),
.enc_key = std::move(enc_key),
.owner = value["owner"].string_value(),
.owner_group = value["owner_group"].string_value(),
.reader_group = value["reader_group"].string_value(),
.meta = value["meta"],
.mod_revision = mod_revision,
};
}
int etcd_state_client_t::address_count()
{
return etcd_addresses.size() + etcd_local.size();

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