Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62826a4627 | ||
|
|
35e7bc8aeb | ||
|
|
9a5fabddb4 | ||
|
|
088e85a423 | ||
|
|
43f1d58f29 | ||
|
|
a6b29a01a2 | ||
|
|
5d65d87dfb | ||
|
|
ddf28feaf8 | ||
|
|
a57049be63 | ||
|
|
7f53e315c5 | ||
|
|
48756520dd | ||
|
|
4f23b242f3 | ||
|
|
c371b74e12 | ||
|
|
c014d20fca | ||
|
|
9b2480d552 | ||
|
|
1ae10c21ca | ||
|
|
4698009b59 | ||
|
|
d0e0b70f81 | ||
|
|
914f42da5c | ||
|
|
273b641820 | ||
|
|
4f9de7a6fb | ||
|
|
d3c529abd7 | ||
|
|
a8e19ba28a | ||
|
|
64a8cd1f4b | ||
|
|
cc6e531410 | ||
|
|
ca608d0b87 | ||
|
|
0e095d2347 | ||
|
|
4fe2a0a3eb | ||
|
|
1a4d275616 | ||
|
|
803c870493 | ||
|
|
0d97fba466 | ||
|
|
41100260ec | ||
|
|
07c9606594 | ||
|
|
4ba361d83d | ||
|
|
662fb86eae | ||
|
|
1df8b51abb | ||
|
|
d2d01e5183 | ||
|
|
3d96c3907e | ||
|
|
fb483185ff | ||
|
|
d6eb00e18e | ||
|
|
34d47bd62a | ||
|
|
2846eba3af |
@@ -234,6 +234,60 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https_antietcd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_interrupted_rebalance:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -648,6 +702,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_snapshot_chain:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1224,6 +1296,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum_xxhash:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -2052,3 +2142,39 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted_ec:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
|
||||
@@ -38,6 +38,14 @@ for my $line (<>)
|
||||
{
|
||||
$test_name .= '_antietcd';
|
||||
}
|
||||
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
|
||||
{
|
||||
$test_name .= '_https';
|
||||
}
|
||||
elsif ($1 eq 'ENCRYPTED')
|
||||
{
|
||||
$test_name .= '_encrypted';
|
||||
}
|
||||
elsif ($1 eq 'OLD')
|
||||
{
|
||||
$test_name =~ s/^test_/test_old_/s;
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
package-lock.json
|
||||
fio
|
||||
qemu
|
||||
node_modules
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.13")
|
||||
set(VITASTOR_VERSION "3.0.9")
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
|
||||
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](docs/config/osd.ru.md)
|
||||
- [Параметры мониторов](docs/config/monitor.ru.md)
|
||||
- [Безопасность](docs/config/security.ru.md)
|
||||
- [Настройки пулов](docs/config/pool.ru.md)
|
||||
- [Метаданные образов в etcd](docs/config/inode.ru.md)
|
||||
- Использование
|
||||
|
||||
@@ -62,6 +62,7 @@ Read more details in the documentation. You can start from here: [Quick Start](d
|
||||
- [OSD Disk Layout](docs/config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](docs/config/osd.en.md)
|
||||
- [Monitor](docs/config/monitor.en.md)
|
||||
- [Security](docs/config/security.en.md)
|
||||
- [Pool configuration](docs/config/pool.en.md)
|
||||
- [Image metadata in etcd](docs/config/inode.en.md)
|
||||
- Usage
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.13
|
||||
VITASTOR_VERSION ?= v3.0.9
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.13
|
||||
image: vitalif/vitastor-csi:v3.0.9
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.13
|
||||
image: vitalif/vitastor-csi:v3.0.9
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.13"
|
||||
vitastorCSIDriverVersion = "3.0.9"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.13-1) unstable; urgency=medium
|
||||
vitastor (3.0.9-1) unstable; urgency=medium
|
||||
|
||||
* Bugfixes
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Vitaliy Filippov <vitalif@yourcmc.ru>
|
||||
Build-Depends: debhelper, g++ (>= 8), libstdc++6 (>= 8),
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev, libc-ares-dev,
|
||||
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
|
||||
node-bindings <!nocheck>, node-gyp, node-nan
|
||||
Standards-Version: 4.5.0
|
||||
|
||||
Vendored
+1
-1
@@ -25,7 +25,7 @@ RUN set -e -x; \
|
||||
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev \
|
||||
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
|
||||
apt-get -y build-dep fio && \
|
||||
apt-get --download-only source fio
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.13
|
||||
VITASTOR_VERSION ?= v3.0.9
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.13
|
||||
VITASTOR_VERSION=v3.0.9
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
@@ -38,3 +38,4 @@ In the future, additional configuration methods may be added:
|
||||
- [OSD Disk Layout](config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](config/osd.en.md)
|
||||
- [Monitor](config/monitor.en.md)
|
||||
- [Security Parameters](config/security.en.md)
|
||||
|
||||
@@ -41,3 +41,4 @@
|
||||
- [Дисковые параметры OSD](config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](config/osd.ru.md)
|
||||
- [Параметры мониторов](config/monitor.ru.md)
|
||||
- [Параметры безопасности](config/security.ru.md)
|
||||
|
||||
@@ -198,8 +198,14 @@ put a modified value into etcd key /vitastor/config/global.
|
||||
- Type: string
|
||||
- Default: none
|
||||
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
|
||||
## csum_block_size
|
||||
|
||||
|
||||
@@ -209,8 +209,12 @@ journal_block_size и meta_block_size. Однако на данный момен
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: none
|
||||
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
[Documentation](../../README.md#documentation) → [Configuration](../config.en.md) → Security Parameters
|
||||
|
||||
-----
|
||||
|
||||
[Читать на русском](security.ru.md)
|
||||
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
|
||||
- [etcd_client_cert](#etcd_client_cert)
|
||||
- [etcd_client_key](#etcd_client_key)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [osd_etcd_client_cert](#osd_etcd_client_cert)
|
||||
- [osd_etcd_client_key](#osd_etcd_client_key)
|
||||
- [mon_etcd_client_cert](#mon_etcd_client_cert)
|
||||
- [mon_etcd_client_key](#mon_etcd_client_key)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
- [vault_client_key](#vault_client_key)
|
||||
- [vault_ca](#vault_ca)
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
|
||||
etcd https connections. May be path to a file or just a PEM string with certificate.
|
||||
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for etcd_client_cert (also a file or a PEM string).
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify etcd server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
|
||||
OSDs, clients and monitors should have different permissions, so they should
|
||||
use different certificates.
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
|
||||
|
||||
## vault_url
|
||||
|
||||
- Type: string
|
||||
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
|
||||
## vault_secret_api_path
|
||||
|
||||
- Type: string
|
||||
- Default: /v1/secret/
|
||||
|
||||
Vault v1 secret API mount path to use.
|
||||
|
||||
## vault_client_cert
|
||||
|
||||
- Type: string
|
||||
|
||||
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
|
||||
may be path to a file or just a certificate in PEM string.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Type: string
|
||||
|
||||
Private key for vault_client_cert (also a file or a PEM string).
|
||||
|
||||
## vault_ca
|
||||
|
||||
- Type: string
|
||||
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
|
||||
## vault_timeout_ms
|
||||
|
||||
- Type: integer
|
||||
- Default: 5000
|
||||
|
||||
Timeout for Vault requests in milliseconds.
|
||||
|
||||
## vault_error_timeout_sec
|
||||
|
||||
- Type: integer
|
||||
- Default: 60
|
||||
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
|
||||
## vault_refresh_leeway_sec
|
||||
|
||||
- Type: integer
|
||||
- Default: 60
|
||||
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
|
||||
## max_aes_xts_pool_size
|
||||
|
||||
- Type: integer
|
||||
- Default: 256
|
||||
|
||||
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
|
||||
doesn't require modification.
|
||||
@@ -0,0 +1,154 @@
|
||||
[Документация](../../README-ru.md#документация) → [Конфигурация](../config.ru.md) → Параметры безопасности
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
|
||||
- [etcd_client_cert](#etcd_client_cert)
|
||||
- [etcd_client_key](#etcd_client_key)
|
||||
- [etcd_ca](#etcd_ca)
|
||||
- [osd_etcd_client_cert](#osd_etcd_client_cert)
|
||||
- [osd_etcd_client_key](#osd_etcd_client_key)
|
||||
- [mon_etcd_client_cert](#mon_etcd_client_cert)
|
||||
- [mon_etcd_client_key](#mon_etcd_client_key)
|
||||
- [vault_url](#vault_url)
|
||||
- [vault_secret_api_path](#vault_secret_api_path)
|
||||
- [vault_client_cert](#vault_client_cert)
|
||||
- [vault_client_key](#vault_client_key)
|
||||
- [vault_ca](#vault_ca)
|
||||
- [vault_timeout_ms](#vault_timeout_ms)
|
||||
- [vault_error_timeout_sec](#vault_error_timeout_sec)
|
||||
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
|
||||
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
|
||||
|
||||
## etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
|
||||
## etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
|
||||
|
||||
## etcd_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
|
||||
## osd_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
|
||||
## osd_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
|
||||
|
||||
## mon_etcd_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
|
||||
|
||||
## mon_etcd_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
|
||||
|
||||
## vault_url
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
|
||||
## vault_secret_api_path
|
||||
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: /v1/secret/
|
||||
|
||||
Путь к API секретов v1 для использования клиентами.
|
||||
|
||||
## vault_client_cert
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
|
||||
## vault_client_key
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
|
||||
|
||||
## vault_ca
|
||||
|
||||
- Тип: строка
|
||||
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
|
||||
## vault_timeout_ms
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 5000
|
||||
|
||||
Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
|
||||
## vault_error_timeout_sec
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 60
|
||||
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
|
||||
## vault_refresh_leeway_sec
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 60
|
||||
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
|
||||
## max_aes_xts_pool_size
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 256
|
||||
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
|
||||
Вряд ли требует изменения.
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.en.md|indent=2}}
|
||||
|
||||
{{../../config/security.en.md|indent=2}}
|
||||
|
||||
{{../../config/pool.en.md|indent=2}}
|
||||
|
||||
{{../../config/inode.en.md|indent=2}}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.ru.md|indent=2}}
|
||||
|
||||
{{../../config/security.ru.md|indent=2}}
|
||||
|
||||
{{../../config/pool.ru.md|indent=2}}
|
||||
|
||||
{{../../config/inode.ru.md|indent=2}}
|
||||
|
||||
@@ -233,11 +233,21 @@
|
||||
type: string
|
||||
default: none
|
||||
info: |
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
info_ru: |
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
@@ -0,0 +1,131 @@
|
||||
- name: etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
|
||||
etcd https connections. May be path to a file or just a PEM string with certificate.
|
||||
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
- name: etcd_client_key
|
||||
type: string
|
||||
info: Private key for etcd_client_cert (also a file or a PEM string).
|
||||
info_ru: Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
|
||||
- name: etcd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify etcd server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: osd_etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
|
||||
OSDs, clients and monitors should have different permissions, so they should
|
||||
use different certificates.
|
||||
info_ru: |
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
- name: osd_etcd_client_key
|
||||
type: string
|
||||
info: Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
|
||||
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
|
||||
- name: mon_etcd_client_cert
|
||||
type: string
|
||||
info: Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
|
||||
info_ru: Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
|
||||
- name: mon_etcd_client_key
|
||||
type: string
|
||||
info: Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
|
||||
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
|
||||
- name: vault_url
|
||||
type: string
|
||||
info: |
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
info_ru: |
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
- name: vault_secret_api_path
|
||||
type: string
|
||||
default: /v1/secret/
|
||||
info: Vault v1 secret API mount path to use.
|
||||
info_ru: Путь к API секретов v1 для использования клиентами.
|
||||
- name: vault_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
|
||||
may be path to a file or just a certificate in PEM string.
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
- name: vault_client_key
|
||||
type: string
|
||||
info: Private key for vault_client_cert (also a file or a PEM string).
|
||||
info_ru: Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
|
||||
- name: vault_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: vault_timeout_ms
|
||||
type: int
|
||||
default: 5000
|
||||
info: Timeout for Vault requests in milliseconds.
|
||||
info_ru: Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
- name: vault_error_timeout_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
info_ru: |
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
- name: vault_refresh_leeway_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
info_ru: |
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
- name: max_aes_xts_pool_size
|
||||
type: int
|
||||
default: 256
|
||||
info: |
|
||||
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
|
||||
doesn't require modification.
|
||||
info_ru: |
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
|
||||
Вряд ли требует изменения.
|
||||
@@ -26,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.13`
|
||||
`docker pull vitalif/vitastor:v3.0.9`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.13 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
|
||||
3. Reload udev rules: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Enable the vitastor-host service: \
|
||||
|
||||
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
|
||||
Инструкция по установке максимально простая.
|
||||
|
||||
1. Скачайте Docker-образ желаемой версии: \
|
||||
`docker pull vitalif/vitastor:v3.0.13`
|
||||
`docker pull vitalif/vitastor:v3.0.9`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.13 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.9 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
- gcc and g++ 8 or newer, clang 10 or newer, or other compiler with C++11 plus
|
||||
designated initializers support from C++20
|
||||
- CMake
|
||||
- jerasure headers and libraries
|
||||
- jerasure, c-ares headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
- tcmalloc (google-perftools-dev)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
|
||||
назначенных инициализаторов (designated initializers) из C++20
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
- tcmalloc (google-perftools-dev)
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
|
||||
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
|
||||
- [S3](../installation/s3.en.md)
|
||||
- [TLS support for etcd connections](../config/security.en.md)
|
||||
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
|
||||
|
||||
## Plugins and tools
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
|
||||
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
|
||||
- [S3](../installation/s3.ru.md)
|
||||
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
|
||||
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
|
||||
|
||||
## Драйверы и инструменты
|
||||
|
||||
|
||||
+21
-7
@@ -125,18 +125,31 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
|
||||
|
||||
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
|
||||
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
|
||||
Pool must be specified if there is more than one pool.
|
||||
Create an image. Options:
|
||||
|
||||
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
|
||||
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
|
||||
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
|
||||
If parent is not a snapshot, it must be a read-only image.
|
||||
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
|
||||
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
|
||||
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
|
||||
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
|
||||
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
|
||||
|
||||
Options:
|
||||
|
||||
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
|
||||
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
|
||||
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
|
||||
By default, the image retains its old encryption key when taking a snapshot.
|
||||
|
||||
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
|
||||
|
||||
@@ -151,6 +164,7 @@ You should resize file system in the image, if present, before shrinking it.
|
||||
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
|
||||
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
|
||||
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
|
||||
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+22
-8
@@ -127,19 +127,32 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
|
||||
|
||||
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
|
||||
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
|
||||
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
|
||||
Создать образ. Опции:
|
||||
|
||||
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
|
||||
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
|
||||
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
|
||||
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
|
||||
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
|
||||
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
|
||||
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
|
||||
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
|
||||
клиентов, если пишущий клиент максимум 1.
|
||||
Создать снимок образа `<image>` (можно использовать любую форму команды).
|
||||
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
|
||||
|
||||
Опции:
|
||||
|
||||
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
|
||||
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
|
||||
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
|
||||
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
|
||||
|
||||
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
|
||||
|
||||
@@ -156,6 +169,7 @@ vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
|
||||
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
|
||||
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
|
||||
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
@@ -262,4 +262,3 @@ Options:
|
||||
| `--logfile <FILE>` | log to the specified file |
|
||||
| `--enforce 1` | enforce permissions at the server side (no by default) |
|
||||
| `--foreground 1` | stay in foreground, do not daemonize |
|
||||
| `--trace` | trace all NFS requests |
|
||||
|
||||
@@ -274,4 +274,3 @@ VitastorFS из GPUDirect.
|
||||
| `--logfile <FILE>` | записывать логи в заданный файл |
|
||||
| `--enforce 1` | проверять права доступа на стороне сервера (по умолчанию нет) |
|
||||
| `--foreground 1` | не уходить в фон после запуска |
|
||||
| `--trace` | логгировать все запросы NFS |
|
||||
|
||||
+23
-8
@@ -18,7 +18,7 @@ class AntiEtcdAdapter
|
||||
cluster = cluster ? (''+(cluster||'')).split(/,+/) : [];
|
||||
cluster = Object.keys(cluster.reduce((a, url) =>
|
||||
{
|
||||
a[url.toLowerCase().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '')] = true;
|
||||
a[url.toLowerCase().replace(/^(https?:\/\/)?(.*?)(\/.*)?$/, (m, m1, m2) => (m1||'http://')+m2)] = true;
|
||||
return a;
|
||||
}, {}));
|
||||
const cfg_port = config.antietcd_port;
|
||||
@@ -26,7 +26,8 @@ class AntiEtcdAdapter
|
||||
is_local['0.0.0.0'] = true;
|
||||
is_local['::'] = true;
|
||||
is_local[''] = true;
|
||||
const selected = cluster.map(s => s.split(':', 2)).filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
|
||||
// split :, 3 -> <schema>:<//ip>:<port>
|
||||
const selected = cluster.map(s => s.split(':', 3)).filter(ip => is_local[ip[1].substr(2)] && (!cfg_port || ip[2] == cfg_port));
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
@@ -35,16 +36,30 @@ class AntiEtcdAdapter
|
||||
else if (selected.length == 1)
|
||||
{
|
||||
const antietcd_config = {
|
||||
ip: selected[0][0],
|
||||
port: selected[0][1],
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
|
||||
ip: selected[0][1].substr(2),
|
||||
port: selected[0][2],
|
||||
cert: config.antietcd_cert,
|
||||
key: config.antietcd_key,
|
||||
ca: config.etcd_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][2]+'.json.gz'),
|
||||
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
|
||||
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
|
||||
node_id: selected[0][1].substr(2)+':'+selected[0][2], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
|
||||
cluster_key: (config.etcd_prefix || '/vitastor'),
|
||||
stale_read: 1,
|
||||
log_level: 1,
|
||||
};
|
||||
if (config.use_auth)
|
||||
{
|
||||
antietcd_config.client_cert_auth = true;
|
||||
antietcd_config.auth_filter = require('./vitastor_auth_filter.js');
|
||||
antietcd_config.peer_ca = config.antietcd_server_ca;
|
||||
if (!config.antietcd_server_ca || config.antietcd_server_ca == config.etcd_ca)
|
||||
{
|
||||
console.error('Secure setup requires separate antietcd_server_ca (for signing antietcd server certificates) and etcd_ca (for signing client certificates)');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
for (const key in config)
|
||||
{
|
||||
if (key.substr(0, 9) === 'antietcd_')
|
||||
@@ -169,7 +184,7 @@ class AntiEtcdAdapter
|
||||
await new Promise(ok => setTimeout(ok, timeout-(Date.now()-prev)));
|
||||
}
|
||||
prev = Date.now();
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body);
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { username: 'root' });
|
||||
if (res.error)
|
||||
{
|
||||
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
|
||||
|
||||
+27
-6
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -15,11 +17,30 @@ class EtcdAdapter
|
||||
this.ws = null;
|
||||
this.ws_alive = false;
|
||||
this.ws_keepalive_timer = null;
|
||||
this.opts = {};
|
||||
}
|
||||
|
||||
parse_config(config)
|
||||
{
|
||||
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
|
||||
if (config.mon_etcd_client_cert || config.etcd_client_cert)
|
||||
{
|
||||
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
|
||||
if (this.opts.cert.substr(0, 5) != '-----')
|
||||
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.mon_etcd_client_key || config.etcd_client_key)
|
||||
{
|
||||
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
|
||||
if (this.opts.key.substr(0, 5) != '-----')
|
||||
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.etcd_ca)
|
||||
{
|
||||
this.opts.ca = config.etcd_ca;
|
||||
if (this.opts.ca.substr(0, 5) != '-----')
|
||||
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
|
||||
}
|
||||
}
|
||||
|
||||
parse_etcd_addresses(addrs)
|
||||
@@ -39,7 +60,7 @@ class EtcdAdapter
|
||||
for (let url of addrs)
|
||||
{
|
||||
let scheme = 'http';
|
||||
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
|
||||
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
|
||||
const slash = url.indexOf('/');
|
||||
const colon = url.indexOf(':');
|
||||
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
|
||||
@@ -130,7 +151,7 @@ class EtcdAdapter
|
||||
}
|
||||
ok(false);
|
||||
}, this.mon.config.etcd_mon_timeout);
|
||||
this.ws = new WebSocket(base+'/watch');
|
||||
this.ws = new WebSocket(base+'/watch', this.opts);
|
||||
this.ws_used_url = cur_addr;
|
||||
const fail = () =>
|
||||
{
|
||||
@@ -272,7 +293,7 @@ class EtcdAdapter
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
}
|
||||
const res = await POST(base+path, body, timeout);
|
||||
const res = await POST(base+path, body, timeout, this.opts);
|
||||
if (this.mon.stopped)
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
@@ -298,7 +319,7 @@ class EtcdAdapter
|
||||
}
|
||||
}
|
||||
|
||||
function POST(url, body, timeout)
|
||||
function POST(url, body, timeout, opts)
|
||||
{
|
||||
return new Promise(ok =>
|
||||
{
|
||||
@@ -310,10 +331,10 @@ function POST(url, body, timeout)
|
||||
req = null;
|
||||
ok({ error: 'timeout' });
|
||||
}, timeout) : null;
|
||||
let req = http.request(url, { method: 'POST', headers: {
|
||||
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body_text.length,
|
||||
} }, (res) =>
|
||||
}, ...(opts||{}) }, (res) =>
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
|
||||
+22
-1
@@ -16,6 +16,7 @@ const etcd_allow = new RegExp('^'+[
|
||||
'config/pools',
|
||||
'config/osd/[1-9]\\d*',
|
||||
'config/pgs', // old name
|
||||
'config/user/.*',
|
||||
'pg/config',
|
||||
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
||||
'osd/state/[1-9]\\d*',
|
||||
@@ -45,7 +46,14 @@ const etcd_tree = {
|
||||
config_path: "/etc/vitastor/vitastor.conf",
|
||||
etcd_prefix: "/vitastor",
|
||||
// etcd connection - configurable online
|
||||
etcd_address: "10.0.115.10:2379/v3",
|
||||
etcd_address: "http://10.0.115.10:2379/v3",
|
||||
etcd_client_cert: "",
|
||||
etcd_client_key: "",
|
||||
osd_etcd_client_cert: "",
|
||||
osd_etcd_client_key: "",
|
||||
mon_etcd_client_cert: "",
|
||||
mon_etcd_client_key: "",
|
||||
etcd_ca: "",
|
||||
// mon
|
||||
etcd_mon_ttl: 5, // min: 1
|
||||
etcd_mon_timeout: 1000, // ms. min: 0
|
||||
@@ -201,6 +209,8 @@ const etcd_tree = {
|
||||
primary_affinity_tags?: 'nvme' | [ 'nvme', ... ],
|
||||
// scrub interval
|
||||
scrub_interval?: '30d',
|
||||
// users allowed to create images in this pool
|
||||
creator_group?: '',
|
||||
},
|
||||
...
|
||||
}, */
|
||||
@@ -217,10 +227,21 @@ const etcd_tree = {
|
||||
parent_id?: <inode_t>,
|
||||
readonly?: boolean,
|
||||
deleted?: boolean,
|
||||
enc_key?: string,
|
||||
owner?: string,
|
||||
owner_group?: string,
|
||||
reader_group?: string,
|
||||
}
|
||||
}
|
||||
}, */
|
||||
inode: {},
|
||||
/* user: {
|
||||
<username>: {
|
||||
type: 'osd'|'mon'|'admin'|'client',
|
||||
groups: string[],
|
||||
},
|
||||
}, */
|
||||
user: {},
|
||||
},
|
||||
osd: {
|
||||
state: {
|
||||
|
||||
@@ -112,10 +112,9 @@ function make_cyclic(pgs, parity_space)
|
||||
{
|
||||
if (parity_space > 1)
|
||||
{
|
||||
for (const id in pgs)
|
||||
for (const pg in pgs)
|
||||
{
|
||||
const pg = pgs[id];
|
||||
for (let i = 1; i < pg.length; i++)
|
||||
for (let i = 1; i < pg.size; i++)
|
||||
{
|
||||
const cyclic = [ ...pg.slice(i), ...pg.slice(0, i) ];
|
||||
pgs['pg_'+cyclic.join('_')] = cyclic;
|
||||
|
||||
+2
-2
@@ -627,7 +627,7 @@ class Mon
|
||||
if (this.state.pg.history[pool_id] &&
|
||||
this.state.pg.history[pool_id][pg])
|
||||
{
|
||||
pg_history[pg-1] = JSON.parse(JSON.stringify(this.state.pg.history[pool_id][pg]));
|
||||
pg_history[pg-1] = this.state.pg.history[pool_id][pg];
|
||||
}
|
||||
}
|
||||
const real_prev_pgs = [];
|
||||
@@ -719,7 +719,7 @@ class Mon
|
||||
this.next_recheck_timer = null;
|
||||
this.next_recheck_at = 0;
|
||||
this.schedule_recheck();
|
||||
}, (this.next_recheck_at-now)*1000);
|
||||
}, now-this.next_recheck_at);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.13",
|
||||
"version": "3.0.9",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ function scale_pg_history(prev_pg_history, prev_pgs, new_pgs)
|
||||
finish_pg_history(merged_history[1]);
|
||||
for (let i = 0; i < new_pg_count; i++)
|
||||
{
|
||||
new_pg_history[i] = JSON.parse(JSON.stringify(merged_history[1]));
|
||||
new_pg_history[i] = { ...merged_history[1] };
|
||||
}
|
||||
}
|
||||
// Mark history keys for removed PGs as removed
|
||||
@@ -102,7 +102,7 @@ function scale_pg_count(prev_pgs, new_pg_count)
|
||||
{
|
||||
for (let i = prev_pgs.length; i < new_pg_count; i++)
|
||||
{
|
||||
prev_pgs[i] = [ ...prev_pgs[i % prev_pgs.length] ];
|
||||
prev_pgs[i] = prev_pgs[i % prev_pgs.length];
|
||||
}
|
||||
}
|
||||
else if (prev_pgs.length > new_pg_count)
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
// 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: osd, mon, admin, client.
|
||||
// osd, mon types should be used by OSDs/monitors.
|
||||
// admin should be used for administrative access from vitastor-cli.
|
||||
// client should be used for regular clients.
|
||||
// - groups, a list of group names the user is included in.
|
||||
// 2. Images.
|
||||
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
|
||||
// - owner (user name)
|
||||
// - owner_group (group name)
|
||||
// - reader_group
|
||||
|
||||
const static_perms = {
|
||||
invalid: {
|
||||
keys: {},
|
||||
prefixes: {},
|
||||
},
|
||||
osd: {
|
||||
keys: { '/pg/config': false },
|
||||
prefixes: { '/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/stats/': false, '/pg/state/': false, '/index/maxid/': false },
|
||||
},
|
||||
};
|
||||
|
||||
const api_perms = {
|
||||
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true },
|
||||
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true },
|
||||
admin: { maintenance_status: true },
|
||||
client: {},
|
||||
};
|
||||
|
||||
class VitastorAuthFilter
|
||||
{
|
||||
constructor(antietcd)
|
||||
{
|
||||
this.cfg = antietcd.cfg;
|
||||
this.antietcd = antietcd;
|
||||
this.prefix = this.cfg.vitastor_prefix || '/vitastor';
|
||||
this.prefix_parts = this.prefix.split('/');
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
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(username)
|
||||
{
|
||||
if (!username)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', username ], true);
|
||||
if (!userInfo)
|
||||
{
|
||||
userInfo = { type: 'client' };
|
||||
}
|
||||
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
|
||||
userInfo.name = 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(username, api/*, data*/)
|
||||
{
|
||||
if (username === 'root')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', username ], true);
|
||||
return userInfo && api_perms[userInfo.type] && api_perms[userInfo.type][api];
|
||||
}
|
||||
|
||||
filter_txn(username, txn)
|
||||
{
|
||||
if (username === 'root')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const userInfo = this._get_user(username);
|
||||
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(username, txn, res)
|
||||
{
|
||||
if (!res.responses || username === 'root')
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(username);
|
||||
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(username, msg)
|
||||
{
|
||||
if (!msg.result || !msg.result.events || username === 'root')
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(username);
|
||||
if (!userInfo)
|
||||
{
|
||||
msg.result.events = [];
|
||||
return;
|
||||
}
|
||||
msg.result.events = msg.result.events.filter(ev => this._check_read(ev.kv, userInfo));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VitastorAuthFilter;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor",
|
||||
"version": "3.0.13",
|
||||
"version": "3.0.9",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.13'
|
||||
VITASTOR_VERSION = '3.0.9'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.13
|
||||
Version: 3.0.9
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.13.el10.tar.gz
|
||||
Source0: vitastor-3.0.9.el10.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
|
||||
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
|
||||
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
|
||||
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.13
|
||||
Version: 3.0.9
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.13.el7.tar.gz
|
||||
Source0: vitastor-3.0.9.el7.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: devtoolset-9-gcc-c++
|
||||
@@ -17,6 +17,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake3
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
|
||||
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
|
||||
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.13
|
||||
Version: 3.0.9
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.13.el8.tar.gz
|
||||
Source0: vitastor-3.0.9.el8.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-toolset-9-gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.13
|
||||
Version: 3.0.9
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.13.el9.tar.gz
|
||||
Source0: vitastor-3.0.9.el9.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
+9
-1
@@ -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.13")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.9")
|
||||
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
|
||||
add_link_options(-fno-omit-frame-pointer)
|
||||
if (${WITH_ASAN})
|
||||
@@ -74,6 +74,14 @@ if (RDMACM_LIBRARIES)
|
||||
add_definitions(-DWITH_RDMACM)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (OPENSSL_FOUND)
|
||||
add_definitions(-DWITH_OPENSSL)
|
||||
endif (OPENSSL_FOUND)
|
||||
|
||||
pkg_check_modules(CARES REQUIRED libcares)
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
|
||||
if (${WITH_SYSTEM_LIBURING})
|
||||
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
|
||||
include_directories(${LIBURING_INCLUDE_DIRS})
|
||||
|
||||
@@ -83,20 +83,23 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
|
||||
}
|
||||
else if (config["data_csum_type"] == "xxh3_32")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
|
||||
}
|
||||
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
|
||||
}
|
||||
csum_block_size = parse_size(config["csum_block_size"]);
|
||||
discard_on_start = config.find("discard_on_start") != config.end() &&
|
||||
(config["discard_on_start"] == "true" || config["discard_on_start"] == "1" || config["discard_on_start"] == "yes");
|
||||
gc_on_start = config.find("gc_on_start") == config.end() ||
|
||||
(config["gc_on_start"] == "true" || config["gc_on_start"] == "1" || config["gc_on_start"] == "yes");
|
||||
skip_double_claim = (config["skip_double_claim"] == "true" || config["skip_double_claim"] == "1" || config["skip_double_claim"] == "yes");
|
||||
min_discard_size = parse_size(config["min_discard_size"]);
|
||||
if (!min_discard_size)
|
||||
min_discard_size = 1024*1024;
|
||||
@@ -517,7 +520,7 @@ void blockstore_disk_t::close_all()
|
||||
|
||||
// Sadly DISCARD only works through ioctl(), but it seems to always block the device queue,
|
||||
// so it's not a big deal that we can only run it synchronously.
|
||||
int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_used)
|
||||
int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_free)
|
||||
{
|
||||
if (mock_mode)
|
||||
{
|
||||
@@ -528,7 +531,7 @@ int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_used)
|
||||
uint64_t discarded = 0;
|
||||
for (; i <= block_count; i++)
|
||||
{
|
||||
if (i >= block_count || is_used(i))
|
||||
if (i >= block_count || is_free(i))
|
||||
{
|
||||
if (i > j && (i-j)*data_block_size >= min_discard_size)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define BLOCKSTORE_CSUM_NONE 0
|
||||
// Lower byte of checksum type is its length
|
||||
#define BLOCKSTORE_CSUM_CRC32C 0x104
|
||||
#define BLOCKSTORE_CSUM_XXH3_32 0x204
|
||||
|
||||
#define MOCK_DATA_FD 1000
|
||||
#define MOCK_META_FD 1001
|
||||
@@ -59,8 +60,6 @@ struct blockstore_disk_t
|
||||
bool discard_on_start = false;
|
||||
// GC on start (new store)
|
||||
bool gc_on_start = true;
|
||||
// Skip double claim conflicts on start (new store, temporary until the bug is found)
|
||||
bool skip_double_claim = false;
|
||||
uint64_t min_discard_size = 1024*1024;
|
||||
uint64_t discard_granularity = 0;
|
||||
|
||||
@@ -83,7 +82,7 @@ struct blockstore_disk_t
|
||||
void calc_lengths(bool skip_meta_check = false);
|
||||
void check_lengths();
|
||||
void close_all();
|
||||
int trim_data(std::function<bool(uint64_t)> is_used);
|
||||
int trim_data(std::function<bool(uint64_t)> is_free);
|
||||
|
||||
inline uint64_t dirty_dyn_size(uint64_t offset, uint64_t len)
|
||||
{
|
||||
|
||||
@@ -174,18 +174,14 @@ bool journal_flusher_co::loop()
|
||||
else if (wait_state == 19) goto resume_19;
|
||||
else if (wait_state == 20) goto resume_20;
|
||||
else if (wait_state == 21) goto resume_21;
|
||||
else if (wait_state == 22) goto resume_22;
|
||||
else if (wait_state == 23) goto resume_23;
|
||||
else if (wait_state == 24) goto resume_24;
|
||||
else if (wait_state == 25) goto resume_25;
|
||||
resume_0:
|
||||
wait_state = 0;
|
||||
wait_count = 0;
|
||||
cur_oid = {};
|
||||
res = bs->heap->get_next_compact(cur_oid);
|
||||
// Advance fsynced_lsn every <journal_trim_interval> intent writes
|
||||
if ((bs->intent_write_counter >= bs->journal_trim_interval) && co_id == 0)
|
||||
{
|
||||
// Advance fsynced_lsn every <journal_trim_interval> intent writes
|
||||
bs->intent_write_counter = 0;
|
||||
resume_17:
|
||||
resume_18:
|
||||
@@ -200,7 +196,6 @@ resume_21:
|
||||
if (res == ENOENT && flusher->force_start > 0 && co_id == 0 &&
|
||||
(!bs->dsk.disable_journal_fsync || !bs->dsk.disable_meta_fsync || !bs->dsk.disable_data_fsync))
|
||||
{
|
||||
// When under pressure, do an additional fsync to force entries to be marked compactable
|
||||
flusher->active_flushers++;
|
||||
resume_14:
|
||||
resume_15:
|
||||
@@ -264,9 +259,11 @@ resume_1:
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE ||
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity)
|
||||
{
|
||||
bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
auto res = bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity && !bs->perfect_csum_update
|
||||
? COPY_BUF_SKIP_CSUM : 0);
|
||||
if (res > 0)
|
||||
copy_count++;
|
||||
}
|
||||
});
|
||||
if (!compact_info.compact_lsn)
|
||||
@@ -276,53 +273,30 @@ resume_1:
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
memcpy(new_ext_bmp, compact_info.clean_wr->get_ext_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
bitmap_copied = true;
|
||||
}
|
||||
if (bs->dsk.csum_block_size && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
memcpy(new_csums, compact_info.clean_wr->get_checksums(bs->heap), bs->dsk.data_block_size/bs->dsk.csum_block_size * (bs->dsk.data_csum_type & 0xFF));
|
||||
for (size_t i = csum_copy.size(); i > 0; i--)
|
||||
{
|
||||
auto wr = csum_copy[i-1];
|
||||
memcpy(new_csums + wr->small().offset/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF),
|
||||
wr->get_checksums(bs->heap), wr->small().len/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF));
|
||||
}
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
flusher->active_flushers++;
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
|
||||
{
|
||||
copy_count++;
|
||||
}
|
||||
}
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
init_fsync_data();
|
||||
}
|
||||
if (compact_info.do_delete)
|
||||
{
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx up to l%ju (delete)\n", cur_oid.inode, cur_oid.stripe, compact_info.compact_lsn);
|
||||
}
|
||||
clean_loc = UINT64_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
memcpy(new_ext_bmp, compact_info.clean_wr->get_ext_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
bitmap_copied = true;
|
||||
}
|
||||
if (bs->dsk.csum_block_size && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
memcpy(new_csums, compact_info.clean_wr->get_checksums(bs->heap), bs->dsk.data_block_size/bs->dsk.csum_block_size * (bs->dsk.data_csum_type & 0xFF));
|
||||
for (size_t i = csum_copy.size(); i > 0; i--)
|
||||
{
|
||||
auto wr = csum_copy[i-1];
|
||||
memcpy(new_csums + wr->small().offset/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF),
|
||||
wr->get_checksums(bs->heap), wr->small().len/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF));
|
||||
}
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
overwrite_start = overwrite_end = 0;
|
||||
if (read_vec.size() > 0)
|
||||
@@ -362,13 +336,6 @@ resume_3:
|
||||
if (res == ENOENT || res == EDOM)
|
||||
{
|
||||
// Abort compaction
|
||||
abort_compact:
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
cur_sync->member_count--;
|
||||
if (cur_sync->member_count > 0)
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
@@ -382,7 +349,10 @@ resume_4:
|
||||
if (res == ENOENT)
|
||||
{
|
||||
// Abort compaction
|
||||
goto abort_compact;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
goto resume_0;
|
||||
}
|
||||
if (res == EAGAIN)
|
||||
{
|
||||
@@ -411,14 +381,14 @@ resume_9:
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
{
|
||||
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED) ||
|
||||
(read_vec[i].copy_flags & COPY_BUF_PADDED)) // FIXME Shit, simplify these flags
|
||||
{
|
||||
assert(read_vec[i].buf);
|
||||
await_sqe(10);
|
||||
data->iov = (struct iovec){ read_vec[i].buf + (read_vec[i].copy_flags & COPY_BUF_PADDED
|
||||
? read_vec[i].offset - read_vec[i].disk_offset : 0), (size_t)read_vec[i].len };
|
||||
data->callback = simple_callback_w;
|
||||
assert(clean_loc + read_vec[i].offset + data->iov.iov_len <= bs->dsk.block_count*bs->dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + read_vec[i].offset);
|
||||
wait_count++;
|
||||
}
|
||||
@@ -429,17 +399,6 @@ resume_11:
|
||||
wait_state = 11;
|
||||
return false;
|
||||
}
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
resume_22:
|
||||
resume_23:
|
||||
resume_24:
|
||||
resume_25:
|
||||
if (!fsync_data(22))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Lock is only needed to prevent freeing the big_write because we overwrite it...
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
// Mark the object compacted, but don't free and remove small_writes
|
||||
@@ -449,14 +408,12 @@ resume_25:
|
||||
if (!cur_obj)
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
if (!calc_block_checksums())
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -465,7 +422,6 @@ resume_25:
|
||||
if (res == EBUSY)
|
||||
{
|
||||
// Abort compaction, object is already overwritten by something else
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -630,13 +586,13 @@ int journal_flusher_co::check_and_punch_checksums()
|
||||
bs->heap->calc_block_checksums((uint32_t*)(new_csums+csum_off), vec.buf, punch_bmp, vec.offset, vec.offset+vec.len, true, NULL);
|
||||
}
|
||||
}
|
||||
// Modified, we should punch_holes and then write the block to disk
|
||||
// Modified, we should add_punch_holes and then write the block to disk
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::calc_block_checksums()
|
||||
{
|
||||
if (bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity || compact_info.do_delete)
|
||||
if (bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -743,67 +699,6 @@ resume_1:
|
||||
return true;
|
||||
}
|
||||
|
||||
void journal_flusher_co::init_fsync_data()
|
||||
{
|
||||
cur_sync = flusher->data_syncs.begin();
|
||||
if (cur_sync == flusher->data_syncs.end() || cur_sync->ready_count > 0)
|
||||
{
|
||||
cur_sync = flusher->data_syncs.emplace(cur_sync);
|
||||
}
|
||||
cur_sync->member_count++;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::fsync_data(int wait_base)
|
||||
{
|
||||
if (wait_state == wait_base)
|
||||
goto resume_0;
|
||||
else if (wait_state == wait_base+1)
|
||||
goto resume_1;
|
||||
else if (wait_state == wait_base+2)
|
||||
goto resume_2;
|
||||
else if (wait_state == wait_base+3)
|
||||
goto resume_3;
|
||||
cur_sync->ready_count++;
|
||||
resume_0:
|
||||
if (cur_sync->ready_count < cur_sync->member_count)
|
||||
{
|
||||
wait_state = wait_base;
|
||||
return false;
|
||||
}
|
||||
if (!cur_sync->sent)
|
||||
{
|
||||
// Sync batch is ready. Do it.
|
||||
await_sqe(1);
|
||||
data->iov = { 0 };
|
||||
data->callback = simple_callback_w;
|
||||
io_uring_prep_fsync(sqe, bs->dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
cur_sync->sent = true;
|
||||
wait_count++;
|
||||
resume_2:
|
||||
if (wait_count > 0)
|
||||
{
|
||||
wait_state = wait_base+2;
|
||||
return false;
|
||||
}
|
||||
cur_sync->done = true;
|
||||
// Wake up other flushers
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
resume_3:
|
||||
if (!cur_sync->done)
|
||||
{
|
||||
wait_state = wait_base+3;
|
||||
return false;
|
||||
}
|
||||
cur_sync->done_count++;
|
||||
if (cur_sync->done_count >= cur_sync->member_count)
|
||||
{
|
||||
flusher->data_syncs.erase(cur_sync);
|
||||
cur_sync = flusher->data_syncs.end();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::fsync_meta(int wait_base)
|
||||
{
|
||||
if (wait_state == wait_base) goto resume_0;
|
||||
|
||||
@@ -25,15 +25,6 @@ struct flusher_meta_write_t
|
||||
std::map<uint64_t, meta_sector_t>::iterator it;
|
||||
};
|
||||
|
||||
struct flusher_data_sync_t
|
||||
{
|
||||
int member_count = 0;
|
||||
int ready_count = 0;
|
||||
int done_count = 0;
|
||||
bool sent = false;
|
||||
bool done = false;
|
||||
};
|
||||
|
||||
class journal_flusher_t;
|
||||
|
||||
// Journal flusher coroutine
|
||||
@@ -67,7 +58,6 @@ class journal_flusher_co
|
||||
int i, res;
|
||||
bool read_to_fill_incomplete;
|
||||
int copy_count;
|
||||
std::list<flusher_data_sync_t>::iterator cur_sync;
|
||||
|
||||
friend class journal_flusher_t;
|
||||
|
||||
@@ -78,8 +68,6 @@ class journal_flusher_co
|
||||
bool calc_block_checksums();
|
||||
bool write_meta_block(int wait_base);
|
||||
bool read_buffered(int wait_base);
|
||||
void init_fsync_data();
|
||||
bool fsync_data(int wait_base);
|
||||
bool fsync_meta(int wait_base);
|
||||
bool fsync_buffer(int wait_base);
|
||||
bool trim_lsn(int wait_base);
|
||||
@@ -100,7 +88,6 @@ class journal_flusher_t
|
||||
|
||||
robin_hood::unordered_flat_set<object_id> flushing;
|
||||
int active_flushers = 0;
|
||||
std::list<flusher_data_sync_t> data_syncs;
|
||||
int wanting_meta_fsync = 0;
|
||||
bool fsyncing_meta = false;
|
||||
int syncing_buffer = 0;
|
||||
|
||||
+284
-369
@@ -12,6 +12,7 @@
|
||||
#include "blockstore_heap.h"
|
||||
#include "../util/allocator.h"
|
||||
#include "../util/crc32c.h"
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/malloc_or_die.h"
|
||||
|
||||
#define BS_HEAP_FREE_MVCC 1
|
||||
@@ -28,7 +29,6 @@
|
||||
|
||||
#define IMAP_MALLOC_LOW_BITS ((size_t)0x0F)
|
||||
#define IMAP_MAX_LOW 16
|
||||
#define POSTPONE_INSERT_COUNT 10
|
||||
|
||||
#define list_item_overhead(a) (((a) + sizeof(heap_list_item_t) - sizeof(heap_entry_t) + sizeof(void*) + 15) & ~15)
|
||||
|
||||
@@ -65,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->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_big_intent_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
|
||||
{
|
||||
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
|
||||
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
|
||||
}
|
||||
|
||||
@@ -92,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->data_csum_type)
|
||||
if (!dsk->csum_block_size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -127,26 +127,26 @@ uint32_t heap_entry_t::get_size(blockstore_heap_t *heap)
|
||||
return heap->get_simple_entry_size();
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_overwrite() const
|
||||
bool heap_entry_t::is_overwrite()
|
||||
{
|
||||
return ((entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_BIG_WRITE|BS_HEAP_STABLE) ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_BIG_INTENT|BS_HEAP_STABLE) ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_compactable() const
|
||||
bool heap_entry_t::is_compactable()
|
||||
{
|
||||
return !is_overwrite() && (entry_type & BS_HEAP_STABLE) ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == BS_HEAP_COMMIT ||
|
||||
(entry_type & ~BS_HEAP_GARBAGE) == BS_HEAP_ROLLBACK;
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_before(const heap_entry_t *other) const
|
||||
bool heap_entry_t::is_before(heap_entry_t *other)
|
||||
{
|
||||
return lsn < other->lsn || lsn == other->lsn && !is_overwrite() && other->is_overwrite();
|
||||
}
|
||||
|
||||
bool heap_entry_t::is_garbage() const
|
||||
bool heap_entry_t::is_garbage()
|
||||
{
|
||||
return (entry_type & BS_HEAP_GARBAGE);
|
||||
}
|
||||
@@ -215,15 +215,24 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
|
||||
big().block_num = location / heap->dsk->data_block_size;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_crc32c()
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
|
||||
{
|
||||
auto old_crc32c = crc32c;
|
||||
crc32c = 0;
|
||||
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
|
||||
crc32c = old_crc32c;
|
||||
auto old_checksum = checksum;
|
||||
checksum = 0;
|
||||
uint32_t res = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
res = (uint32_t)XXH3_64bits(this, size);
|
||||
else
|
||||
res = ::crc32c(0, (uint8_t*)this, size);
|
||||
checksum = old_checksum;
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
|
||||
{
|
||||
return calc_checksum(heap->dsk);
|
||||
}
|
||||
|
||||
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
|
||||
{
|
||||
uint64_t pg_num = 0;
|
||||
@@ -381,12 +390,12 @@ corrupted_object:
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify crc
|
||||
uint32_t expected_crc32c = wr->calc_crc32c();
|
||||
if (wr->crc32c != expected_crc32c)
|
||||
uint32_t expected_checksum = wr->calc_checksum(this);
|
||||
if (wr->checksum != expected_checksum)
|
||||
{
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). ",
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn,
|
||||
block_num, block_offset, expected_crc32c, wr->crc32c);
|
||||
block_num, block_offset, expected_checksum, wr->checksum);
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify offset & len
|
||||
@@ -409,13 +418,6 @@ corrupted_object:
|
||||
wr->inode, wr->stripe, wr->version, wr->big_intent().offset, wr->big_intent().len);
|
||||
goto corrupted_object;
|
||||
}
|
||||
if ((wr->type() == BS_HEAP_BIG_INTENT || wr->type() == BS_HEAP_BIG_WRITE) &&
|
||||
wr->big().block_num >= dsk->block_count)
|
||||
{
|
||||
fprintf(stderr, "Error: big_write or big_intent entry %jx:%jx v%ju block_num is too large: %u > %lu. Metadata is incompatible with current parameters. ",
|
||||
wr->inode, wr->stripe, wr->version, wr->big_intent().block_num, dsk->block_count);
|
||||
goto corrupted_object;
|
||||
}
|
||||
handle_write(block_num, wr);
|
||||
block_offset += wr->size;
|
||||
}
|
||||
@@ -442,7 +444,7 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
|
||||
next_lsn = wr->lsn;
|
||||
}
|
||||
entries_loaded++;
|
||||
insert_list_items(&li, 1, true);
|
||||
loaded_list_items.push_back(li);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
if (!inf.entries.size())
|
||||
@@ -548,26 +550,18 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
|
||||
|
||||
void blockstore_heap_t::finish_load()
|
||||
{
|
||||
if (postponed_items.size())
|
||||
if (loaded_list_items.size())
|
||||
{
|
||||
// Sort "postponed" items and load in batches
|
||||
std::sort(postponed_items.begin(), postponed_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
|
||||
// Sort everything and load in correct order
|
||||
std::sort(loaded_list_items.begin(), loaded_list_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
|
||||
{
|
||||
return a->entry.inode < b->entry.inode || a->entry.inode == b->entry.inode &&
|
||||
(a->entry.stripe < b->entry.stripe || a->entry.stripe == b->entry.stripe &&
|
||||
!a->entry.is_before(&b->entry)); // object ASC, lsn DESC
|
||||
return a->entry.lsn < b->entry.lsn;
|
||||
});
|
||||
size_t s = 0, e, n = postponed_items.size();
|
||||
for (e = 1; e <= n; e++)
|
||||
for (auto & li: loaded_list_items)
|
||||
{
|
||||
if (e >= n || postponed_items[e]->entry.inode != postponed_items[s]->entry.inode ||
|
||||
postponed_items[e]->entry.stripe != postponed_items[s]->entry.stripe)
|
||||
{
|
||||
insert_list_items(postponed_items.data()+s, e-s, false);
|
||||
s = e;
|
||||
}
|
||||
insert_list_item(li);
|
||||
}
|
||||
postponed_items.clear();
|
||||
loaded_list_items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,11 +574,26 @@ void blockstore_heap_t::fill_recheck_queue()
|
||||
inode_map_iterate(ip.second, [&](heap_list_item_t *li)
|
||||
{
|
||||
auto obj = &li->entry;
|
||||
// Recheck only the latest intent_write (if after completed_lsn) or a series of small_writes
|
||||
if ((obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
|
||||
&& obj->lsn > completed_lsn || obj->type() == BS_HEAP_SMALL_WRITE)
|
||||
// Add object to recheck queue
|
||||
if (obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
recheck_queue.push_back(obj);
|
||||
// Recheck only the latest intent_write
|
||||
if (obj->lsn > completed_lsn)
|
||||
{
|
||||
// Do not recheck if it's already marked as completed in the superblock
|
||||
recheck_queue.push_back(obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Or recheck a series of small_writes
|
||||
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
{
|
||||
if (wr->small().len > 0)
|
||||
{
|
||||
recheck_queue.push_back(wr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -594,11 +603,6 @@ void blockstore_heap_t::fill_recheck_queue()
|
||||
int blockstore_heap_t::mark_used_blocks()
|
||||
{
|
||||
int res = 0;
|
||||
std::vector<heap_list_item_t*> used_by;
|
||||
if (dsk->skip_double_claim)
|
||||
{
|
||||
used_by.resize(dsk->block_count);
|
||||
}
|
||||
for (auto & pgp: block_index)
|
||||
{
|
||||
for (auto & ip: pgp.second)
|
||||
@@ -652,34 +656,10 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
{
|
||||
if (is_data_used(wr->big_location(this)))
|
||||
{
|
||||
if (dsk->skip_double_claim)
|
||||
{
|
||||
// There is a BUG currently:
|
||||
// Sometimes (under unknown conditions) deletion entries are removed from the disk
|
||||
// earlier than previous big_writes.
|
||||
// Until it's fixed, we provide a way to ignore such objects on start.
|
||||
auto prev_li = used_by[wr->big().block_num];
|
||||
assert(prev_li);
|
||||
// Newer LSN must be trusted. Remove the older object.
|
||||
fprintf(stderr, "Block %u is double-claimed by entries %jx:%jx l%ju and %jx:%jx l%ju\n",
|
||||
wr->big().block_num, prev_li->entry.inode, prev_li->entry.stripe, prev_li->entry.lsn, wr->inode, wr->stripe, wr->lsn);
|
||||
if (init_erase_double_claim(prev_li, li))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: double-claimed data block %u, second time by %jx:%jx l%ju\n",
|
||||
wr->big().block_num, wr->inode, wr->stripe, wr->lsn);
|
||||
res = EDOM;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dsk->skip_double_claim)
|
||||
{
|
||||
// Record the object which uses the data block
|
||||
used_by[wr->big().block_num] = li;
|
||||
fprintf(stderr, "Error: double-claimed data block %u, second time by %jx:%jx l%ju\n",
|
||||
wr->big().block_num, wr->inode, wr->stripe, wr->lsn);
|
||||
res = EDOM;
|
||||
return;
|
||||
}
|
||||
use_data(wr->inode, wr->big_location(this));
|
||||
}
|
||||
@@ -703,116 +683,6 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
return res;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::init_free_bad_entry(heap_entry_t *wr)
|
||||
{
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE)
|
||||
{
|
||||
free_buffer_area(wr->inode, wr->small().location, wr->small().len);
|
||||
}
|
||||
else if (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
free_data(wr->inode, wr->big_location(this));
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::init_erase_bad_entry(heap_list_item_t *li)
|
||||
{
|
||||
modify_alloc(li->block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
for (size_t i = 0; i < inf.entries.size(); i++)
|
||||
{
|
||||
if (inf.entries[i] == li)
|
||||
{
|
||||
inf.entries.erase(inf.entries.begin()+i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
inf.used_space -= li->entry.size;
|
||||
inf.garbage_space -= (li->entry.is_garbage() ? li->entry.size : 0);
|
||||
});
|
||||
recheck_modified_blocks.insert(li->block_num);
|
||||
unlink_list_item(li);
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li)
|
||||
{
|
||||
bool erase_prev = false;
|
||||
bool erase_cur = false;
|
||||
if (prev_li->entry.lsn < cur_li->entry.lsn)
|
||||
{
|
||||
erase_prev = true;
|
||||
auto latest_li = prev_li;
|
||||
while (latest_li->next)
|
||||
{
|
||||
latest_li = latest_li->next;
|
||||
}
|
||||
if (latest_li->entry.lsn >= cur_li->entry.lsn)
|
||||
{
|
||||
// LSN ranges intersect, erase both
|
||||
erase_cur = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
erase_cur = true;
|
||||
auto latest_li = cur_li;
|
||||
while (latest_li->next)
|
||||
{
|
||||
latest_li = latest_li->next;
|
||||
}
|
||||
if ((latest_li->entry.inode != prev_li->entry.inode ||
|
||||
latest_li->entry.stripe != prev_li->entry.stripe) &&
|
||||
latest_li->entry.lsn >= prev_li->entry.lsn)
|
||||
{
|
||||
// LSN ranges intersect, erase both
|
||||
erase_prev = true;
|
||||
}
|
||||
}
|
||||
if (erase_prev)
|
||||
{
|
||||
fprintf(stderr, "Erasing object %jx:%jx due to double-claim\n", prev_li->entry.inode, prev_li->entry.stripe);
|
||||
auto erase_li = prev_li;
|
||||
while (erase_li->next)
|
||||
{
|
||||
erase_li = erase_li->next;
|
||||
}
|
||||
bool overwritten = false;
|
||||
while (erase_li)
|
||||
{
|
||||
auto prev_erase_li = erase_li->prev;
|
||||
if (!overwritten)
|
||||
{
|
||||
init_free_bad_entry(&erase_li->entry);
|
||||
overwritten = erase_li->entry.is_overwrite();
|
||||
}
|
||||
init_erase_bad_entry(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
if (erase_cur)
|
||||
{
|
||||
fprintf(stderr, "Erasing object %jx:%jx due to double-claim\n", cur_li->entry.inode, cur_li->entry.stripe);
|
||||
auto erase_li = cur_li->next;
|
||||
while (erase_li)
|
||||
{
|
||||
// Only newer entries are marked as used
|
||||
auto next_erase_li = erase_li->next;
|
||||
init_free_bad_entry(&erase_li->entry);
|
||||
init_erase_bad_entry(erase_li);
|
||||
erase_li = next_erase_li;
|
||||
}
|
||||
erase_li = cur_li;
|
||||
// Older ones are not
|
||||
while (erase_li)
|
||||
{
|
||||
auto prev_erase_li = erase_li->prev;
|
||||
init_erase_bad_entry(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
return erase_cur;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_full_gc()
|
||||
{
|
||||
uint32_t block_num = 0;
|
||||
@@ -853,98 +723,80 @@ void blockstore_heap_t::recheck_full_gc()
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr)
|
||||
void blockstore_heap_t::recheck_buffer(heap_entry_t *cwr, uint8_t *buf)
|
||||
{
|
||||
// write entry is invalid, erase it and all newer entries
|
||||
int bad_count = 1;
|
||||
for (auto wr = obj; wr && wr != bad_wr; wr = prev(wr))
|
||||
auto free_entry = [&](heap_list_item_t *li)
|
||||
{
|
||||
bad_count++;
|
||||
}
|
||||
auto prev_wr = prev(bad_wr);
|
||||
if (prev_wr)
|
||||
{
|
||||
fprintf(stderr, "Notice: %u unfinished %s to %jx:%jx v%ju since good lsn %ju, rolling back\n",
|
||||
bad_count, bad_count > 1 ? "writes" : "write", obj->inode, obj->stripe, obj->version, prev_wr->lsn);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Notice: the whole object %jx:%jx only has unfinished writes, rolling back\n", obj->inode, obj->stripe);
|
||||
}
|
||||
auto li = list_item(obj);
|
||||
while (li && prev_wr != &li->entry)
|
||||
{
|
||||
auto prev = li->prev;
|
||||
assert(li->entry.type() == bad_wr->type());
|
||||
init_erase_bad_entry(li);
|
||||
li = prev;
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::recheck_start_reads(heap_recheck_state_t *st)
|
||||
{
|
||||
if (st->sent_reads >= st->total_reads)
|
||||
return;
|
||||
while (recheck_in_progress < recheck_queue_depth)
|
||||
{
|
||||
auto wr = st->next_wr;
|
||||
st->next_wr = prev(st->next_wr);
|
||||
uint64_t loc = 0, len = 0;
|
||||
bool from_data = false;
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE)
|
||||
uint32_t block_num = li->block_num;
|
||||
auto wr_size = li->entry.size;
|
||||
if (li->entry.is_garbage())
|
||||
{
|
||||
loc = wr->small().location;
|
||||
len = wr->small().len;
|
||||
garbage_entries--;
|
||||
garbage_memory -= list_item_overhead(wr_size);
|
||||
}
|
||||
else if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
live_entries--;
|
||||
live_memory -= list_item_overhead(wr_size);
|
||||
free(li);
|
||||
modify_alloc(block_num, [&](heap_block_info_t & inf)
|
||||
{
|
||||
auto & bi = wr->big_intent();
|
||||
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
|
||||
len = bi.len;
|
||||
from_data = true;
|
||||
inf.used_space -= wr_size;
|
||||
bool found = false;
|
||||
for (auto it = inf.entries.begin(); it != inf.entries.end(); it++)
|
||||
{
|
||||
if (*it == li)
|
||||
{
|
||||
found = true;
|
||||
inf.entries.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(found);
|
||||
});
|
||||
recheck_modified_blocks.insert(block_num);
|
||||
};
|
||||
if (cwr->is_garbage())
|
||||
{
|
||||
// already freed after rechecking one of the previous small_write entries
|
||||
free_entry(list_item(cwr));
|
||||
}
|
||||
else if (!calc_checksums(cwr, buf, false))
|
||||
{
|
||||
// write entry is invalid, erase it and mark newer entries with garbage bit
|
||||
auto & pg_idx = block_index[get_pg_id(cwr->inode, cwr->stripe)];
|
||||
auto & inode_idx = pg_idx[cwr->inode];
|
||||
heap_inode_map_t::iterator li_it;
|
||||
heap_list_item_t *li = NULL;
|
||||
inode_map_get(inode_idx, li_it, li, cwr->stripe);
|
||||
int rolled_back = 1;
|
||||
while (li && cwr != &li->entry)
|
||||
{
|
||||
assert(li->entry.entry_type == cwr->entry_type);
|
||||
auto prev = li->prev;
|
||||
li->next = li->prev = NULL;
|
||||
if (!li->entry.is_garbage())
|
||||
{
|
||||
garbage_entries++;
|
||||
garbage_memory += list_item_overhead(li->entry.size);
|
||||
li->entry.set_garbage();
|
||||
}
|
||||
li = prev;
|
||||
rolled_back++;
|
||||
}
|
||||
assert(li);
|
||||
if (li->prev)
|
||||
{
|
||||
fprintf(stderr, "Notice: %u unfinished %s to %jx:%jx v%ju since lsn %ju, rolling back\n",
|
||||
rolled_back, rolled_back > 1 ? "writes" : "write", cwr->inode, cwr->stripe, li->prev->entry.version, li->entry.lsn);
|
||||
inode_map_replace(inode_idx, li_it, li->prev);
|
||||
li->prev->next = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(wr->type() == BS_HEAP_INTENT_WRITE);
|
||||
auto prev_wr = prev(wr);
|
||||
while (prev_wr && prev_wr->entry_type == wr->entry_type)
|
||||
{
|
||||
// Skip other intent_writes
|
||||
prev_wr = prev(prev_wr);
|
||||
}
|
||||
if (!prev_wr || prev_wr->entry_type != (BS_HEAP_BIG_WRITE | (wr->entry_type & BS_HEAP_STABLE)) &&
|
||||
prev_wr->entry_type != (BS_HEAP_BIG_INTENT | (wr->entry_type & BS_HEAP_STABLE)))
|
||||
{
|
||||
fprintf(stderr, "Error: intent_write entry %jx:%jx v%ju l%ju is not written over a big_write\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn);
|
||||
exit(1);
|
||||
}
|
||||
loc = wr->small().offset + prev_wr->big_location(this);
|
||||
len = wr->small().len;
|
||||
from_data = true;
|
||||
fprintf(stderr, "Notice: the whole object %jx:%jx only has unfinished writes, rolling back\n",
|
||||
cwr->inode, cwr->stripe);
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, li);
|
||||
}
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
|
||||
st->sent_reads++;
|
||||
recheck_in_progress++;
|
||||
recheck_pending_reads--;
|
||||
bool is_last = st->sent_reads >= st->total_reads;
|
||||
recheck_cb(from_data, loc, len, buf, [this, st, wr, buf]()
|
||||
{
|
||||
st->checked_reads++;
|
||||
if (!calc_checksums(wr, buf, false))
|
||||
st->bad_wr = !st->bad_wr || st->bad_wr->lsn > wr->lsn ? wr : st->bad_wr;
|
||||
if (st->checked_reads >= st->total_reads)
|
||||
{
|
||||
if (st->bad_wr)
|
||||
recheck_drop_entries(st->obj, st->bad_wr);
|
||||
recheck_states.erase(st->obj);
|
||||
}
|
||||
free(buf);
|
||||
recheck_in_progress--;
|
||||
recheck_small_writes(NULL, 0);
|
||||
});
|
||||
if (is_last)
|
||||
break;
|
||||
free_entry(li);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,47 +819,70 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
|
||||
recheck_queue_depth = queue_depth;
|
||||
}
|
||||
in_recheck = true;
|
||||
while (recheck_pending_reads > 0 && recheck_in_progress < recheck_queue_depth)
|
||||
{
|
||||
for (auto & sp: recheck_states)
|
||||
recheck_start_reads(&sp.second);
|
||||
}
|
||||
while (recheck_queue.size() > 0 && recheck_in_progress < recheck_queue_depth)
|
||||
{
|
||||
heap_entry_t *obj = recheck_queue.front();
|
||||
heap_entry_t *wr = recheck_queue.front();
|
||||
recheck_queue.pop_front();
|
||||
if (obj->type() == BS_HEAP_SMALL_WRITE && buffer_area)
|
||||
bool from_data = false;
|
||||
uint64_t loc = 0;
|
||||
uint32_t len = 0;
|
||||
if (wr->type() == BS_HEAP_INTENT_WRITE)
|
||||
{
|
||||
// Check this object synchronously
|
||||
heap_entry_t *bad_wr = NULL;
|
||||
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
auto prev_wr = prev(wr);
|
||||
while (prev_wr && prev_wr->entry_type == wr->entry_type)
|
||||
{
|
||||
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in buffer area\n",
|
||||
wr->inode, wr->stripe, wr->lsn, wr->small().len, wr->small().location);
|
||||
if (!calc_checksums(wr, buffer_area + wr->small().location, false))
|
||||
bad_wr = wr;
|
||||
// Skip other intent_writes
|
||||
prev_wr = prev(prev_wr);
|
||||
}
|
||||
if (bad_wr)
|
||||
recheck_drop_entries(obj, bad_wr);
|
||||
if (!prev_wr || prev_wr->entry_type != (BS_HEAP_BIG_WRITE | (wr->entry_type & BS_HEAP_STABLE)) &&
|
||||
prev_wr->entry_type != (BS_HEAP_BIG_INTENT | (wr->entry_type & BS_HEAP_STABLE)))
|
||||
{
|
||||
fprintf(stderr, "Error: intent_write entry %jx:%jx v%ju l%ju is not written over a big_write\n",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn);
|
||||
exit(1);
|
||||
}
|
||||
loc = wr->small().offset + prev_wr->big_location(this);
|
||||
len = wr->small().len;
|
||||
from_data = true;
|
||||
}
|
||||
else if (wr->type() == BS_HEAP_BIG_INTENT)
|
||||
{
|
||||
auto & bi = wr->big_intent();
|
||||
loc = (uint64_t)bi.block_num * dsk->data_block_size + bi.offset;
|
||||
len = bi.len;
|
||||
from_data = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recheck will be asynchronous. Create state and start it
|
||||
auto & st = recheck_states[obj];
|
||||
st.obj = obj;
|
||||
st.next_wr = obj;
|
||||
st.total_reads = 1;
|
||||
if (obj->type() == BS_HEAP_SMALL_WRITE)
|
||||
for (auto wr = prev(obj); wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
|
||||
st.total_reads++;
|
||||
recheck_pending_reads += st.total_reads;
|
||||
recheck_start_reads(&st);
|
||||
assert(wr->type() == BS_HEAP_SMALL_WRITE);
|
||||
loc = wr->small().location;
|
||||
len = wr->small().len;
|
||||
}
|
||||
if (log_level > 5)
|
||||
{
|
||||
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in %s area\n",
|
||||
wr->inode, wr->stripe, wr->lsn, len, loc, from_data ? "data" : "buffer");
|
||||
}
|
||||
if (!from_data && buffer_area)
|
||||
{
|
||||
recheck_buffer(wr, buffer_area+loc);
|
||||
}
|
||||
else
|
||||
{
|
||||
recheck_in_progress++;
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
|
||||
recheck_cb(from_data, loc, len, buf, [this, wr, buf]()
|
||||
{
|
||||
recheck_buffer(wr, buf);
|
||||
free(buf);
|
||||
recheck_in_progress--;
|
||||
recheck_small_writes(NULL, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
in_recheck = false;
|
||||
if (!recheck_queue.size() && !recheck_in_progress)
|
||||
{
|
||||
assert(!recheck_states.size());
|
||||
auto cb = std::move(recheck_cb);
|
||||
recheck_queue_depth = 0;
|
||||
if (cb)
|
||||
@@ -1069,7 +944,11 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
|
||||
len = wr->big_intent().len;
|
||||
else
|
||||
assert(0);
|
||||
uint32_t real_csum = crc32c(0, data, len);
|
||||
uint32_t real_csum = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
real_csum = (uint32_t)XXH3_64bits(data, len);
|
||||
else
|
||||
real_csum = crc32c(0, data, len);
|
||||
if (set)
|
||||
{
|
||||
*wr_csum = real_csum;
|
||||
@@ -1119,11 +998,26 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
|
||||
return prev_crc;
|
||||
}
|
||||
|
||||
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
|
||||
{
|
||||
uint32_t cur_len = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
uint8_t *data = next(pos, cur_len);
|
||||
assert(data);
|
||||
cur_len = (cur_len < size ? cur_len : size);
|
||||
XXH3_64bits_update(xxh3_state, data, cur_len);
|
||||
pos += cur_len;
|
||||
size -= cur_len;
|
||||
}
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
|
||||
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
|
||||
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
|
||||
{
|
||||
bool res = true;
|
||||
XXH3_state_t* xxh3_state = NULL;
|
||||
uint32_t pos = start;
|
||||
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
|
||||
uint32_t block_crc = 0;
|
||||
@@ -1140,42 +1034,89 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
|
||||
pos += dsk->bitmap_granularity;
|
||||
// zero padding at the beginning or at the end of the block is not counted
|
||||
if (pos > prev && prev > 0 && pos < block_end)
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
{
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
uint32_t zeropad = pos-prev;
|
||||
while (zeropad > 0)
|
||||
{
|
||||
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
|
||||
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
|
||||
zeropad -= zerolen;
|
||||
}
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
}
|
||||
prev = pos;
|
||||
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
if (pos > prev)
|
||||
{
|
||||
isset = true;
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, prev, pos-prev);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
}
|
||||
prev = pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
pos = (end > block_end ? block_end : end);
|
||||
isset = true;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
if (set)
|
||||
{
|
||||
*block_csums = block_crc;
|
||||
}
|
||||
else if (isset && block_crc != *block_csums)
|
||||
{
|
||||
res = false;
|
||||
if (bad_block_cb)
|
||||
{
|
||||
bad_block_cb(blk_start, *block_csums, block_crc);
|
||||
res = false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
block_end += dsk->csum_block_size;
|
||||
block_crc = 0;
|
||||
block_csums++;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_freeState(xxh3_state);
|
||||
xxh3_state = NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1463,51 +1404,43 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blockstore_heap_t::insert_list_items(heap_list_item_t** v, size_t count, bool postpone)
|
||||
void blockstore_heap_t::insert_list_item(heap_list_item_t *li)
|
||||
{
|
||||
auto wr = &v[0]->entry;
|
||||
auto & inode_idx = block_index[get_pg_id(wr->inode, wr->stripe)][wr->inode];
|
||||
auto & inode_idx = block_index[get_pg_id(li->entry.inode, li->entry.stripe)][li->entry.inode];
|
||||
heap_inode_map_t::iterator li_it;
|
||||
heap_list_item_t *old_head = NULL;
|
||||
if (inode_idx)
|
||||
inode_map_get(inode_idx, li_it, old_head, wr->stripe);
|
||||
heap_list_item_t *next_li = NULL;
|
||||
heap_list_item_t *prev_li = old_head;
|
||||
int skips = 0;
|
||||
// Merge entry array and inode_idx linked list (both sorted in newest first order)
|
||||
for (size_t i = 0; i < count; i++)
|
||||
inode_map_get(inode_idx, li_it, old_head, li->entry.stripe);
|
||||
if (old_head && !old_head->entry.is_before(&li->entry))
|
||||
{
|
||||
// BIG_WRITE may be inserted into the middle of the sequence during compaction
|
||||
// and it overrides SMALL_WRITEs and COMMITs with the same LSN
|
||||
// However, all entries of other types (say DELETE) override previous ones
|
||||
auto li = v[i];
|
||||
auto next_li = old_head;
|
||||
auto prev_li = old_head->prev;
|
||||
while (prev_li && !prev_li->entry.is_before(&li->entry))
|
||||
{
|
||||
next_li = prev_li;
|
||||
prev_li = prev_li->prev;
|
||||
skips++;
|
||||
}
|
||||
if (postpone && skips > POSTPONE_INSERT_COUNT)
|
||||
{
|
||||
postponed_items.push_back(li);
|
||||
return;
|
||||
}
|
||||
if (next_li == NULL)
|
||||
{
|
||||
// Replace the latest entry pointer
|
||||
if (old_head)
|
||||
inode_map_replace(inode_idx, li_it, li);
|
||||
else
|
||||
inode_map_put(inode_idx, li);
|
||||
}
|
||||
// Insert <li> between <next_li> and <prev_li>
|
||||
li->next = next_li;
|
||||
if (next_li)
|
||||
next_li->prev = li;
|
||||
li->prev = prev_li;
|
||||
if (prev_li)
|
||||
prev_li->next = li;
|
||||
next_li = li;
|
||||
next_li->prev = li;
|
||||
li->next = next_li;
|
||||
}
|
||||
else
|
||||
{
|
||||
li->prev = old_head;
|
||||
li->next = NULL;
|
||||
if (old_head)
|
||||
{
|
||||
old_head->next = li;
|
||||
inode_map_replace(inode_idx, li_it, li);
|
||||
}
|
||||
else
|
||||
inode_map_put(inode_idx, li);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1539,10 +1472,10 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
|
||||
(explicit_complete ? HEAP_INFLIGHT_EXPLICIT : 0) |
|
||||
(new_wr->is_overwrite() ? HEAP_INFLIGHT_COMPACTED : 0) |
|
||||
(new_wr->is_compactable() ? HEAP_INFLIGHT_COMPACTABLE : 0));
|
||||
insert_list_items(&li, 1, false);
|
||||
insert_list_item(li);
|
||||
li->block_num = block_num;
|
||||
new_wr->size = wr_size;
|
||||
new_wr->crc32c = new_wr->calc_crc32c();
|
||||
new_wr->checksum = new_wr->calc_checksum(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1602,7 +1535,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
{
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true, offset, len);
|
||||
@@ -1631,7 +1564,7 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
*obj_ptr = wr;
|
||||
@@ -1669,7 +1602,7 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
|
||||
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->data_csum_type)
|
||||
if (dsk->csum_block_size)
|
||||
{
|
||||
if (checksums)
|
||||
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
|
||||
@@ -1716,7 +1649,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
|
||||
new_wr->set_big_location(this, compact_location);
|
||||
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
|
||||
if (dsk->data_csum_type && new_csums)
|
||||
if (dsk->csum_block_size && new_csums)
|
||||
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
});
|
||||
}
|
||||
@@ -1735,7 +1668,6 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
|
||||
*modified_block = block_num;
|
||||
memcpy(wr->get_int_bitmap(this), new_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
wr->crc32c = wr->calc_crc32c();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2038,10 +1970,7 @@ void blockstore_heap_t::iterate_with_stable(heap_entry_t *obj, uint64_t max_lsn,
|
||||
{
|
||||
if (old_wr->type() == BS_HEAP_ROLLBACK)
|
||||
{
|
||||
if (rollback_version > old_wr->version)
|
||||
{
|
||||
rollback_version = old_wr->version;
|
||||
}
|
||||
rollback_version = old_wr->version;
|
||||
}
|
||||
else if (old_wr->type() == BS_HEAP_COMMIT)
|
||||
{
|
||||
@@ -2093,10 +2022,7 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
|
||||
res.compact_lsn = wr->lsn;
|
||||
res.compact_version = wr->version;
|
||||
}
|
||||
if (rollback_version > wr->version)
|
||||
{
|
||||
rollback_version = wr->version;
|
||||
}
|
||||
rollback_version = wr->version;
|
||||
continue;
|
||||
}
|
||||
if (wr->type() == BS_HEAP_COMMIT && wr->lsn <= fsynced_lsn)
|
||||
@@ -2540,22 +2466,6 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
|
||||
}
|
||||
|
||||
void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
|
||||
{
|
||||
if (!li->next)
|
||||
{
|
||||
// The last freed entry must be a deletion
|
||||
assert(!li->prev);
|
||||
assert((li->entry.entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
|
||||
}
|
||||
else if (!li->prev && li->next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
|
||||
{
|
||||
// free BS_HEAP_DELETEs when all previous entries are also freed
|
||||
mark_garbage(li->next->block_num, &li->next->entry, UINT32_MAX);
|
||||
}
|
||||
unlink_list_item(li);
|
||||
}
|
||||
|
||||
void blockstore_heap_t::unlink_list_item(heap_list_item_t *li)
|
||||
{
|
||||
auto prev = li->prev;
|
||||
auto next = li->next;
|
||||
@@ -2565,20 +2475,25 @@ void blockstore_heap_t::unlink_list_item(heap_list_item_t *li)
|
||||
}
|
||||
if (!next)
|
||||
{
|
||||
// The last freed entry must be a deletion
|
||||
assert(!prev);
|
||||
auto wr = &li->entry;
|
||||
assert(wr->entry_type == BS_HEAP_DELETE|BS_HEAP_STABLE);
|
||||
auto & pg_idx = block_index[get_pg_id(wr->inode, wr->stripe)];
|
||||
auto & inode_idx = pg_idx[wr->inode];
|
||||
heap_inode_map_t::iterator li_it;
|
||||
heap_list_item_t *old_li = NULL;
|
||||
inode_map_get(inode_idx, li_it, old_li, wr->stripe);
|
||||
if (!prev)
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
|
||||
else
|
||||
inode_map_replace(inode_idx, li_it, prev);
|
||||
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
|
||||
}
|
||||
else
|
||||
{
|
||||
next->prev = prev;
|
||||
if (!prev && next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
|
||||
{
|
||||
// free BS_HEAP_DELETEs when all previous entries are also freed
|
||||
mark_garbage(next->block_num, &next->entry, UINT32_MAX);
|
||||
}
|
||||
}
|
||||
if (li->entry.is_garbage())
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t entry_type;
|
||||
uint32_t crc32c;
|
||||
uint32_t checksum;
|
||||
uint64_t lsn;
|
||||
uint64_t inode;
|
||||
uint64_t stripe;
|
||||
@@ -57,11 +57,11 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
inline heap_small_write_t& small() { return *(heap_small_write_t*)this; }
|
||||
inline heap_big_write_t& big() { return *(heap_big_write_t*)this; }
|
||||
inline heap_big_intent_t& big_intent() { return *(heap_big_intent_t*)this; }
|
||||
bool is_garbage() const;
|
||||
bool is_garbage();
|
||||
void set_garbage();
|
||||
bool is_overwrite() const;
|
||||
bool is_compactable() const;
|
||||
bool is_before(const heap_entry_t *other) const;
|
||||
bool is_overwrite();
|
||||
bool is_compactable();
|
||||
bool is_before(heap_entry_t *other);
|
||||
uint32_t get_size(blockstore_heap_t *heap);
|
||||
uint8_t *get_ext_bitmap(blockstore_heap_t *heap);
|
||||
uint8_t *get_int_bitmap(blockstore_heap_t *heap);
|
||||
@@ -69,7 +69,8 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
uint32_t *get_checksum(blockstore_heap_t *heap);
|
||||
uint64_t big_location(blockstore_heap_t *heap);
|
||||
void set_big_location(blockstore_heap_t *heap, uint64_t location);
|
||||
uint32_t calc_crc32c();
|
||||
uint32_t calc_checksum(blockstore_heap_t *heap);
|
||||
uint32_t calc_checksum(blockstore_disk_t *dsk);
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_small_write_t
|
||||
@@ -80,7 +81,7 @@ struct __attribute__((__packed__)) heap_small_write_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
|
||||
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_big_write_t
|
||||
@@ -98,7 +99,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
|
||||
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_list_item_t
|
||||
@@ -158,16 +159,6 @@ struct heap_li_equal
|
||||
}
|
||||
};
|
||||
|
||||
struct heap_recheck_state_t
|
||||
{
|
||||
heap_entry_t *obj = NULL;
|
||||
heap_entry_t *next_wr = NULL;
|
||||
size_t total_reads = 0;
|
||||
size_t sent_reads = 0;
|
||||
size_t checked_reads = 0;
|
||||
heap_entry_t *bad_wr = NULL;
|
||||
};
|
||||
|
||||
using i64hash_t = robin_hood::hash<uint64_t>;
|
||||
using heap_inode_map_t = robin_hood::unordered_flat_set<heap_list_item_t*, heap_li_hash, heap_li_equal, 88>;
|
||||
using heap_block_index_t = robin_hood::unordered_flat_map<uint64_t,
|
||||
@@ -218,11 +209,9 @@ class blockstore_heap_t
|
||||
|
||||
bool marked_used_blocks = false;
|
||||
bool recheck_queue_filled = false;
|
||||
std::vector<heap_list_item_t*> postponed_items;
|
||||
std::vector<heap_list_item_t*> loaded_list_items;
|
||||
std::set<uint32_t> recheck_modified_blocks;
|
||||
std::deque<heap_entry_t*> recheck_queue;
|
||||
std::map<heap_entry_t*, heap_recheck_state_t> recheck_states;
|
||||
size_t recheck_pending_reads = 0;
|
||||
int recheck_in_progress = 0;
|
||||
bool in_recheck = false;
|
||||
std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> recheck_cb;
|
||||
@@ -231,12 +220,7 @@ class blockstore_heap_t
|
||||
uint64_t get_pg_id(inode_t inode, uint64_t stripe);
|
||||
bool validate_object(heap_entry_t *obj);
|
||||
void fill_recheck_queue();
|
||||
void recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr);
|
||||
void recheck_start_reads(heap_recheck_state_t *st);
|
||||
int mark_used_blocks();
|
||||
void init_free_bad_entry(heap_entry_t *wr);
|
||||
void init_erase_bad_entry(heap_list_item_t *li);
|
||||
bool init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li);
|
||||
void recheck_full_gc();
|
||||
void recheck_buffer(heap_entry_t *cwr, uint8_t *buf);
|
||||
void defragment_block(uint32_t block_num);
|
||||
@@ -244,9 +228,8 @@ class blockstore_heap_t
|
||||
|
||||
void gc_block(heap_block_info_t & inf);
|
||||
int allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free);
|
||||
void insert_list_items(heap_list_item_t** v, size_t count, bool postpone);
|
||||
void insert_list_item(heap_list_item_t *li);
|
||||
void remove_list_item(heap_list_item_t *li);
|
||||
void unlink_list_item(heap_list_item_t *li);
|
||||
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
|
||||
bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry);
|
||||
int add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type);
|
||||
|
||||
@@ -101,7 +101,6 @@ void blockstore_impl_t::loop()
|
||||
unsigned initial_ring_space = ringloop->space_left();
|
||||
int op_idx = 0, new_idx = 0;
|
||||
bool has_unfinished_writes = false;
|
||||
bool has_unfinished_sync = false;
|
||||
for (; op_idx < submit_queue.size(); op_idx++, new_idx++)
|
||||
{
|
||||
auto op = submit_queue[op_idx];
|
||||
@@ -139,13 +138,7 @@ void blockstore_impl_t::loop()
|
||||
else if (op->opcode == BS_OP_SYNC)
|
||||
{
|
||||
// syncs only completed writes, so doesn't have to be blocked by anything
|
||||
if (!has_unfinished_sync)
|
||||
{
|
||||
wr_st = continue_sync(op);
|
||||
has_unfinished_sync = (wr_st != 2);
|
||||
}
|
||||
else
|
||||
wr_st = 0;
|
||||
wr_st = continue_sync(op);
|
||||
}
|
||||
else if (op->opcode == BS_OP_STABLE || op->opcode == BS_OP_ROLLBACK)
|
||||
{
|
||||
@@ -161,7 +154,9 @@ void blockstore_impl_t::loop()
|
||||
wr_st = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
wr_st = 0;
|
||||
}
|
||||
}
|
||||
if (wr_st == 2)
|
||||
{
|
||||
|
||||
@@ -153,14 +153,6 @@ resume_1:
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
uint32_t csum = hdr->header_csum;
|
||||
hdr->header_csum = 0;
|
||||
if (crc32c(0, hdr, sizeof(*hdr)) != csum)
|
||||
{
|
||||
printf("Metadata header is corrupt (checksum mismatch).\n");
|
||||
exit(1);
|
||||
}
|
||||
hdr->header_csum = csum;
|
||||
}
|
||||
bs->heap->start_load(((blockstore_meta_header_v3_t *)bs->meta_superblock)->completed_lsn);
|
||||
if (bs->dsk.inmemory_journal)
|
||||
@@ -249,6 +241,22 @@ resume_4:
|
||||
// metadata read finished
|
||||
bs->heap->finish_load();
|
||||
printf("Metadata entries loaded: %ju, rechecking unfinished writes and garbage entries\n", entries_loaded);
|
||||
if (zero_on_init && !bs->dsk.disable_meta_fsync)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// asynchronous recheck
|
||||
resume_6:
|
||||
wait_state = 6;
|
||||
@@ -329,22 +337,6 @@ resume_9:
|
||||
}
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
if (!bs->dsk.disable_meta_fsync && !bs->readonly)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("Loading finished. Data used: %ju / %ju bytes (%s / %s)\n",
|
||||
bs->heap->get_data_used_space(), bs->dsk.block_count * bs->dsk.data_block_size,
|
||||
format_size(bs->heap->get_data_used_space()).c_str(),
|
||||
|
||||
@@ -16,7 +16,6 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
else if (priv->op_state == 5) goto resume_5;
|
||||
assert(!priv->op_state);
|
||||
op->retval = 0;
|
||||
PRIV(op)->lsn = 0;
|
||||
priv->modified_block = priv->modified_block2 = UINT32_MAX;
|
||||
for (priv->stab_pos = 0; priv->stab_pos < op->len; priv->stab_pos++)
|
||||
{
|
||||
@@ -37,12 +36,6 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (res == ENOENT)
|
||||
{
|
||||
op->retval = -ENOENT;
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (res == ENOSPC)
|
||||
{
|
||||
if (!heap->get_to_compact_count())
|
||||
|
||||
@@ -9,7 +9,6 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
|
||||
if (!PRIV(op)->op_state)
|
||||
{
|
||||
op->retval = 0;
|
||||
PRIV(op)->lsn = 0;
|
||||
}
|
||||
int res = do_sync(op, 0);
|
||||
if (res == 2)
|
||||
@@ -105,8 +104,7 @@ int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
|
||||
unsynced_data_write_count = unsynced_buffer_write_count = unsynced_meta_write_count = 0;
|
||||
return 2;
|
||||
}
|
||||
assert(!PRIV(op)->lsn);
|
||||
PRIV(op)->lsn = heap->get_completed_lsn();
|
||||
PRIV(op)->modified_block = heap->get_completed_lsn();
|
||||
if (!submit_fsyncs(PRIV(op)->pending_ops))
|
||||
{
|
||||
PRIV(op)->wait_detail = 1;
|
||||
@@ -120,6 +118,6 @@ resume_1:
|
||||
return 1;
|
||||
}
|
||||
resume_2:
|
||||
heap->mark_lsn_fsynced(PRIV(op)->lsn);
|
||||
heap->mark_lsn_fsynced(PRIV(op)->modified_block);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
|
||||
heap->complete_block_write(modified_block);
|
||||
ringloop->wakeup();
|
||||
};
|
||||
assert(((uint64_t)modified_block+2)*dsk.meta_block_size <= dsk.meta_area_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
|
||||
);
|
||||
@@ -178,7 +177,6 @@ enospc:
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->offset+op->len <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
|
||||
PRIV(op)->pending_ops++;
|
||||
write_iodepth++;
|
||||
@@ -266,7 +264,6 @@ enospc:
|
||||
BS_SUBMIT_GET_SQE(sqe2, data2);
|
||||
data2->iov = (struct iovec){ op->buf, op->len };
|
||||
data2->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->len <= dsk.journal_len);
|
||||
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, dsk.journal_offset + loc);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
@@ -456,7 +453,6 @@ resume_10:
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(PRIV(op)->location + op->offset <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + PRIV(op)->location + op->offset);
|
||||
if (dsk.use_atomic_flag)
|
||||
sqe->rw_flags = RWF_ATOMIC;
|
||||
|
||||
@@ -12,7 +12,7 @@ multilist_alloc_t::multilist_alloc_t(uint32_t count, uint32_t maxn):
|
||||
count(count), maxn(maxn)
|
||||
{
|
||||
// not-so-memory-efficient: 16 MB memory per 1 GB buffer space, but buffer spaces are small, so OK
|
||||
assert(count > 1 && count < 0x80000000 && count >= maxn);
|
||||
assert(count > 1 && count < 0x80000000);
|
||||
sizes.resize(count);
|
||||
nexts.resize(count); // nexts[i] = 0 -> area is used; nexts[i] = 1 -> no next; nexts[i] >= 2 -> next item
|
||||
prevs.resize(count);
|
||||
|
||||
@@ -141,7 +141,7 @@ struct __attribute__((__packed__)) journal_entry
|
||||
inline uint32_t je_crc32(journal_entry *je)
|
||||
{
|
||||
// 0x48674bc7 = crc32(4 zero bytes)
|
||||
return je->size < 4 ? 0 : crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
return crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
}
|
||||
|
||||
// "VITAstor"
|
||||
|
||||
@@ -520,7 +520,6 @@ resume_2:
|
||||
await_sqe(15);
|
||||
data->iov = (struct iovec){ it->buf, (size_t)it->len };
|
||||
data->callback = simple_callback_w;
|
||||
assert(clean_loc+it->offset+it->len <= bs->dsk.block_count*bs->dsk.data_block_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + it->offset
|
||||
);
|
||||
@@ -750,7 +749,6 @@ bool journal_flusher_co::write_meta_block(flusher_meta_write_t & meta_block, int
|
||||
await_sqe(0);
|
||||
data->iov = (struct iovec){ meta_block.buf, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = simple_callback_w;
|
||||
assert(bs->dsk.meta_block_size + meta_block.sector + bs->dsk.meta_block_size <= bs->dsk.meta_area_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bs->dsk.meta_block_size + meta_block.sector
|
||||
);
|
||||
@@ -1349,7 +1347,7 @@ bool journal_flusher_co::fsync_batch(bool fsync_meta, int wait_base)
|
||||
cur_sync->ready_count++;
|
||||
flusher->syncing_flushers++;
|
||||
resume_1:
|
||||
if (cur_sync->state == 0)
|
||||
if (!cur_sync->state)
|
||||
{
|
||||
if (flusher->syncing_flushers >= flusher->active_flushers || !flusher->flush_queue.size())
|
||||
{
|
||||
@@ -1377,12 +1375,6 @@ bool journal_flusher_co::fsync_batch(bool fsync_meta, int wait_base)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (cur_sync->state == 1)
|
||||
{
|
||||
// Wait for fsync completion
|
||||
wait_state = wait_base+1;
|
||||
return false;
|
||||
}
|
||||
flusher->syncing_flushers--;
|
||||
cur_sync->ready_count--;
|
||||
if (cur_sync->ready_count == 0)
|
||||
|
||||
@@ -193,7 +193,6 @@ void blockstore_impl_t::prepare_journal_sector_write(int cur_sector, blockstore_
|
||||
(size_t)journal.block_size
|
||||
};
|
||||
data->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
|
||||
assert(journal.sector_info[cur_sector].offset+journal.block_size <= dsk.journal_len);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.journal_fd, &data->iov, 1, journal.offset + journal.sector_info[cur_sector].offset
|
||||
);
|
||||
|
||||
@@ -368,9 +368,9 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
}
|
||||
data->iov.iov_len = op->len + stripe_offset + stripe_end; // to check it in the callback
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
const uint64_t write_offset = (loc * dsk.data_block_size) + op->offset - stripe_offset;
|
||||
assert(write_offset+op->len+stripe_offset+stripe_end <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + write_offset);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + (loc * dsk.data_block_size) + op->offset - stripe_offset
|
||||
);
|
||||
PRIV(op)->pending_ops = 1;
|
||||
if (!(dirty_it->second.state & BS_ST_INSTANT))
|
||||
{
|
||||
@@ -495,8 +495,9 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
.op = op,
|
||||
});
|
||||
data2->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
|
||||
assert(journal.next_free+op->len <= dsk.journal_len);
|
||||
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free);
|
||||
io_uring_prep_writev(
|
||||
sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free
|
||||
);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -12,11 +12,11 @@ if (RDMACM_LIBRARIES)
|
||||
set(MSGR_RDMACM "msgr_rdmacm.cpp")
|
||||
endif (RDMACM_LIBRARIES)
|
||||
add_library(vitastor_common STATIC
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp 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
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp ../util/xxh_x86dispatch.c
|
||||
msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
|
||||
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
|
||||
)
|
||||
target_link_libraries(vitastor_common pthread)
|
||||
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES})
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client.so
|
||||
@@ -24,6 +24,7 @@ add_library(vitastor_client SHARED
|
||||
cluster_client.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
cluster_client_icache.cpp
|
||||
vitastor_c.cpp
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
|
||||
@@ -33,6 +34,7 @@ target_link_libraries(vitastor_client
|
||||
${LIBURING_LIBRARIES}
|
||||
${IBVERBS_LIBRARIES}
|
||||
${RDMACM_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
|
||||
configure_file(vitastor.pc.in vitastor.pc @ONLY)
|
||||
@@ -52,6 +54,9 @@ if (${WITH_FIO})
|
||||
../util/rw_blocking.cpp
|
||||
../util/addr_util.cpp
|
||||
)
|
||||
target_link_libraries(fio_vitastor_sec
|
||||
tcmalloc_minimal
|
||||
)
|
||||
endif (${WITH_FIO})
|
||||
|
||||
# vitastor-nbd
|
||||
@@ -95,10 +100,10 @@ endif (${WITH_QEMU})
|
||||
add_executable(test_cluster_client
|
||||
EXCLUDE_FROM_ALL
|
||||
../test/test_cluster_client.cpp
|
||||
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp
|
||||
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp
|
||||
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp cluster_client_icache.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp
|
||||
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../util/xxh_x86dispatch.c ../../json11/json11.cpp
|
||||
)
|
||||
target_link_libraries(test_cluster_client ${LIBURING_LIBRARIES})
|
||||
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES})
|
||||
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
|
||||
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
|
||||
add_dependencies(build_tests test_cluster_client)
|
||||
|
||||
+117
-69
@@ -62,6 +62,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli.on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
|
||||
st_cli.on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
|
||||
st_cli.on_reload_hook = [this]() { st_cli.load_global_config(); };
|
||||
st_cli.on_inode_change_hook = [this](uint64_t inode, bool removed) { on_change_inode_hook(inode, removed); };
|
||||
|
||||
st_cli.parse_config(config);
|
||||
st_cli.infinite_start = false;
|
||||
@@ -70,13 +71,11 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli.infinite_start = config["client_infinite_start"].bool_value();
|
||||
}
|
||||
st_cli.load_global_config();
|
||||
|
||||
scrap_buffer_size = SCRAP_BUFFER_SIZE;
|
||||
scrap_buffer = malloc_or_die(scrap_buffer_size);
|
||||
}
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
vault_destroy();
|
||||
if (retry_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(retry_timeout_id);
|
||||
@@ -94,7 +93,6 @@ cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
ringloop->unregister_consumer(&consumer);
|
||||
}
|
||||
free(scrap_buffer);
|
||||
delete wb;
|
||||
wb = NULL;
|
||||
}
|
||||
@@ -481,6 +479,8 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
self_tree_metrics.clear();
|
||||
client_hostname = new_hostname;
|
||||
}
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config);
|
||||
st_cli.parse_config(config);
|
||||
st_cli.load_pgs();
|
||||
@@ -607,6 +607,9 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
}
|
||||
inode_cache.clear();
|
||||
inode_cache_children.clear();
|
||||
vault_keys.clear();
|
||||
continue_ops();
|
||||
}
|
||||
|
||||
@@ -673,6 +676,10 @@ bool cluster_client_t::flush()
|
||||
{
|
||||
if (!ringloop)
|
||||
{
|
||||
if (vault_loading)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (wb->writeback_queue.size())
|
||||
{
|
||||
wb->start_writebacks(this, 0);
|
||||
@@ -695,7 +702,7 @@ bool cluster_client_t::flush()
|
||||
sync_done = true;
|
||||
};
|
||||
execute(sync);
|
||||
while (!sync_done)
|
||||
while (!sync_done || vault_loading)
|
||||
{
|
||||
ringloop->loop();
|
||||
if (!sync_done)
|
||||
@@ -958,10 +965,40 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
{
|
||||
op->flags |= OP_IMMEDIATE_COMMIT;
|
||||
}
|
||||
bool searched = false;
|
||||
std::shared_ptr<inode_cache_t> icache;
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
|
||||
{
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (icache && icache->op_enc)
|
||||
{
|
||||
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
|
||||
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
|
||||
{
|
||||
auto ino_it = st_cli.inode_config.find(op->inode);
|
||||
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->readonly)
|
||||
{
|
||||
op->retval = -EROFS;
|
||||
auto cb = std::move(op->callback);
|
||||
@@ -972,33 +1009,39 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
op->deoptimise_snapshot = false;
|
||||
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
|
||||
{
|
||||
auto ino_it = st_cli.inode_config.find(op->inode);
|
||||
if (ino_it != st_cli.inode_config.end())
|
||||
if (!searched)
|
||||
{
|
||||
int chain_size = 0;
|
||||
while (ino_it != st_cli.inode_config.end() && ino_it->second.parent_id)
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache)
|
||||
{
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
chain_size > st_cli.inode_config.size())
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (INODE_POOL(ino_it->second.parent_id) == INODE_POOL(ino_it->first) &&
|
||||
wb->has_inode(ino_it->second.parent_id))
|
||||
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
|
||||
{
|
||||
// Deoptimise reads - we have dirty data for one of the parent layer(s).
|
||||
op->deoptimise_snapshot = true;
|
||||
break;
|
||||
}
|
||||
chain_size++;
|
||||
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (icache && icache->err_code)
|
||||
{
|
||||
if (icache->err_code == EPERM)
|
||||
{
|
||||
op->retval = -EPERM;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
else if (icache->err_code == EAGAIN)
|
||||
{
|
||||
key_wait_ops.push_back(op);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1119,40 +1162,35 @@ resume_2:
|
||||
// Finished successfully
|
||||
// Even if the PG count has changed in meanwhile we treat it as success
|
||||
// because if some operations were invalid for the new PG count we'd get errors
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Copy part bitmaps only after finishing all part reads
|
||||
for (auto & part: op->parts)
|
||||
if ((part.flags & (PART_SENT|PART_DONE|PART_VALID)) == (PART_SENT|PART_DONE|PART_VALID))
|
||||
copy_part_bitmap(op, &part);
|
||||
}
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Check parent inode
|
||||
auto ino_it = st_cli.inode_config.find(op->cur_inode);
|
||||
// Skip parents from the same pool
|
||||
int skipped = 0;
|
||||
while (!op->deoptimise_snapshot &&
|
||||
ino_it != st_cli.inode_config.end() && ino_it->second.parent_id &&
|
||||
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
|
||||
uint64_t next_inode = 0;
|
||||
auto icache = inode_cache_get(op->cur_inode);
|
||||
if (icache)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
skipped > st_cli.inode_config.size())
|
||||
if (icache->has_parent_loop)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
skipped++;
|
||||
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
|
||||
if (op->deoptimise_snapshot)
|
||||
{
|
||||
if (icache->chain.size() > 1)
|
||||
next_inode = icache->chain[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (icache->other_pool_parent_id)
|
||||
next_inode = icache->other_pool_parent_id;
|
||||
}
|
||||
}
|
||||
if (ino_it != st_cli.inode_config.end() &&
|
||||
ino_it->second.parent_id &&
|
||||
ino_it->second.parent_id != op->inode)
|
||||
if (next_inode)
|
||||
{
|
||||
// Continue reading from the parent inode
|
||||
op->cur_inode = ino_it->second.parent_id;
|
||||
icache = inode_cache_get(next_inode);
|
||||
op->cur_inode = next_inode;
|
||||
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
|
||||
op->parts.clear();
|
||||
op->done_count = 0;
|
||||
goto resume_0;
|
||||
@@ -1171,7 +1209,7 @@ resume_2:
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
else if (op->retval != 0 && op->opcode != OSD_OP_SYNC && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
else if (op->retval != 0 && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
op->retval != -EPIPE && (op->retval != -EIO || !client_eio_retry_interval) && (op->retval != -ENOSPC || !client_retry_enospc))
|
||||
{
|
||||
// Fatal error (neither -EPIPE, -EIO nor -ENOSPC)
|
||||
@@ -1203,7 +1241,7 @@ resume_2:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov, void *scrap, int scrap_len)
|
||||
static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov)
|
||||
{
|
||||
int left = size;
|
||||
while (left > 0 && iov_idx < op->iov.count)
|
||||
@@ -1211,7 +1249,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
int cur_left = op->iov.buf[iov_idx].iov_len - iov_pos;
|
||||
if (cur_left < left)
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, cur_left);
|
||||
}
|
||||
@@ -1221,7 +1259,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, left);
|
||||
}
|
||||
@@ -1230,16 +1268,10 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
}
|
||||
assert(left == 0);
|
||||
if (skip && scrap_len > 0)
|
||||
if (skip == 1)
|
||||
{
|
||||
// All skipped ranges are read into the same useless buffer
|
||||
left = size;
|
||||
while (left > 0)
|
||||
{
|
||||
int cur_left = scrap_len < left ? scrap_len : left;
|
||||
iov.push_back(scrap, cur_left);
|
||||
left -= cur_left;
|
||||
}
|
||||
// data read into a NULL buffer will be discarded by messenger
|
||||
iov.push_back(NULL, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,7 +1291,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
// Allocate memory for the bitmap
|
||||
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
|
||||
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
|
||||
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
|
||||
unsigned bitmap_mem = object_bitmap_size +
|
||||
op->parts.size() * pg_data_size *
|
||||
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->enc ? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*op->len/pool_cfg.bitmap_granularity : 0));
|
||||
if (!op->bitmap_buf || op->bitmap_buf_size < bitmap_mem)
|
||||
{
|
||||
op->bitmap_buf = realloc_or_die(op->bitmap_buf, bitmap_mem);
|
||||
@@ -1301,10 +1337,10 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
begin = cur;
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(cur-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(cur-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
skip_prev = skip;
|
||||
prev = cur;
|
||||
@@ -1315,11 +1351,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
if (skip_prev)
|
||||
{
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(end-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
end = prev;
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
if (end == begin)
|
||||
{
|
||||
op->done_count++;
|
||||
@@ -1328,7 +1364,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
}
|
||||
else if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_DELETE)
|
||||
{
|
||||
add_iov(end-begin, false, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-begin, 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
op->parts[i].parent = op;
|
||||
op->parts[i].offset = begin;
|
||||
@@ -1414,9 +1450,12 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
osd_client_t *cl = peer_it->second;
|
||||
part->flags |= PART_SENT|PART_VALID;
|
||||
op->inflight_count++;
|
||||
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->opcode == OSD_OP_READ && op->enc
|
||||
? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*pool_cfg.data_block_size/pool_cfg.bitmap_granularity
|
||||
: 0));
|
||||
uint64_t meta_rev = 0;
|
||||
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
|
||||
{
|
||||
@@ -1435,6 +1474,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
.inode = op->cur_inode,
|
||||
.offset = part->offset,
|
||||
.len = part->len,
|
||||
.flags = op->opcode == OSD_OP_READ && op->enc && !op->deoptimise_snapshot ? OSD_OP_RETURN_CHAIN : 0,
|
||||
.meta_revision = meta_rev,
|
||||
.version = op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE ? op->version : 0,
|
||||
} },
|
||||
@@ -1442,6 +1482,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
|
||||
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
|
||||
? pg_bitmap_size : 0),
|
||||
.enc = op->enc,
|
||||
.callback = cb ? cb : [this, part](osd_op_t *op_part)
|
||||
{
|
||||
handle_op_part(part);
|
||||
@@ -1637,6 +1678,13 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
}
|
||||
if (op->inflight_count == 0 && !op->retry_after)
|
||||
{
|
||||
// Copy part bitmaps only after finishing all part reads
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
for (auto & part: op->parts)
|
||||
if ((part.flags & (PART_SENT|PART_VALID|PART_DONE)) == (PART_SENT|PART_VALID|PART_DONE))
|
||||
copy_part_bitmap(op, &part);
|
||||
}
|
||||
if (op->opcode == OSD_OP_SYNC)
|
||||
continue_sync(op);
|
||||
else
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "messenger.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_OPS 1024
|
||||
@@ -71,6 +72,7 @@ protected:
|
||||
cluster_op_t *prev = NULL, *next = NULL;
|
||||
int prev_wait = 0;
|
||||
uint64_t flush_id = 0;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
friend class cluster_client_t;
|
||||
friend class writeback_cache_t;
|
||||
};
|
||||
@@ -80,14 +82,36 @@ struct inode_list_osd_t;
|
||||
struct inode_list_pg_t;
|
||||
class writeback_cache_t;
|
||||
|
||||
struct inode_cache_t
|
||||
{
|
||||
std::vector<inode_t> chain; // only parents from the same pool
|
||||
uint8_t *key_data = NULL;
|
||||
osd_op_enc_t *op_enc = NULL;
|
||||
bool readonly = false;
|
||||
bool has_parent_loop = false;
|
||||
inode_t other_pool_parent_id = 0;
|
||||
int err_code = 0;
|
||||
|
||||
~inode_cache_t();
|
||||
};
|
||||
|
||||
struct vault_load_key_t
|
||||
{
|
||||
int key_state = 0;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
// FIXME: Split into public and private interfaces
|
||||
class __attribute__((visibility("default"))) cluster_client_t
|
||||
{
|
||||
#ifdef __MOCK__
|
||||
public:
|
||||
#endif
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
std::map<pool_pg_num_t, osd_num_t> pg_primary;
|
||||
// config:
|
||||
|
||||
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
|
||||
uint64_t client_max_dirty_bytes = 0;
|
||||
uint64_t client_max_dirty_ops = 0;
|
||||
@@ -99,12 +123,23 @@ 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;
|
||||
@@ -112,15 +147,28 @@ 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;
|
||||
|
||||
void *scrap_buffer = NULL;
|
||||
unsigned scrap_buffer_size = 0;
|
||||
// inodes require some extra state for read/write, it's stored here.
|
||||
// moreover, robin_hood access is slightly faster than std::map :)
|
||||
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
|
||||
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
|
||||
|
||||
http_context_t *vault_http_ctx = NULL;
|
||||
http_co_t *vault_http_cli = NULL;
|
||||
bool vault_loading = false;
|
||||
std::string vault_token;
|
||||
bool vault_auth_error = false;
|
||||
timespec vault_token_expire = {};
|
||||
std::vector<std::string> vault_key_load_queue;
|
||||
std::map<std::string, vault_load_key_t> vault_keys;
|
||||
|
||||
bool pgs_loaded = false;
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
ring_consumer_t consumer;
|
||||
std::vector<std::function<void(void)>> on_ready_hooks;
|
||||
int list_retry_timeout_id = -1;
|
||||
@@ -152,9 +200,22 @@ public:
|
||||
void list_inode(inode_t inode, uint64_t min_offset, uint64_t max_offset, int max_parallel_pgs, std::function<void(
|
||||
int status, int pgs_left, pg_num_t pg_num, std::set<object_id>&& objects)> pg_callback);
|
||||
|
||||
//inline uint32_t get_bs_bitmap_granularity() { return st_cli.global_bitmap_granularity; }
|
||||
//inline uint64_t get_bs_block_size() { return st_cli.global_block_size; }
|
||||
|
||||
#ifndef __MOCK__
|
||||
protected:
|
||||
#endif
|
||||
void continue_ops(int time_passed = 0);
|
||||
|
||||
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
|
||||
void vault_parse_config();
|
||||
bool vault_check_token();
|
||||
void vault_load_keys();
|
||||
void vault_destroy();
|
||||
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
|
||||
|
||||
protected:
|
||||
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
|
||||
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
|
||||
|
||||
@@ -164,6 +225,7 @@ protected:
|
||||
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
|
||||
void on_change_osd_state_hook(uint64_t peer_osd);
|
||||
void on_change_node_placement_hook();
|
||||
void on_change_inode_hook(uint64_t inode, bool removed);
|
||||
|
||||
void execute_internal(cluster_op_t *op);
|
||||
void execute_cas(cluster_op_t *op);
|
||||
@@ -180,6 +242,7 @@ protected:
|
||||
void erase_op(cluster_op_t *op);
|
||||
void calc_wait(cluster_op_t *op);
|
||||
void inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *next, int inc);
|
||||
|
||||
void continue_lists();
|
||||
bool continue_listing(inode_list_t *lst);
|
||||
bool restart_listing(inode_list_t* lst);
|
||||
@@ -195,5 +258,4 @@ protected:
|
||||
osd_num_t select_nearest_osd(const std::vector<osd_num_t> & osds);
|
||||
|
||||
friend class writeback_cache_t;
|
||||
friend class cluster_client_test_t;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
#include "cluster_client_impl.h"
|
||||
#include "http_client.h"
|
||||
#include "str_util.h"
|
||||
|
||||
#define VAULT_KEY_NOT_LOADED 0
|
||||
#define VAULT_KEY_LOADING 1
|
||||
#define VAULT_KEY_LOADED 2
|
||||
#define VAULT_KEY_ERROR 3
|
||||
|
||||
inode_cache_t::~inode_cache_t()
|
||||
{
|
||||
if (key_data)
|
||||
{
|
||||
free(key_data);
|
||||
key_data = NULL;
|
||||
op_enc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_destroy()
|
||||
{
|
||||
if (vault_http_ctx)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
http_destroy(vault_http_cli);
|
||||
http_context_destroy(vault_http_ctx);
|
||||
vault_http_cli = NULL;
|
||||
vault_http_ctx = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_config()
|
||||
{
|
||||
vault_url = config["vault_url"].string_value();
|
||||
vault_client_cert = config["vault_client_cert"].string_value();
|
||||
vault_client_key = config["vault_client_key"].string_value();
|
||||
vault_ca = config["vault_ca"].string_value();
|
||||
vault_secret_api_path = "/v1/secret/";
|
||||
if (config["vault_secret_api_path"].is_string())
|
||||
vault_secret_api_path = config["vault_secret_api_path"].string_value();
|
||||
vault_timeout_ms = config["vault_timeout_ms"].uint64_value();
|
||||
if (!vault_timeout_ms)
|
||||
vault_timeout_ms = 5000;
|
||||
vault_error_timeout_sec = config["vault_error_timeout_sec"].uint64_value();
|
||||
if (!vault_error_timeout_sec)
|
||||
vault_error_timeout_sec = 60;
|
||||
vault_refresh_leeway_sec = config["vault_refresh_leeway_sec"].uint64_value();
|
||||
if (!vault_refresh_leeway_sec)
|
||||
vault_refresh_leeway_sec = 60;
|
||||
}
|
||||
|
||||
// FIXME: Rework client API by adding open/close and cache inode information in the "FD" (maybe)
|
||||
void cluster_client_t::on_change_inode_hook(uint64_t inode, bool removed)
|
||||
{
|
||||
std::vector<inode_t> children = { inode };
|
||||
for (size_t i = 0; i < children.size(); i++)
|
||||
{
|
||||
auto it = inode_cache_children.lower_bound(std::make_pair(children[i], (inode_t)0));
|
||||
while (it != inode_cache_children.end() && it->first == children[i])
|
||||
{
|
||||
children.push_back(it->second);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
for (auto & inode: children)
|
||||
{
|
||||
auto it = inode_cache.find(inode);
|
||||
if (it != inode_cache.end())
|
||||
{
|
||||
auto icache = it->second;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
inode_cache_children.erase(std::make_pair(parent, inode));
|
||||
}
|
||||
inode_cache.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
{
|
||||
auto icache_it = inode_cache.find(ino);
|
||||
if (icache_it != inode_cache.end())
|
||||
{
|
||||
return icache_it->second;
|
||||
}
|
||||
// Fill inode cache
|
||||
auto ino_it = st_cli.inode_config.find(ino);
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto pool_it = st_cli.pool_config.find(INODE_POOL(ino));
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto & inode_cfg = ino_it->second;
|
||||
auto & pool_cfg = pool_it->second;
|
||||
std::shared_ptr<inode_cache_t> icache = std::make_shared<inode_cache_t>();
|
||||
icache->readonly = inode_cfg.readonly;
|
||||
icache->chain.push_back(ino);
|
||||
std::vector<inode_config_t*> chain_cfg;
|
||||
// FIXME: Allow unencrypted read & write when all chain is encrypted with the same key
|
||||
int enc_key_count = !inode_cfg.enc_key.empty() ? 1 : 0;
|
||||
if (inode_cfg.parent_id)
|
||||
{
|
||||
// Check for loops and cache the chain
|
||||
robin_hood::unordered_flat_set<inode_t> seen;
|
||||
seen.insert(ino);
|
||||
uint64_t parent_id = inode_cfg.parent_id;
|
||||
while (parent_id)
|
||||
{
|
||||
if (seen.find(parent_id) != seen.end())
|
||||
{
|
||||
icache->has_parent_loop = true;
|
||||
break;
|
||||
}
|
||||
seen.insert(parent_id);
|
||||
ino_it = st_cli.inode_config.find(parent_id);
|
||||
if (INODE_POOL(parent_id) == INODE_POOL(ino))
|
||||
{
|
||||
icache->chain.push_back(parent_id);
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
chain_cfg.push_back(NULL);
|
||||
else
|
||||
{
|
||||
chain_cfg.push_back(&ino_it->second);
|
||||
if (!ino_it->second.enc_key.empty())
|
||||
enc_key_count++;
|
||||
}
|
||||
}
|
||||
else if (!icache->other_pool_parent_id)
|
||||
icache->other_pool_parent_id = parent_id;
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
break;
|
||||
parent_id = ino_it->second.parent_id;
|
||||
}
|
||||
}
|
||||
// Check external keys and wait for loading, if required
|
||||
if (enc_key_count)
|
||||
{
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
|
||||
{
|
||||
auto & ik = vault_keys[inode_cfg.enc_key];
|
||||
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
|
||||
{
|
||||
icache->err_code = EPERM;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
|
||||
{
|
||||
ik.key_state = VAULT_KEY_LOADING;
|
||||
vault_key_load_queue.push_back(inode_cfg.enc_key);
|
||||
vault_load_keys();
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_LOADING)
|
||||
{
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ik.key_state == VAULT_KEY_LOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate encryption key chain, if applicable
|
||||
if (enc_key_count)
|
||||
{
|
||||
uint8_t *key_data = (uint8_t*)malloc_or_die(
|
||||
AES_256_XTS_KEY_SIZE * enc_key_count +
|
||||
sizeof(uint8_t*) * icache->chain.size() +
|
||||
sizeof(osd_op_enc_t)
|
||||
);
|
||||
uint8_t **keys = (uint8_t**)(key_data + AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
osd_op_enc_t *enc = (osd_op_enc_t*)((uint8_t*)keys + sizeof(uint8_t*)*icache->chain.size());
|
||||
size_t key_pos = 0;
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && !cfg->enc_key.empty())
|
||||
{
|
||||
const auto & key = cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX
|
||||
? vault_keys.at(cfg->enc_key).key
|
||||
: cfg->enc_key;
|
||||
assert(key_pos < AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
assert(key.size() == 2*AES_256_XTS_KEY_SIZE);
|
||||
keys[i] = key_data + key_pos;
|
||||
fromhexstr(key, AES_256_XTS_KEY_SIZE, key_data + key_pos);
|
||||
key_pos += AES_256_XTS_KEY_SIZE;
|
||||
}
|
||||
else
|
||||
keys[i] = NULL;
|
||||
}
|
||||
enc->key_chain = keys;
|
||||
enc->chain_size = icache->chain.size();
|
||||
enc->read_chain_bitmap_pos = pool_cfg.data_block_size/pool_cfg.bitmap_granularity/8;
|
||||
enc->bitmap_granularity = pool_cfg.bitmap_granularity;
|
||||
icache->key_data = key_data;
|
||||
icache->op_enc = enc;
|
||||
}
|
||||
inode_cache[ino] = icache;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
if (parent != ino)
|
||||
inode_cache_children.insert(std::make_pair(parent, ino));
|
||||
}
|
||||
return icache;
|
||||
}
|
||||
|
||||
#ifndef __MOCK__
|
||||
bool cluster_client_t::vault_check_token()
|
||||
{
|
||||
timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
if (!vault_token_expire.tv_sec || vault_token_expire.tv_sec < now.tv_sec)
|
||||
{
|
||||
vault_loading = true;
|
||||
http_json_post(
|
||||
vault_http_cli, vault_url+"/v1/auth/cert/login", json11::Json::object{}, "",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this](http_message_t *response)
|
||||
{
|
||||
clock_gettime(CLOCK_REALTIME, &vault_token_expire);
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "Vault request failed: %s\n", err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t ttl = data["auth"]["lease_duration"].uint64_value();
|
||||
vault_token = data["auth"]["client_token"].string_value();
|
||||
if (vault_token.empty() || !ttl)
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "No token or lease_duration in Vault response: %s\n", data.dump().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttl < vault_refresh_leeway_sec)
|
||||
vault_token_expire.tv_sec += ttl/2;
|
||||
else
|
||||
vault_token_expire.tv_sec += ttl - vault_refresh_leeway_sec;
|
||||
}
|
||||
}
|
||||
vault_load_keys();
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (vault_token.empty())
|
||||
{
|
||||
// Auth error happened, mark all loads as failed
|
||||
for (auto & key_id: vault_key_load_queue)
|
||||
{
|
||||
auto & k = vault_keys[key_id];
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
}
|
||||
vault_key_load_queue.clear();
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void cluster_client_t::vault_load_keys()
|
||||
{
|
||||
if (vault_loading || !vault_key_load_queue.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
#ifdef __MOCK__
|
||||
vault_loading = true;
|
||||
#else
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
vault_http_ctx = http_context_init(tfd, vault_client_cert, vault_client_key, vault_ca, true, error);
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context for Vault: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
vault_http_cli = http_init(vault_http_ctx);
|
||||
}
|
||||
if (!vault_check_token())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::string key_id = vault_key_load_queue[0];
|
||||
vault_key_load_queue.erase(vault_key_load_queue.begin());
|
||||
vault_loading = true;
|
||||
http_get(
|
||||
vault_http_cli, vault_url+vault_secret_api_path+key_id.substr(strlen(VAULT_KEY_PREFIX)), "X-Vault-Token: "+vault_token+"\r\n",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this, key_id](http_message_t *response)
|
||||
{
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
vault_parse_secret(key_id, err, data);
|
||||
}
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data)
|
||||
{
|
||||
vault_loading = false;
|
||||
auto & k = vault_keys[key_id];
|
||||
if (err != "")
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault %s%s%s request failed: %s\n", vault_url.c_str(),
|
||||
vault_secret_api_path.c_str(), key_id.c_str()+strlen(VAULT_KEY_PREFIX), err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hexkey = data["data"]["key"].string_value();
|
||||
if (hexkey.empty() || !ishexstr(hexkey) || hexkey.size() != 2*AES_256_XTS_KEY_SIZE)
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault /v1/secret/%s request failed: 'key' is empty or has invalid format\n", key_id.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
k.key_state = VAULT_KEY_LOADED;
|
||||
k.key = hexkey;
|
||||
}
|
||||
}
|
||||
if (vault_key_load_queue.empty())
|
||||
{
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
}
|
||||
else
|
||||
vault_load_keys();
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "cluster_client.h"
|
||||
|
||||
#define SCRAP_BUFFER_SIZE 4*1024*1024
|
||||
#define PART_SENT 1
|
||||
#define PART_DONE 2
|
||||
#define PART_ERROR 4
|
||||
|
||||
@@ -88,11 +88,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
// ...or just save it for writeback if write buffering is enabled
|
||||
if (op->len == 0)
|
||||
{
|
||||
// FIXME: OSD_OP_DELETEs are currently only sent by vitastor-cli rm/rm-data and
|
||||
// actually have len=0, because delete is actually a delete of the full object
|
||||
// containing the requested offset, not a "punch hole" operation. But here, writeback
|
||||
// cache assumes it IS a "punch hole" operation. I should select one of these
|
||||
// approaches and fix everything accordingly when I decide to implement TRIM.
|
||||
return;
|
||||
}
|
||||
auto dirty_it = find_dirty(op->inode, op->offset);
|
||||
@@ -249,13 +244,12 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
writeback_queue_size--;
|
||||
}
|
||||
}
|
||||
if (!is_del && op->len > 0)
|
||||
if (!is_del)
|
||||
{
|
||||
uint64_t pos = 0, len = op->len, iov_idx = 0;
|
||||
while (iov_idx < op->iov.count)
|
||||
while (len > 0 && iov_idx < op->iov.count)
|
||||
{
|
||||
auto & iov = op->iov.buf[iov_idx];
|
||||
assert(pos + iov.iov_len <= len);
|
||||
memcpy(buf + pos, iov.iov_base, iov.iov_len);
|
||||
pos += iov.iov_len;
|
||||
iov_idx++;
|
||||
@@ -449,7 +443,7 @@ void writeback_cache_t::start_writebacks(cluster_client_t *cli, int count)
|
||||
started++;
|
||||
assert(writeback_queue_size > 0);
|
||||
writeback_queue_size--;
|
||||
writeback_bytes -= (is_del ? 0 : off - from_it->first.stripe);
|
||||
writeback_bytes -= off - from_it->first.stripe;
|
||||
assert(writeback_queue_size > 0 || !writeback_bytes);
|
||||
flush_buffers(cli, from_it, to_it);
|
||||
}
|
||||
|
||||
+290
-165
@@ -1,7 +1,10 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "malloc_or_die.h"
|
||||
#include "osd_ops.h"
|
||||
#include "msgr_op.h"
|
||||
#include "pg_states.h"
|
||||
#include "etcd_state_client.h"
|
||||
#ifndef __MOCK__
|
||||
@@ -22,14 +25,19 @@ etcd_state_client_t::~etcd_state_client_t()
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
http_destroy(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (keepalive_client)
|
||||
{
|
||||
http_close(keepalive_client);
|
||||
http_destroy(keepalive_client);
|
||||
keepalive_client = NULL;
|
||||
}
|
||||
if (http_ctx)
|
||||
{
|
||||
http_context_destroy(http_ctx);
|
||||
http_ctx = NULL;
|
||||
}
|
||||
#endif
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
@@ -72,55 +80,51 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
|
||||
return addrs;
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
|
||||
http_context_t *etcd_state_client_t::get_http_ctx()
|
||||
{
|
||||
if (!http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
http_ctx = http_context_init(tfd, etcd_client_cert, etcd_client_key, etcd_ca, true, error);
|
||||
if (!http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return http_ctx;
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_oneshot(const std::string & etcd_url, const std::string & api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"+req;
|
||||
auto http_cli = http_init(tfd);
|
||||
auto cb = [http_cli, callback](const http_response_t *response)
|
||||
auto http_cli = http_init(get_http_ctx());
|
||||
http_json_post(http_cli, etcd_url+api, payload, "", { .timeout = timeout }, [http_cli, callback](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
callback(err, data);
|
||||
http_close(http_cli);
|
||||
};
|
||||
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
|
||||
http_destroy(http_cli);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int timeout,
|
||||
void etcd_state_client_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
pick_next_etcd([=]()
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
etcd_call_selected(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_selected(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
req = "POST "+url.path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+url.hostname+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: keep-alive\r\n"
|
||||
@@ -128,15 +132,15 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
cur_addr = selected_etcd_address](const http_response_t *response)
|
||||
cur_addr = url.addr](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
@@ -164,54 +168,58 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
{
|
||||
keepalive_client = http_init(tfd);
|
||||
}
|
||||
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
|
||||
keepalive_client = http_init(get_http_ctx());
|
||||
http_request(keepalive_client, url.addr, req, { .timeout = timeout, .keepalive = true, .ssl = url.ssl }, cb);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string addr)
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string etcd_address)
|
||||
{
|
||||
if (addr.length() > 0)
|
||||
if (etcd_address.size() > 0)
|
||||
{
|
||||
if (strtolower(addr.substr(0, 7)) == "http://")
|
||||
addr = addr.substr(7);
|
||||
else if (strtolower(addr.substr(0, 8)) == "https://")
|
||||
{
|
||||
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!local_ips.size())
|
||||
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
|
||||
std::string check_addr;
|
||||
int pos = addr.find('/');
|
||||
int pos2 = addr.find(':');
|
||||
if (pos2 >= 0)
|
||||
check_addr = addr.substr(0, pos2);
|
||||
else if (pos >= 0)
|
||||
check_addr = addr.substr(0, pos);
|
||||
else
|
||||
check_addr = addr;
|
||||
if (pos == std::string::npos)
|
||||
addr += "/v3";
|
||||
bool local = false;
|
||||
int i;
|
||||
for (i = 0; i < local_ips.size(); i++)
|
||||
{
|
||||
if (local_ips[i] == check_addr)
|
||||
{
|
||||
local = true;
|
||||
break;
|
||||
}
|
||||
// Fill local_ips
|
||||
for (auto & ip: getifaddr_list(std::vector<addr_mask_t>(), true))
|
||||
local_ips.insert(ip);
|
||||
}
|
||||
auto & to = local ? this->etcd_local : this->etcd_addresses;
|
||||
std::string etcd_api_path;
|
||||
bool ssl = false;
|
||||
if (etcd_address.substr(0, 8) == "https://")
|
||||
{
|
||||
ssl = true;
|
||||
etcd_address = etcd_address.substr(8);
|
||||
}
|
||||
else if (etcd_address.substr(0, 7) == "http://")
|
||||
etcd_address = etcd_address.substr(7);
|
||||
auto pos = etcd_address.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
else
|
||||
etcd_api_path = "/v3";
|
||||
pos = etcd_address.find(':');
|
||||
auto check_addr = (pos != std::string::npos ? etcd_address.substr(0, pos) : etcd_address);
|
||||
bool is_local = local_ips.find(check_addr) != local_ips.end();
|
||||
auto & to = (is_local ? etcd_local : etcd_addresses);
|
||||
check_addr = (ssl ? "https://" : "http://") + etcd_address + etcd_api_path;
|
||||
size_t i;
|
||||
for (i = 0; i < to.size(); i++)
|
||||
{
|
||||
if (to[i] == addr)
|
||||
if (to[i] == check_addr)
|
||||
break;
|
||||
}
|
||||
if (i >= to.size())
|
||||
to.push_back(addr);
|
||||
{
|
||||
to.push_back(check_addr);
|
||||
// Check if it's a domain name
|
||||
sockaddr_storage ss;
|
||||
bool is_name = !is_local && !string_to_addr(etcd_address, true, 0, &ss);
|
||||
auto & to_addr = (is_local ? etcd_local_addr_urls : (is_name ? etcd_name_urls : etcd_nonlocal_addr_urls));
|
||||
to_addr.push_back((http_url_t){ .ssl = ssl, .addr = etcd_address, .hostname = etcd_address, .path = etcd_api_path });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +227,9 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_local.clear();
|
||||
this->etcd_addresses.clear();
|
||||
this->etcd_local_addr_urls.clear();
|
||||
this->etcd_nonlocal_addr_urls.clear();
|
||||
this->etcd_name_urls.clear();
|
||||
if (config["etcd_address"].is_string())
|
||||
{
|
||||
std::string ea = config["etcd_address"].string_value();
|
||||
@@ -239,7 +250,19 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
add_etcd_url(ea.string_value());
|
||||
}
|
||||
}
|
||||
if (this->osd_num)
|
||||
{
|
||||
this->etcd_client_cert = config["osd_etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["osd_etcd_client_key"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->etcd_client_cert = config["etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["etcd_client_key"].string_value();
|
||||
}
|
||||
this->etcd_ca = config["etcd_ca"].string_value();
|
||||
this->etcd_prefix = config["etcd_prefix"].string_value();
|
||||
this->use_auth = config["use_auth"].bool_value();
|
||||
if (this->etcd_prefix == "")
|
||||
{
|
||||
this->etcd_prefix = "/vitastor";
|
||||
@@ -291,66 +314,130 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::pick_next_etcd()
|
||||
{
|
||||
if (selected_etcd_address != "")
|
||||
return;
|
||||
if (addresses_to_try.size() == 0)
|
||||
{
|
||||
// Prefer local etcd, if any
|
||||
for (int i = 0; i < etcd_local.size(); i++)
|
||||
addresses_to_try.push_back(etcd_local[i]);
|
||||
std::vector<int> ns;
|
||||
for (int i = 0; i < etcd_addresses.size(); i++)
|
||||
ns.push_back(i);
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
while (ns.size())
|
||||
{
|
||||
int i = lrand48() % ns.size();
|
||||
addresses_to_try.push_back(etcd_addresses[ns[i]]);
|
||||
ns.erase(ns.begin()+i, ns.begin()+i+1);
|
||||
}
|
||||
}
|
||||
selected_etcd_address = addresses_to_try[0];
|
||||
addresses_to_try.erase(addresses_to_try.begin(), addresses_to_try.begin()+1);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher()
|
||||
void etcd_state_client_t::pick_next_etcd(std::function<void()> cb)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
if (selected_etcd_url.addr != "")
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
if (etcd_urls_to_try.size() != 0)
|
||||
{
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
on_resolve_queue.push_back(std::move(cb));
|
||||
if (on_resolve_queue.size() > 1)
|
||||
{
|
||||
// Already resolving
|
||||
return;
|
||||
}
|
||||
assert(!resolve_count);
|
||||
local_to_try = 0;
|
||||
for (auto & url: etcd_local_addr_urls)
|
||||
{
|
||||
// Prefer local IPs, if any
|
||||
etcd_urls_to_try.push_back(url);
|
||||
local_to_try++;
|
||||
}
|
||||
for (auto & url: etcd_nonlocal_addr_urls)
|
||||
{
|
||||
etcd_urls_to_try.push_back(url);
|
||||
}
|
||||
resolve_count++;
|
||||
for (auto & url: etcd_name_urls)
|
||||
{
|
||||
resolve_count++;
|
||||
http_resolve(get_http_ctx(), url.ssl, url.addr, [this, url](const std::string & error, const std::vector<std::string>& addresses)
|
||||
{
|
||||
if (error != "")
|
||||
fprintf(stderr, "Error resolving %s: %s\n", url.addr.c_str(), error.c_str());
|
||||
for (auto & addr: addresses)
|
||||
{
|
||||
auto url_copy = url;
|
||||
url_copy.addr = addr;
|
||||
if (local_ips.find(addr) != local_ips.end())
|
||||
{
|
||||
etcd_urls_to_try.insert(etcd_urls_to_try.begin(), std::move(url_copy));
|
||||
local_to_try++;
|
||||
}
|
||||
else
|
||||
etcd_urls_to_try.push_back(std::move(url_copy));
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
pick_next_etcd_on_resolve();
|
||||
});
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
{
|
||||
pick_next_etcd_on_resolve();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::pick_next_etcd_on_resolve()
|
||||
{
|
||||
if (!etcd_urls_to_try.size())
|
||||
{
|
||||
fprintf(stderr, "None of etcd_address could be resolved\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
// Shuffle addresses
|
||||
for (size_t i = etcd_urls_to_try.size()-1; i > local_to_try; i--)
|
||||
{
|
||||
size_t j = local_to_try + lrand48() % (i - local_to_try);
|
||||
if (j != i)
|
||||
std::swap(etcd_urls_to_try[i], etcd_urls_to_try[j]);
|
||||
}
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
auto cbs = std::move(on_resolve_queue);
|
||||
for (auto cb: cbs)
|
||||
{
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher()
|
||||
{
|
||||
pick_next_etcd([this]()
|
||||
{
|
||||
start_etcd_watcher_selected();
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher_selected()
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
etcd_watches_initialised = 0;
|
||||
ws_alive = 1;
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (this->log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s, watch from revision %ju/%ju/%ju\n", etcd_address.c_str(),
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s%s%s (hostname %s), watch from revision %ju/%ju/%ju\n",
|
||||
url.ssl ? "https://" : "http://", url.addr.c_str(), url.path.c_str(), url.hostname.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
etcd_watch_ws = open_websocket(tfd, etcd_address, etcd_api_path+"/watch", etcd_slow_timeout,
|
||||
[this, cur_addr = selected_etcd_address](const http_response_t *msg)
|
||||
if (!etcd_watch_ws)
|
||||
etcd_watch_ws = http_init(get_http_ctx());
|
||||
else
|
||||
http_close(etcd_watch_ws);
|
||||
open_websocket(etcd_watch_ws, url.addr, url.hostname, url.path+"/watch", { .timeout = etcd_slow_timeout, .ssl = url.ssl },
|
||||
[this, cur_addr = url.addr](http_message_t *msg)
|
||||
{
|
||||
if (msg->body.length())
|
||||
{
|
||||
@@ -393,7 +480,6 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
@@ -438,7 +524,7 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
addresses_to_try.clear();
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
@@ -468,13 +554,8 @@ void etcd_state_client_t::start_etcd_watcher()
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (etcd_watches_initialised == 0)
|
||||
{
|
||||
// Connection not established, retry in <etcd_quick_timeout>
|
||||
@@ -549,12 +630,7 @@ void etcd_state_client_t::start_ws_keepalive()
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_address.c_str());
|
||||
}
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
@@ -942,6 +1018,8 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
pc.used_for_app = "fs:"+pc.used_for_app;
|
||||
else
|
||||
pc.used_for_app = pool_item.second["used_for_app"].as_string();
|
||||
// Create group permission
|
||||
pc.creator_group = pool_item.second["creator_group"].string_value();
|
||||
// Local Read Configuration
|
||||
std::string local_reads = pool_item.second["local_reads"].string_value();
|
||||
if (local_reads == "nearest")
|
||||
@@ -1185,6 +1263,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (i >= pg_state_bit_count)
|
||||
{
|
||||
fprintf(stderr, "Unexpected pool %u PG %u state keyword in etcd: %s\n", pool_id, pg_num, e.dump().c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!cur_primary || !value["state"].is_array() || !state ||
|
||||
@@ -1193,6 +1272,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
(state & PG_INCOMPLETE) && state != PG_INCOMPLETE && state != (PG_INCOMPLETE|PG_HAS_INVALID))
|
||||
{
|
||||
fprintf(stderr, "Unexpected pool %u PG %u state in etcd: primary=%ju, state=%s\n", pool_id, pg_num, cur_primary, value["state"].dump().c_str());
|
||||
return;
|
||||
}
|
||||
pg_cfg.cur_primary = cur_primary;
|
||||
pg_cfg.cur_state = state;
|
||||
@@ -1263,33 +1343,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
}
|
||||
else
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num |= pool_id << (64-POOL_ID_BITS);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %ju/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
inode_num >> (64-POOL_ID_BITS), inode_num & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
insert_inode_config((inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = kv.mod_revision,
|
||||
});
|
||||
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1300,6 +1354,14 @@ 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 (use_auth && key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
|
||||
{
|
||||
// <etcd_prefix>/config/user/<username>
|
||||
if (!value.is_object())
|
||||
user_info.erase(key.substr(etcd_prefix.length()+13));
|
||||
else
|
||||
user_info[key.substr(etcd_prefix.length()+13)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
|
||||
@@ -1378,6 +1440,10 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
new_cfg["parent_pool"] = (uint64_t)INODE_POOL(cfg->parent_id);
|
||||
new_cfg["parent_id"] = (uint64_t)INODE_NO_POOL(cfg->parent_id);
|
||||
}
|
||||
if (!cfg->enc_key.empty())
|
||||
{
|
||||
new_cfg["enc_key"] = cfg->enc_key;
|
||||
}
|
||||
if (cfg->readonly)
|
||||
{
|
||||
new_cfg["readonly"] = true;
|
||||
@@ -1386,6 +1452,18 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
{
|
||||
new_cfg["deleted"] = true;
|
||||
}
|
||||
if (!cfg->owner.empty())
|
||||
{
|
||||
new_cfg["owner"] = cfg->owner;
|
||||
}
|
||||
if (!cfg->owner_group.empty())
|
||||
{
|
||||
new_cfg["owner_group"] = cfg->owner_group;
|
||||
}
|
||||
if (!cfg->reader_group.empty())
|
||||
{
|
||||
new_cfg["reader_group"] = cfg->reader_group;
|
||||
}
|
||||
if (cfg->meta.is_object())
|
||||
{
|
||||
new_cfg["meta"] = cfg->meta;
|
||||
@@ -1393,6 +1471,53 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
return new_cfg;
|
||||
}
|
||||
|
||||
inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision)
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !INODE_POOL(parent_inode_num))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num = INODE_WITH_POOL(INODE_POOL(inode_num), parent_inode_num);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %u/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
std::string enc_key;
|
||||
if (!value["enc_key"].is_null())
|
||||
{
|
||||
enc_key = value["enc_key"].string_value();
|
||||
if (enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) != VAULT_KEY_PREFIX &&
|
||||
(enc_key.size() != 2*AES_256_XTS_KEY_SIZE || !ishexstr(enc_key)))
|
||||
{
|
||||
enc_key = "";
|
||||
fprintf(stderr, "Inode %u/%ju has invalid enc_key, should be %u bit hex string or Vault key reference\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num), AES_256_XTS_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
return (inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.enc_key = std::move(enc_key),
|
||||
.owner = value["owner"].string_value(),
|
||||
.owner_group = value["owner_group"].string_value(),
|
||||
.reader_group = value["reader_group"].string_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = mod_revision,
|
||||
};
|
||||
}
|
||||
|
||||
int etcd_state_client_t::address_count()
|
||||
{
|
||||
return etcd_addresses.size() + etcd_local.size();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "json11/json11.hpp"
|
||||
#include "object_id.h"
|
||||
@@ -19,6 +20,8 @@
|
||||
#define MAX_DATA_BLOCK_SIZE 128*1024*1024
|
||||
#define DEFAULT_BITMAP_GRANULARITY 4096
|
||||
|
||||
#define VAULT_KEY_PREFIX "vault:"
|
||||
|
||||
#ifndef IMMEDIATE_NONE
|
||||
#define IMMEDIATE_NONE 0
|
||||
#define IMMEDIATE_SMALL 1
|
||||
@@ -66,6 +69,7 @@ struct pool_config_t
|
||||
std::map<pg_num_t, pg_config_t> pg_config;
|
||||
uint64_t scrub_interval = 0;
|
||||
std::string used_for_app;
|
||||
std::string creator_group;
|
||||
int backfillfull = 0;
|
||||
int local_reads = 0;
|
||||
|
||||
@@ -83,6 +87,9 @@ struct inode_config_t
|
||||
inode_t parent_id = 0;
|
||||
bool readonly = false;
|
||||
bool deleted = false;
|
||||
std::string enc_key;
|
||||
// Permissions
|
||||
std::string owner, owner_group, reader_group;
|
||||
// Arbitrary metadata
|
||||
json11::Json meta;
|
||||
// Change revision of the metadata in etcd
|
||||
@@ -95,23 +102,41 @@ struct inode_watch_t
|
||||
inode_config_t cfg = {};
|
||||
};
|
||||
|
||||
struct http_url_t
|
||||
{
|
||||
bool ssl;
|
||||
std::string addr;
|
||||
std::string hostname;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct http_co_t;
|
||||
struct http_context_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
std::vector<std::string> local_ips;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::set<std::string> local_ips;
|
||||
std::vector<std::string> etcd_local;
|
||||
std::string selected_etcd_address;
|
||||
std::vector<std::string> addresses_to_try;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::vector<http_url_t> etcd_local_addr_urls;
|
||||
std::vector<http_url_t> etcd_nonlocal_addr_urls;
|
||||
std::vector<http_url_t> etcd_name_urls;
|
||||
size_t local_to_try = 0;
|
||||
std::vector<http_url_t> etcd_urls_to_try;
|
||||
http_url_t selected_etcd_url;
|
||||
size_t resolve_count = 0;
|
||||
std::vector<inode_watch_t*> watches;
|
||||
std::vector<std::function<void()>> on_resolve_queue;
|
||||
bool new_pg_config = false;
|
||||
int ws_keepalive_timer = -1;
|
||||
int ws_alive = 0;
|
||||
bool rand_initialized = false;
|
||||
void add_etcd_url(std::string);
|
||||
void pick_next_etcd();
|
||||
void pick_next_etcd(std::function<void()> cb);
|
||||
void pick_next_etcd_on_resolve();
|
||||
void etcd_call_selected(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void start_etcd_watcher_selected();
|
||||
public:
|
||||
int etcd_keepalive_timeout = 30;
|
||||
int etcd_ws_keepalive_interval = 5;
|
||||
@@ -120,14 +145,20 @@ public:
|
||||
int etcd_slow_timeout = 5000;
|
||||
int etcd_min_reload_interval = 1000;
|
||||
bool infinite_start = true;
|
||||
bool use_auth = false;
|
||||
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
|
||||
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
||||
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
||||
|
||||
uint64_t osd_num = 0;
|
||||
std::string etcd_prefix;
|
||||
std::string etcd_client_cert;
|
||||
std::string etcd_client_key;
|
||||
std::string etcd_ca;
|
||||
int log_level = 0;
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
|
||||
http_context_t *http_ctx = NULL;
|
||||
http_co_t *etcd_watch_ws = NULL, *keepalive_client = NULL;
|
||||
int etcd_watches_initialised = 0;
|
||||
uint64_t etcd_watch_revision_config = 0;
|
||||
@@ -140,6 +171,7 @@ public:
|
||||
std::set<osd_num_t> seen_peers;
|
||||
std::map<inode_t, inode_config_t> inode_config;
|
||||
std::map<std::string, inode_t> inode_by_name;
|
||||
std::map<std::string, json11::Json> user_info;
|
||||
json11::Json node_placement;
|
||||
|
||||
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
||||
@@ -158,10 +190,12 @@ public:
|
||||
std::function<void(http_co_t *)> on_start_watcher_hook;
|
||||
|
||||
json11::Json::object serialize_inode_cfg(inode_config_t *cfg);
|
||||
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
|
||||
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
|
||||
std::vector<std::string> get_addresses();
|
||||
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
http_context_t *get_http_ctx();
|
||||
void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_txn(json11::Json txn, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_txn_slow(json11::Json txn, std::function<void(std::string, json11::Json)> callback);
|
||||
void start_etcd_watcher();
|
||||
|
||||
+727
-87
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,10 @@
|
||||
#include <functional>
|
||||
#include "json11/json11.hpp"
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/types.h>
|
||||
#endif
|
||||
|
||||
#define WS_CONTINUATION 0
|
||||
#define WS_TEXT 1
|
||||
#define WS_BINARY 2
|
||||
@@ -17,14 +21,19 @@
|
||||
|
||||
class timerfd_manager_t;
|
||||
|
||||
#pragma GCC visibility push(default)
|
||||
|
||||
struct http_options_t
|
||||
{
|
||||
int timeout;
|
||||
bool want_streaming;
|
||||
bool keepalive;
|
||||
bool ssl;
|
||||
};
|
||||
|
||||
struct http_response_t
|
||||
struct http_context_t;
|
||||
|
||||
struct http_message_t
|
||||
{
|
||||
std::string error;
|
||||
|
||||
@@ -41,10 +50,34 @@ struct http_response_t
|
||||
// Opened websocket or keepalive HTTP connection
|
||||
struct http_co_t;
|
||||
|
||||
http_co_t* http_init(timerfd_manager_t *tfd);
|
||||
http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, const std::string & path,
|
||||
int timeout, std::function<void(const http_response_t *msg)> on_message);
|
||||
http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key,
|
||||
const std::string & ssl_ca, bool verify_peer, std::string & error);
|
||||
std::string http_context_get_ssl_cn(http_context_t *ctx);
|
||||
void http_resolve(http_context_t *ctx, bool ssl, std::string host,
|
||||
std::function<void(const std::string & error, const std::vector<std::string> & addrs)> cb);
|
||||
void http_context_destroy(http_context_t *ctx);
|
||||
http_co_t* http_init(http_context_t *ctx = NULL);
|
||||
void open_websocket(http_co_t *handler, const std::string & addr, const std::string & hostname, const std::string & path,
|
||||
const http_options_t & options, std::function<void(http_message_t *msg)> on_message);
|
||||
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
|
||||
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_get(http_co_t *handler, const std::string & url, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_json_post(http_co_t *handler, const std::string & url, json11::Json body, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_post_message(http_co_t *handler, uint8_t type, const std::string & msg);
|
||||
void http_serve(http_co_t *handler, int peer_fd, const http_options_t & options,
|
||||
std::function<void(http_message_t *msg)> request_callback);
|
||||
void http_reply(http_co_t *handler, const std::string & reply);
|
||||
void http_close(http_co_t *co);
|
||||
void http_destroy(http_co_t *co);
|
||||
|
||||
#pragma GCC visibility pop
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
bool openssl_ctx_add_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem);
|
||||
bool openssl_ctx_use_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem);
|
||||
std::string openssl_get_cn(X509 *x509);
|
||||
bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem, std::string & common_name);
|
||||
bool openssl_ctx_use_key(SSL_CTX *ssl_ctx, const std::string & file_or_pem);
|
||||
#endif
|
||||
|
||||
+274
-8
@@ -10,13 +10,165 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "addr_util.h"
|
||||
#include "str_util.h"
|
||||
#include "messenger.h"
|
||||
#ifdef WITH_RDMA
|
||||
#include "msgr_rdma.h"
|
||||
#endif
|
||||
#include "http_client.h"
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
#include <sys/poll.h>
|
||||
|
||||
msgr_iothread_t::msgr_iothread_t():
|
||||
ring(RINGLOOP_DEFAULT_SIZE, true),
|
||||
thread(&msgr_iothread_t::run, this)
|
||||
{
|
||||
eventfd = ring.register_eventfd();
|
||||
if (eventfd < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("failed to register eventfd: ") + strerror(-eventfd));
|
||||
}
|
||||
}
|
||||
|
||||
msgr_iothread_t::~msgr_iothread_t()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_sqe(io_uring_sqe & sqe)
|
||||
{
|
||||
mu.lock();
|
||||
queue.push_back((iothread_sqe_t){ .sqe = sqe, .data = std::move(*(ring_data_t*)sqe.user_data) });
|
||||
if (queue.size() == 1)
|
||||
{
|
||||
cond.notify_all();
|
||||
}
|
||||
mu.unlock();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::stop()
|
||||
{
|
||||
mu.lock();
|
||||
if (stopped)
|
||||
{
|
||||
mu.unlock();
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
if (outer_loop_data)
|
||||
{
|
||||
outer_loop_data->callback = [](ring_data_t*){};
|
||||
}
|
||||
cond.notify_all();
|
||||
close(eventfd);
|
||||
mu.unlock();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_to_ringloop(ring_loop_t *outer_loop)
|
||||
{
|
||||
assert(!this->outer_loop || this->outer_loop == outer_loop);
|
||||
io_uring_sqe *sqe = outer_loop->get_sqe();
|
||||
assert(sqe != NULL);
|
||||
this->outer_loop = outer_loop;
|
||||
this->outer_loop_data = ((ring_data_t*)sqe->user_data);
|
||||
io_uring_prep_poll_add(sqe, eventfd, POLLIN);
|
||||
outer_loop_data->callback = [this](ring_data_t *data)
|
||||
{
|
||||
if (data->res < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("eventfd poll failed: ") + strerror(-data->res));
|
||||
}
|
||||
outer_loop_data = NULL;
|
||||
if (stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
add_to_ringloop(this->outer_loop);
|
||||
ring.loop();
|
||||
};
|
||||
}
|
||||
|
||||
void msgr_iothread_t::run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mu);
|
||||
while (!stopped && !queue.size())
|
||||
cond.wait(lk);
|
||||
if (stopped)
|
||||
return;
|
||||
int i = 0;
|
||||
for (; i < queue.size(); i++)
|
||||
{
|
||||
io_uring_sqe *sqe = ring.get_sqe();
|
||||
if (!sqe)
|
||||
break;
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
*data = std::move(queue[i].data);
|
||||
*sqe = queue[i].sqe;
|
||||
sqe->user_data = (uint64_t)data;
|
||||
}
|
||||
queue.erase(queue.begin(), queue.begin()+i);
|
||||
}
|
||||
// We only want to offload sendmsg/recvmsg. Callbacks will be called in main thread
|
||||
ring.submit();
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init()
|
||||
{
|
||||
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
|
||||
{
|
||||
// Initialize TLS context
|
||||
// FIXME: require OpenSSL
|
||||
#ifndef WITH_OPENSSL
|
||||
fprintf(stderr, "Vitastor is built without OpenSSL support\n");
|
||||
exit(1);
|
||||
#else
|
||||
if (tls_cert.empty() || tls_key.empty() || osd_tls_ca.empty() || osd_num && client_tls_ca.empty())
|
||||
{
|
||||
if (osd_num)
|
||||
fprintf(stderr, "Vitastor OSD TLS requires osd_tls_cert, osd_tls_key, osd_tls_ca, client_tls_ca\n");
|
||||
else
|
||||
fprintf(stderr, "Vitastor client TLS requires tls_cert, tls_key and osd_tls_ca\n");
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl_ctx = SSL_CTX_new(TLS_method());
|
||||
if (!ssl_ctx)
|
||||
{
|
||||
init_err:
|
||||
fprintf(stderr, "OpenSSL initialization failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
|
||||
exit(1);
|
||||
}
|
||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
bool ok = SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||
ok = ok && openssl_ctx_add_ca(ssl_ctx, osd_tls_ca);
|
||||
if (osd_num)
|
||||
{
|
||||
// OSD uses 2 separate root certificates to distinguish between clients and peer OSDs
|
||||
ok = ok && openssl_ctx_add_ca(ssl_ctx, client_tls_ca);
|
||||
}
|
||||
ok = ok && openssl_ctx_use_cert(ssl_ctx, tls_cert, tls_cn);
|
||||
ok = ok && openssl_ctx_use_key(ssl_ctx, tls_key);
|
||||
if (!ok)
|
||||
{
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
ssl_ctx = NULL;
|
||||
goto init_err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_RDMACM
|
||||
if (use_rdmacm)
|
||||
{
|
||||
@@ -73,7 +225,12 @@ void osd_messenger_t::init()
|
||||
}
|
||||
if (ringloop && iothread_count > 0)
|
||||
{
|
||||
init_iothreads();
|
||||
for (int i = 0; i < iothread_count; i++)
|
||||
{
|
||||
auto iot = new msgr_iothread_t();
|
||||
iothreads.push_back(iot);
|
||||
iot->add_to_ringloop(ringloop);
|
||||
}
|
||||
}
|
||||
keepalive_timer_id = tfd->set_timer(1000, true, [this](int)
|
||||
{
|
||||
@@ -115,7 +272,7 @@ void osd_messenger_t::init()
|
||||
.opcode = OSD_OP_PING,
|
||||
},
|
||||
};
|
||||
op->callback = [this](osd_op_t *op)
|
||||
op->callback = [this, cl](osd_op_t *op)
|
||||
{
|
||||
auto cl_it = clients.find(op->client_id);
|
||||
if (cl_it == clients.end())
|
||||
@@ -124,7 +281,6 @@ void osd_messenger_t::init()
|
||||
delete op;
|
||||
return;
|
||||
}
|
||||
auto cl = cl_it->second;
|
||||
uint64_t fail_client_id = (op->reply.hdr.retval != 0 ? op->client_id : 0);
|
||||
auto fail_osd_num = cl->in_osd_num ? cl->in_osd_num : cl->osd_num;
|
||||
cl->ping_time_remaining = 0;
|
||||
@@ -167,7 +323,14 @@ osd_messenger_t::~osd_messenger_t()
|
||||
{
|
||||
stop_client(clients.begin()->first, true);
|
||||
}
|
||||
destroy_iothreads();
|
||||
if (iothreads.size())
|
||||
{
|
||||
for (auto iot: iothreads)
|
||||
{
|
||||
delete iot;
|
||||
}
|
||||
iothreads.clear();
|
||||
}
|
||||
#ifdef WITH_RDMA
|
||||
for (auto rdma_context: rdma_contexts)
|
||||
{
|
||||
@@ -183,6 +346,21 @@ osd_messenger_t::~osd_messenger_t()
|
||||
rdma_destroy_event_channel(rdmacm_evch);
|
||||
rdmacm_evch = NULL;
|
||||
}
|
||||
#endif
|
||||
for (auto encrypt_ctx: encrypt_ctx_pool)
|
||||
{
|
||||
destroy_aes_xts_encrypt(encrypt_ctx);
|
||||
}
|
||||
for (auto decrypt_ctx: decrypt_ctx_pool)
|
||||
{
|
||||
destroy_aes_xts_decrypt(decrypt_ctx);
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
if (ssl_ctx)
|
||||
{
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
ssl_ctx = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -218,6 +396,33 @@ void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
|
||||
this->rdma_max_msg = 129*1024;
|
||||
#endif
|
||||
this->max_aes_xts_pool_size = config["max_aes_xts_pool_size"].uint64_value();
|
||||
if (!this->max_aes_xts_pool_size)
|
||||
this->max_aes_xts_pool_size = 256;
|
||||
if (config["proto_checksums"].is_null())
|
||||
this->use_proto_checksums = MSGR_CSUM_PAYLOAD;
|
||||
else if (config["proto_checksums"].is_bool())
|
||||
this->use_proto_checksums = config["proto_checksums"].bool_value() ? MSGR_CSUM_FULL : 0;
|
||||
else if (config["proto_checksums"].string_value() != "")
|
||||
this->use_proto_checksums = config["proto_checksums"].string_value() == "full" ? MSGR_CSUM_FULL : MSGR_CSUM_PAYLOAD;
|
||||
else
|
||||
this->use_proto_checksums = 0;
|
||||
if (!osd_num)
|
||||
{
|
||||
tls_cert = config["tls_cert"].string_value();
|
||||
tls_key = config["tls_key"].string_value();
|
||||
osd_tls_ca = config["osd_tls_ca"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
tls_cert = config["osd_tls_cert"].string_value();
|
||||
tls_key = config["osd_tls_key"].string_value();
|
||||
osd_tls_ca = config["osd_tls_ca"].string_value();
|
||||
client_tls_ca = config["client_tls_ca"].string_value();
|
||||
}
|
||||
test_osd_aes_key.resize(32);
|
||||
if (fromhexstr(config["test_osd_aes_key"].string_value(), 32, (uint8_t*)test_osd_aes_key.data()) != 32)
|
||||
test_osd_aes_key.clear();
|
||||
if (!osd_num)
|
||||
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
|
||||
else
|
||||
@@ -226,7 +431,7 @@ void osd_messenger_t::parse_config(const json11::Json & config)
|
||||
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
|
||||
this->receive_buffer_size = 65536;
|
||||
this->use_sync_send_recv = config["use_sync_send_recv"].bool_value() ||
|
||||
config["use_sync_send_recv"].uint64_value();
|
||||
config["use_sync_send_recv"].uint64_value() || !ringloop;
|
||||
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
|
||||
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
|
||||
: (int)config["min_zerocopy_send_size"].int64_value();
|
||||
@@ -399,7 +604,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
|
||||
cl->peer_state = PEER_CONNECTING;
|
||||
cl->connect_timeout_id = -1;
|
||||
cl->osd_num = peer_osd;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
clients[client_id] = cl;
|
||||
clients_by_fd[peer_fd] = cl;
|
||||
tfd->set_fd_handler(peer_fd, true, [this](int peer_fd, int epoll_events)
|
||||
@@ -448,6 +653,7 @@ void osd_messenger_t::handle_connect_epoll(int peer_fd)
|
||||
handle_peer_epoll(peer_fd, epoll_events);
|
||||
});
|
||||
// Check OSD number
|
||||
ssl_init(cl, false);
|
||||
check_peer_config(cl);
|
||||
}
|
||||
|
||||
@@ -537,7 +743,12 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
// Inform that we're OSD <osd_num>
|
||||
payload["osd_num"] = osd_num;
|
||||
}
|
||||
payload["features"] = json11::Json::object{ { "check_sequencing", true } };
|
||||
auto features = json11::Json::object{ { "check_sequencing", true } };
|
||||
if (use_proto_checksums)
|
||||
{
|
||||
features["proto_checksums"] = use_proto_checksums;
|
||||
}
|
||||
payload["features"] = features;
|
||||
#ifdef WITH_RDMA
|
||||
if (!use_rdmacm && rdma_contexts.size())
|
||||
{
|
||||
@@ -612,6 +823,14 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
|
||||
delete op;
|
||||
return;
|
||||
}
|
||||
if (use_proto_checksums)
|
||||
{
|
||||
auto peer_csums = config["features"]["proto_checksums"].uint64_value();
|
||||
if (peer_csums == MSGR_CSUM_FULL && use_proto_checksums == MSGR_CSUM_FULL)
|
||||
cl->proto_csum_status = MSGR_CSUM_FULL;
|
||||
else if (peer_csums && use_proto_checksums)
|
||||
cl->proto_csum_status = MSGR_CSUM_PAYLOAD;
|
||||
}
|
||||
#ifdef WITH_RDMA
|
||||
if (!use_rdmacm && cl->rdma_conn && config["rdma_address"].is_string())
|
||||
{
|
||||
@@ -675,7 +894,8 @@ void osd_messenger_t::accept_connections(int listen_fd)
|
||||
cl->peer_port = ntohs(((sockaddr_in*)&addr)->sin_port);
|
||||
cl->peer_fd = peer_fd;
|
||||
cl->peer_state = PEER_CONNECTED;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
ssl_init(cl, true);
|
||||
// Add FD to epoll
|
||||
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
|
||||
{
|
||||
@@ -690,6 +910,52 @@ void osd_messenger_t::accept_connections(int listen_fd)
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::ssl_init(osd_client_t *cl, bool server_mode)
|
||||
{
|
||||
if (!tls_cert.empty())
|
||||
{
|
||||
cl->write_to_ssl = BIO_new(BIO_s_mem());
|
||||
cl->read_from_ssl = BIO_new(BIO_s_mem());
|
||||
cl->ssl_cli = SSL_new(ssl_ctx);
|
||||
if (!cl->ssl_cli)
|
||||
{
|
||||
fprintf(stderr, "OpenSSL initialization failed: %s\n", ERR_error_string(ERR_get_error(), NULL));
|
||||
exit(1);
|
||||
}
|
||||
if (server_mode)
|
||||
{
|
||||
SSL_set_accept_state(cl->ssl_cli);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_set_connect_state(cl->ssl_cli);
|
||||
}
|
||||
SSL_set_bio(cl->ssl_cli, cl->write_to_ssl, cl->read_from_ssl);
|
||||
bool ok = ssl_do_handshake(cl);
|
||||
assert(ok);
|
||||
}
|
||||
else if (!test_osd_aes_key.empty())
|
||||
{
|
||||
int r;
|
||||
cl->enc_ctx = EVP_CIPHER_CTX_new();
|
||||
assert(cl->enc_ctx);
|
||||
r = EVP_EncryptInit_ex(cl->enc_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
|
||||
assert(r == 1);
|
||||
r = EVP_CIPHER_CTX_set_padding(cl->enc_ctx, 0);
|
||||
assert(r == 1);
|
||||
r = EVP_CIPHER_CTX_ctrl(cl->enc_ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
|
||||
assert(r == 1);
|
||||
cl->dec_ctx = EVP_CIPHER_CTX_new();
|
||||
assert(cl->dec_ctx);
|
||||
r = EVP_DecryptInit_ex(cl->dec_ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
|
||||
assert(r == 1);
|
||||
r = EVP_CIPHER_CTX_set_padding(cl->dec_ctx, 0);
|
||||
assert(r == 1);
|
||||
r = EVP_CIPHER_CTX_ctrl(cl->dec_ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
|
||||
assert(r == 1);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
msgr_rdma_context_t* osd_messenger_t::choose_rdma_context(osd_client_t *cl)
|
||||
{
|
||||
|
||||
+155
-19
@@ -12,6 +12,11 @@
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/types.h>
|
||||
#endif
|
||||
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/robin_hood.h"
|
||||
#include "malloc_or_die.h"
|
||||
#include "json11/json11.hpp"
|
||||
@@ -31,13 +36,14 @@
|
||||
#define PEER_RDMA 4
|
||||
#define PEER_STOPPED 5
|
||||
|
||||
#define MSGR_CSUM_PAYLOAD 1
|
||||
#define MSGR_CSUM_FULL 2
|
||||
#define MSGR_CSUM_NEG 4
|
||||
|
||||
#define VITASTOR_CONFIG_PATH "/etc/vitastor/vitastor.conf"
|
||||
|
||||
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
|
||||
|
||||
#define MSGR_SENDP_HDR 1
|
||||
#define MSGR_SENDP_FREE 2
|
||||
|
||||
struct msgr_sendp_t
|
||||
{
|
||||
osd_op_t *op;
|
||||
@@ -49,6 +55,19 @@ struct msgr_rdma_connection_t;
|
||||
struct msgr_rdma_context_t;
|
||||
#endif
|
||||
|
||||
struct op_aes_xts_encrypt_t;
|
||||
struct op_aes_xts_decrypt_t;
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
|
||||
|
||||
// Standard TLS record header. We are only interested in the record size
|
||||
struct __attribute__((__packed__)) msgr_tls_record_hdr_t
|
||||
{
|
||||
uint8_t content_type;
|
||||
uint16_t version;
|
||||
uint16_t size;
|
||||
};
|
||||
|
||||
struct osd_client_t
|
||||
{
|
||||
uint64_t client_id = 0;
|
||||
@@ -65,23 +84,54 @@ struct osd_client_t
|
||||
osd_num_t in_osd_num = 0;
|
||||
bool is_incoming = false;
|
||||
|
||||
void *in_buf = NULL;
|
||||
uint8_t *in_buf = NULL;
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
msgr_rdma_connection_t *rdma_conn = NULL;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
SSL *ssl_cli = NULL;
|
||||
BIO *write_to_ssl = NULL;
|
||||
// FIXME: use custom bio to avoid 1 more memory copy?
|
||||
BIO *read_from_ssl = NULL;
|
||||
uint8_t *ssl_out_buf = NULL;
|
||||
size_t ssl_out_buf_size = 0, ssl_out_buf_cap = 0;
|
||||
bool ssl_handshake_done = false;
|
||||
msgr_tls_record_hdr_t ssl_read_record;
|
||||
size_t ssl_read_header_size = 0;
|
||||
bool ssl_more_to_buffer = false;
|
||||
|
||||
EVP_CIPHER_CTX *enc_ctx = NULL;
|
||||
uint8_t enc_tag[16];
|
||||
size_t enc_tag_size = 0;
|
||||
bool enc_batch = false;
|
||||
EVP_CIPHER_CTX *dec_ctx = NULL;
|
||||
uint8_t dec_tag[16];
|
||||
size_t dec_tag_size = 0;
|
||||
uint32_t dec_batch_size = 0;
|
||||
size_t dec_batch_size_size = 0;
|
||||
std::vector<osd_op_t*> unverified_ops;
|
||||
#endif
|
||||
|
||||
// Read state
|
||||
bool io_error = false;
|
||||
int read_ready = 0;
|
||||
osd_op_t *read_op = NULL;
|
||||
size_t read_op_size = 0;
|
||||
size_t read_op_pos = 0;
|
||||
iovec read_iov = { 0 };
|
||||
msghdr read_msg = { 0 };
|
||||
int read_remaining = 0;
|
||||
int read_state = 0;
|
||||
osd_op_buf_list_t recv_list;
|
||||
std::vector<iovec> recv_list;
|
||||
std::vector<int> recv_flags;
|
||||
uint64_t read_op_id = 1;
|
||||
bool check_sequencing = false;
|
||||
bool enable_pg_locks = false;
|
||||
op_aes_xts_decrypt_t *decrypt_ctx = NULL;
|
||||
size_t read_op_inline_decrypt_pos = 0;
|
||||
size_t read_op_inline_decrypt_in = 0;
|
||||
int proto_csum_status = 0;
|
||||
XXH3_state_t* read_csum_state = NULL;
|
||||
|
||||
// Incoming operations
|
||||
std::vector<osd_op_t*> received_ops;
|
||||
@@ -94,11 +144,17 @@ struct osd_client_t
|
||||
std::set<pool_pg_num_t> dirty_pgs;
|
||||
|
||||
// Write state
|
||||
std::deque<osd_op_t *> write_ops;
|
||||
osd_op_t *write_op = NULL;
|
||||
size_t write_op_pos = 0;
|
||||
msghdr write_msg = { 0 };
|
||||
int write_state = 0;
|
||||
std::vector<iovec> send_list, next_send_list;
|
||||
std::vector<msgr_sendp_t> outbox, next_outbox;
|
||||
std::vector<iovec> send_list;
|
||||
size_t send_list_size = 0;
|
||||
std::deque<osd_op_t*> send_free_ops;
|
||||
std::vector<osd_op_t*> zc_free_list;
|
||||
op_aes_xts_encrypt_t *encrypt_ctx = NULL;
|
||||
XXH3_state_t* write_csum_state = NULL;
|
||||
|
||||
~osd_client_t();
|
||||
void cancel_ops();
|
||||
@@ -129,7 +185,43 @@ struct osd_op_stats_t
|
||||
uint64_t subop_stat_count[OSD_OP_MAX+1] = { 0 };
|
||||
};
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
|
||||
#ifdef __MOCK__
|
||||
class msgr_iothread_t;
|
||||
#else
|
||||
struct iothread_sqe_t
|
||||
{
|
||||
io_uring_sqe sqe;
|
||||
ring_data_t data;
|
||||
};
|
||||
|
||||
class msgr_iothread_t
|
||||
{
|
||||
protected:
|
||||
ring_loop_t ring;
|
||||
ring_loop_t *outer_loop = NULL;
|
||||
ring_data_t *outer_loop_data = NULL;
|
||||
int eventfd = -1;
|
||||
bool stopped = false;
|
||||
std::mutex mu;
|
||||
std::condition_variable cond;
|
||||
std::vector<iothread_sqe_t> queue;
|
||||
std::thread thread;
|
||||
|
||||
void run();
|
||||
public:
|
||||
|
||||
msgr_iothread_t();
|
||||
~msgr_iothread_t();
|
||||
|
||||
void add_sqe(io_uring_sqe & sqe);
|
||||
void stop();
|
||||
void add_to_ringloop(ring_loop_t *outer_loop);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
struct rdma_event_channel;
|
||||
@@ -140,9 +232,21 @@ struct osd_messenger_t;
|
||||
struct rdmacm_connecting_t;
|
||||
#endif
|
||||
|
||||
class msgr_op_reader_t;
|
||||
class msgr_op_writer_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) osd_messenger_t
|
||||
{
|
||||
protected:
|
||||
friend class copy_op_reader_t;
|
||||
friend class ssl_op_reader_t;
|
||||
friend class gcm_op_reader_t;
|
||||
friend class get_op_reader_t;
|
||||
friend class copy_op_writer_t;
|
||||
friend class ssl_op_writer_t;
|
||||
friend class gcm_op_writer_t;
|
||||
friend class get_op_writer_t;
|
||||
|
||||
int keepalive_timer_id = -1;
|
||||
|
||||
uint32_t receive_buffer_size = 0;
|
||||
@@ -154,6 +258,13 @@ protected:
|
||||
bool use_sync_send_recv = false;
|
||||
int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE;
|
||||
int iothread_count = 0;
|
||||
int max_aes_xts_pool_size = 256;
|
||||
|
||||
std::string tls_cert;
|
||||
std::string tls_key;
|
||||
std::string osd_tls_ca;
|
||||
std::string client_tls_ca;
|
||||
std::string test_osd_aes_key; // FIXME Insecure, only for PoC tests
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
bool use_rdma = true;
|
||||
@@ -171,15 +282,26 @@ protected:
|
||||
robin_hood::unordered_flat_map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
std::string tls_cn;
|
||||
|
||||
void ssl_init(osd_client_t *cl, bool server_mode);
|
||||
bool ssl_do_handshake(osd_client_t *cl);
|
||||
#endif
|
||||
|
||||
std::vector<msgr_iothread_t*> iothreads;
|
||||
std::vector<uint64_t> read_ready_clients;
|
||||
std::vector<uint64_t> write_ready_clients;
|
||||
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
|
||||
std::deque<osd_op_t*> set_immediate_ops;
|
||||
|
||||
std::vector<op_aes_xts_encrypt_t*> encrypt_ctx_pool;
|
||||
std::vector<op_aes_xts_decrypt_t*> decrypt_ctx_pool;
|
||||
|
||||
public:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_i *ringloop = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
bool has_sendmsg_zc = false;
|
||||
// osd_num_t is only for logging and asserts
|
||||
uint64_t next_client_id = 1;
|
||||
@@ -194,11 +316,11 @@ public:
|
||||
std::vector<addr_mask_t> osd_cluster_network_masks;
|
||||
std::vector<std::string> all_osd_networks;
|
||||
std::vector<addr_mask_t> all_osd_network_masks;
|
||||
int use_proto_checksums = 0;
|
||||
// op statistics
|
||||
osd_op_stats_t stats, recovery_stats;
|
||||
|
||||
void init();
|
||||
void init_iothreads();
|
||||
void parse_config(const json11::Json & config);
|
||||
void connect_peer(uint64_t osd_num, json11::Json peer_state);
|
||||
void stop_client(uint64_t client_id, bool force_delete = false);
|
||||
@@ -211,7 +333,6 @@ public:
|
||||
void read_requests();
|
||||
void send_replies();
|
||||
void accept_connections(int listen_fd);
|
||||
void destroy_iothreads();
|
||||
~osd_messenger_t();
|
||||
|
||||
static json11::Json::object read_config(const json11::Json & config);
|
||||
@@ -245,21 +366,36 @@ protected:
|
||||
|
||||
bool try_send(osd_client_t *cl);
|
||||
void handle_send(int result, bool prev, bool more, osd_client_t *cl);
|
||||
bool op_write_to(osd_client_t *cl, msgr_op_writer_t & wr);
|
||||
void next_write_op(osd_client_t *cl);
|
||||
bool op_write_buf(osd_client_t *cl, uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len, bool skip_csum, size_t & from, size_t & done);
|
||||
bool op_copy_data_to(osd_client_t *cl, uint8_t *dst, size_t dst_len, size_t & from, size_t & done);
|
||||
size_t copy_ops_to(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
template<typename T> size_t copy_ops_to_with(osd_client_t *cl, uint8_t *dst, size_t dst_len);
|
||||
|
||||
bool handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, void *curbuf, int remain);
|
||||
bool handle_finished_read(osd_client_t *cl);
|
||||
void handle_op_hdr(osd_client_t *cl);
|
||||
bool handle_reply_hdr(osd_client_t *cl);
|
||||
void handle_reply_ready(osd_op_t *op);
|
||||
void handle_read(int result, osd_client_t *cl);
|
||||
bool handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
|
||||
template<typename T> bool handle_buffer_with(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
|
||||
bool handle_hdr(osd_client_t *cl);
|
||||
bool allocate_op_buffers(osd_client_t *cl);
|
||||
bool allocate_reply_buffers(osd_client_t *cl, osd_op_t *op);
|
||||
bool op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr);
|
||||
bool handle_finished_op(osd_client_t *cl);
|
||||
void execute_verified_op(osd_client_t *cl, osd_op_t *op);
|
||||
void handle_immediate_ops();
|
||||
|
||||
void op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
|
||||
void op_encrypt_free(osd_client_t* cl);
|
||||
void op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc);
|
||||
void op_decrypt_start(osd_client_t* cl);
|
||||
void op_decrypt_inline(osd_client_t* cl);
|
||||
void op_decrypt_free(osd_client_t* cl);
|
||||
|
||||
#ifdef WITH_RDMA
|
||||
void try_send_rdma(osd_client_t *cl);
|
||||
bool init_recv_rdma(osd_client_t *cl);
|
||||
void handle_rdma_events(msgr_rdma_context_t *rdma_context);
|
||||
msgr_rdma_context_t* choose_rdma_context(osd_client_t *cl);
|
||||
void destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn);
|
||||
#endif
|
||||
#ifdef WITH_RDMACM
|
||||
void handle_rdmacm_events();
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
#include "messenger.h"
|
||||
#include "msgr_encrypt.h"
|
||||
|
||||
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
if (!(ctx = EVP_CIPHER_CTX_new()))
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
|
||||
{
|
||||
assert(!encrypted);
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!encrypted);
|
||||
this->start_offset = start_offset;
|
||||
this->key = key;
|
||||
this->block_size = block_size;
|
||||
this->offset = 0;
|
||||
this->encrypted = false;
|
||||
this->tmp_pos = 0;
|
||||
if (tmp && tmp_size != block_size)
|
||||
{
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
tmp_size = 0;
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
int actual_out = 0;
|
||||
if (EVP_EncryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
|
||||
{
|
||||
// Fucking AES-XTS implementations (all of them) don't have streaming support,
|
||||
// crafting IV to resume encryption is slow, so we have to accumulate a full block
|
||||
// and encrypt it at once :-(
|
||||
// And then we have to support consuming it in parts because it's simpler for the
|
||||
// higher layers.
|
||||
if (encrypted)
|
||||
{
|
||||
// Copy accumulated and encrypted output
|
||||
assert(tmp);
|
||||
if (max_out > block_size - tmp_pos)
|
||||
max_out = block_size - tmp_pos;
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
encrypted = false;
|
||||
}
|
||||
else if (max_in < block_size - offset%block_size)
|
||||
{
|
||||
// Just accumulate input
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
}
|
||||
else if (max_out < block_size)
|
||||
{
|
||||
// Accumulate and encrypt input in <tmp>, then copy part of it to <out>
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, tmp);
|
||||
encrypted = true;
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
encrypt_block(in, out);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
done_out += block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accumulate input and encrypt directly to <output>
|
||||
assert(tmp);
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
encrypt_block(tmp, out);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
|
||||
{
|
||||
delete encrypt_ctx;
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
if (!(ctx = EVP_CIPHER_CTX_new()))
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
||||
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#else
|
||||
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
|
||||
{
|
||||
assert(!decrypted);
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
#endif
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size)
|
||||
{
|
||||
assert(!decrypted);
|
||||
this->start_offset = start_offset;
|
||||
this->key_chain = chain_size > 1 ? key_chain : 0;
|
||||
this->chain_size = chain_size > 1 ? chain_size : 0;
|
||||
this->key_indexes = chain_size > 1 ? key_indexes : NULL;
|
||||
this->key_index_bytes = osd_op_rw_t::chain_info_bytes(chain_size);
|
||||
assert(chain_size <= 1 || key_indexes != NULL);
|
||||
this->block_size = block_size;
|
||||
this->offset = 0;
|
||||
this->tmp_pos = 0;
|
||||
if (tmp && tmp_size != block_size)
|
||||
{
|
||||
free(tmp);
|
||||
tmp = NULL;
|
||||
tmp_size = 0;
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
if (chain_size == 1 && key_chain[0] && EVP_DecryptInit_ex(ctx, NULL, NULL, key_chain[0], NULL) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
|
||||
{
|
||||
uint8_t *key = NULL;
|
||||
if (chain_size)
|
||||
{
|
||||
uint32_t key_index = key_index_bytes == 1
|
||||
? ((uint8_t*)key_indexes)[offset/block_size]
|
||||
: (key_index_bytes == 2
|
||||
? ((uint16_t*)key_indexes)[offset/block_size]
|
||||
: (key_index_bytes == 4
|
||||
? ((uint32_t*)key_indexes)[offset/block_size]
|
||||
: UINT32_MAX));
|
||||
assert(key_index < chain_size);
|
||||
key = key_chain[key_index];
|
||||
if (!key)
|
||||
{
|
||||
if (in != out)
|
||||
memcpy(out, in, block_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
uint8_t iv[16] = { 0 };
|
||||
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
|
||||
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
int actual_out = 0;
|
||||
if (EVP_DecryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
|
||||
{
|
||||
ERR_print_errors_fp(stderr);
|
||||
abort();
|
||||
}
|
||||
assert(actual_out == block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
// out may be NULL, in this case all input is still decrypted to calculate checksums,
|
||||
// but part of it is skipped and not copied to out
|
||||
void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
|
||||
{
|
||||
// Fucking AES-XTS implementations (all of them) don't have streaming support,
|
||||
// crafting IV to resume decryption is slow, so we have to accumulate a full block
|
||||
// and decrypt it at once :-(
|
||||
// And then we have to support consuming it in parts because clients sometimes need
|
||||
// fragmented output.
|
||||
if (decrypted)
|
||||
{
|
||||
// Copy accumulated and decrypted output
|
||||
assert(tmp);
|
||||
if (max_out > block_size - tmp_pos)
|
||||
max_out = block_size - tmp_pos;
|
||||
if (out)
|
||||
memcpy(out, tmp + tmp_pos, max_out);
|
||||
done_out += max_out;
|
||||
tmp_pos += max_out;
|
||||
if (tmp_pos >= block_size)
|
||||
decrypted = false;
|
||||
}
|
||||
else if (max_in < block_size - offset%block_size)
|
||||
{
|
||||
// Just accumulate input
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
}
|
||||
else if (max_out < block_size || !out)
|
||||
{
|
||||
// Accumulate and decrypt input in <tmp>, then copy part of it to <out>
|
||||
if (!tmp)
|
||||
{
|
||||
tmp = (uint8_t*)malloc_or_die(block_size);
|
||||
tmp_size = block_size;
|
||||
}
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
decrypt_block(tmp, tmp);
|
||||
decrypted = true;
|
||||
if (out)
|
||||
memcpy(out, tmp, max_out);
|
||||
tmp_pos = max_out;
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += max_out;
|
||||
}
|
||||
else if (!(offset%block_size))
|
||||
{
|
||||
// Full block - simplest case
|
||||
if (out)
|
||||
decrypt_block(in, out);
|
||||
done_in += block_size;
|
||||
offset += block_size;
|
||||
done_out += block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accumulate input and decrypt directly to <output>
|
||||
assert(tmp);
|
||||
max_in = block_size - offset%block_size;
|
||||
memcpy(tmp + offset%block_size, in, max_in);
|
||||
assert(out);
|
||||
decrypt_block(tmp, out);
|
||||
done_in += max_in;
|
||||
offset += max_in;
|
||||
done_out += block_size;
|
||||
}
|
||||
}
|
||||
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
|
||||
{
|
||||
delete decrypt_ctx;
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_encrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
|
||||
{
|
||||
if (!cl->encrypt_ctx)
|
||||
{
|
||||
if (encrypt_ctx_pool.size())
|
||||
{
|
||||
cl->encrypt_ctx = encrypt_ctx_pool.back();
|
||||
encrypt_ctx_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->encrypt_ctx = new op_aes_xts_encrypt_t();
|
||||
assert(cl->write_op->enc->key_chain[0]);
|
||||
cl->encrypt_ctx->start(cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
|
||||
}
|
||||
while (done_enc < enc_len && (done_plain < plain_len || cl->encrypt_ctx->has_buffered()))
|
||||
{
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->encrypt_ctx->update(plain+done_plain, plain_len-done_plain, enc_buf+done_enc, enc_len-done_enc, done_in, done_out);
|
||||
if (cl->write_csum_state && done_out > 0)
|
||||
XXH3_64bits_update(cl->write_csum_state, enc_buf+done_enc, done_out);
|
||||
done_enc += done_out;
|
||||
cl->write_op_pos += done_in;
|
||||
done_plain += done_in;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypted_copy_buf(osd_client_t *cl, uint8_t *enc_buf, size_t enc_len, uint8_t *plain, size_t plain_len, size_t & done_plain, size_t & done_enc)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
while (done_plain < plain_len && done_enc < enc_len)
|
||||
{
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
// plain == NULL means skip output
|
||||
cl->decrypt_ctx->update(enc_buf+done_enc, enc_len-done_enc, plain ? plain+done_plain : NULL, plain_len-done_plain, done_in, done_out);
|
||||
if (cl->read_csum_state && done_in > 0)
|
||||
XXH3_64bits_update(cl->read_csum_state, enc_buf+done_enc, done_in);
|
||||
done_enc += done_in;
|
||||
cl->read_op_pos += done_out;
|
||||
cl->read_op_inline_decrypt_in += done_in;
|
||||
done_plain += done_out;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
|
||||
{
|
||||
if (!cl->decrypt_ctx)
|
||||
{
|
||||
if (decrypt_ctx_pool.size())
|
||||
{
|
||||
cl->decrypt_ctx = decrypt_ctx_pool.back();
|
||||
decrypt_ctx_pool.pop_back();
|
||||
}
|
||||
else
|
||||
cl->decrypt_ctx = new op_aes_xts_decrypt_t();
|
||||
auto & enc = cl->read_op->enc;
|
||||
assert(cl->read_op->req.hdr.opcode == OSD_OP_READ);
|
||||
cl->decrypt_ctx->start(enc->key_chain, enc->chain_size,
|
||||
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
|
||||
cl->read_op->req.rw.offset, enc->bitmap_granularity);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
|
||||
{
|
||||
op_decrypt_start(cl);
|
||||
osd_op_t *op = cl->read_op;
|
||||
size_t from_in = cl->read_op_inline_decrypt_in;
|
||||
int i = 0;
|
||||
while (i < op->iov.count && from_in >= op->iov.buf[i].iov_len)
|
||||
{
|
||||
from_in -= op->iov.buf[i].iov_len;
|
||||
i++;
|
||||
}
|
||||
size_t from_out = cl->read_op_inline_decrypt_pos - OSD_PACKET_SIZE - op->reply.rw.bitmap_len;
|
||||
int j = 0;
|
||||
while (j < op->iov.count && from_out >= op->iov.buf[j].iov_len)
|
||||
{
|
||||
from_out -= op->iov.buf[j].iov_len;
|
||||
j++;
|
||||
}
|
||||
while (i < op->iov.count && j < op->iov.count)
|
||||
{
|
||||
uint8_t *in = (uint8_t*)op->iov.buf[i].iov_base + from_in;
|
||||
size_t in_len = op->iov.buf[i].iov_len - from_in;
|
||||
uint8_t *out = (uint8_t*)op->iov.buf[j].iov_base + from_out;
|
||||
size_t out_len = op->iov.buf[j].iov_len - from_out;
|
||||
size_t done_in = 0;
|
||||
size_t done_out = 0;
|
||||
cl->decrypt_ctx->update(in, in_len, out, out_len, done_in, done_out);
|
||||
if (done_in >= in_len)
|
||||
{
|
||||
i++;
|
||||
from_in = 0;
|
||||
}
|
||||
else
|
||||
from_in += done_in;
|
||||
if (done_out >= out_len)
|
||||
{
|
||||
j++;
|
||||
from_out = 0;
|
||||
}
|
||||
else
|
||||
from_out += done_out;
|
||||
}
|
||||
assert(j >= op->iov.count);
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->decrypt_ctx)
|
||||
{
|
||||
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
|
||||
delete cl->decrypt_ctx;
|
||||
else
|
||||
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
|
||||
cl->decrypt_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::op_encrypt_free(osd_client_t* cl)
|
||||
{
|
||||
if (cl->encrypt_ctx)
|
||||
{
|
||||
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
|
||||
delete cl->encrypt_ctx;
|
||||
else
|
||||
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
|
||||
cl->encrypt_ctx = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
// WITH_OPENSSL is left to possibly support other crypto libraries
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
#endif
|
||||
|
||||
class op_aes_xts_encrypt_t
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t *key = NULL;
|
||||
size_t offset = 0;
|
||||
size_t block_size = 0;
|
||||
uint8_t *tmp = NULL;
|
||||
size_t tmp_size = 0;
|
||||
size_t tmp_pos = 0;
|
||||
bool encrypted = false;
|
||||
|
||||
void encrypt_block(uint8_t *in, uint8_t *out);
|
||||
|
||||
public:
|
||||
op_aes_xts_encrypt_t();
|
||||
~op_aes_xts_encrypt_t();
|
||||
|
||||
inline bool has_buffered() { return encrypted; };
|
||||
void start(uint8_t *key, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
|
||||
|
||||
class op_aes_xts_decrypt_t
|
||||
{
|
||||
#ifdef WITH_OPENSSL
|
||||
EVP_CIPHER_CTX *ctx = NULL;
|
||||
#endif
|
||||
uint64_t start_offset = 0;
|
||||
uint8_t **key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
void *key_indexes = NULL;
|
||||
int key_index_bytes = 0;
|
||||
size_t offset = 0;
|
||||
size_t block_size = 0;
|
||||
uint8_t *tmp = NULL;
|
||||
size_t tmp_size = 0;
|
||||
size_t tmp_pos = 0;
|
||||
bool decrypted = false;
|
||||
|
||||
void decrypt_block(uint8_t *in, uint8_t *out);
|
||||
|
||||
public:
|
||||
op_aes_xts_decrypt_t();
|
||||
~op_aes_xts_decrypt_t();
|
||||
|
||||
inline bool has_buffered() { return decrypted; };
|
||||
void start(uint8_t **key_chain, size_t chain_size, void *key_indexes, uint64_t start_offset, size_t block_size);
|
||||
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
|
||||
};
|
||||
|
||||
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
|
||||
@@ -1,129 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "messenger.h"
|
||||
#include "msgr_iothread.h"
|
||||
|
||||
msgr_iothread_t::msgr_iothread_t():
|
||||
ring(RINGLOOP_DEFAULT_SIZE, true),
|
||||
thread(&msgr_iothread_t::run, this)
|
||||
{
|
||||
eventfd = ring.register_eventfd();
|
||||
if (eventfd < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("failed to register eventfd: ") + strerror(-eventfd));
|
||||
}
|
||||
}
|
||||
|
||||
msgr_iothread_t::~msgr_iothread_t()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_sqe(io_uring_sqe & sqe)
|
||||
{
|
||||
mu.lock();
|
||||
queue.push_back((iothread_sqe_t){ .sqe = sqe, .data = std::move(*(ring_data_t*)sqe.user_data) });
|
||||
if (queue.size() == 1)
|
||||
{
|
||||
cond.notify_all();
|
||||
}
|
||||
mu.unlock();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::stop()
|
||||
{
|
||||
mu.lock();
|
||||
if (stopped)
|
||||
{
|
||||
mu.unlock();
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
if (outer_loop_data)
|
||||
{
|
||||
outer_loop_data->callback = [](ring_data_t*){};
|
||||
}
|
||||
cond.notify_all();
|
||||
close(eventfd);
|
||||
mu.unlock();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void msgr_iothread_t::add_to_ringloop(ring_loop_i *outer_loop)
|
||||
{
|
||||
assert(!this->outer_loop || this->outer_loop == outer_loop);
|
||||
io_uring_sqe *sqe = outer_loop->get_sqe();
|
||||
assert(sqe != NULL);
|
||||
this->outer_loop = outer_loop;
|
||||
this->outer_loop_data = ((ring_data_t*)sqe->user_data);
|
||||
io_uring_prep_poll_add(sqe, eventfd, POLLIN);
|
||||
outer_loop_data->callback = [this](ring_data_t *data)
|
||||
{
|
||||
if (data->res < 0)
|
||||
{
|
||||
throw std::runtime_error(std::string("eventfd poll failed: ") + strerror(-data->res));
|
||||
}
|
||||
outer_loop_data = NULL;
|
||||
if (stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
add_to_ringloop(this->outer_loop);
|
||||
ring.loop();
|
||||
};
|
||||
}
|
||||
|
||||
void msgr_iothread_t::run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mu);
|
||||
while (!stopped && !queue.size())
|
||||
cond.wait(lk);
|
||||
if (stopped)
|
||||
return;
|
||||
int i = 0;
|
||||
for (; i < queue.size(); i++)
|
||||
{
|
||||
io_uring_sqe *sqe = ring.get_sqe();
|
||||
if (!sqe)
|
||||
break;
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
*data = std::move(queue[i].data);
|
||||
*sqe = queue[i].sqe;
|
||||
sqe->user_data = (uint64_t)data;
|
||||
}
|
||||
queue.erase(queue.begin(), queue.begin()+i);
|
||||
}
|
||||
// We only want to offload sendmsg/recvmsg. Callbacks will be called in main thread
|
||||
ring.submit();
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::init_iothreads()
|
||||
{
|
||||
for (int i = 0; i < iothread_count; i++)
|
||||
{
|
||||
auto iot = new msgr_iothread_t();
|
||||
iothreads.push_back(iot);
|
||||
iot->add_to_ringloop(ringloop);
|
||||
}
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_iothreads()
|
||||
{
|
||||
if (iothreads.size())
|
||||
{
|
||||
for (auto iot: iothreads)
|
||||
{
|
||||
delete iot;
|
||||
}
|
||||
iothreads.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
|
||||
#include "ringloop.h"
|
||||
|
||||
struct iothread_sqe_t
|
||||
{
|
||||
io_uring_sqe sqe;
|
||||
ring_data_t data;
|
||||
};
|
||||
|
||||
class msgr_iothread_t
|
||||
{
|
||||
protected:
|
||||
ring_loop_t ring;
|
||||
ring_loop_i *outer_loop = NULL;
|
||||
ring_data_t *outer_loop_data = NULL;
|
||||
int eventfd = -1;
|
||||
bool stopped = false;
|
||||
std::mutex mu;
|
||||
std::condition_variable cond;
|
||||
std::vector<iothread_sqe_t> queue;
|
||||
std::thread thread;
|
||||
|
||||
void run();
|
||||
public:
|
||||
|
||||
msgr_iothread_t();
|
||||
~msgr_iothread_t();
|
||||
|
||||
void add_sqe(io_uring_sqe & sqe);
|
||||
void stop();
|
||||
void add_to_ringloop(ring_loop_i *outer_loop);
|
||||
};
|
||||
@@ -8,7 +8,6 @@
|
||||
osd_op_t::~osd_op_t()
|
||||
{
|
||||
assert(!bs_op);
|
||||
assert(!op_data);
|
||||
if (bitmap_buf)
|
||||
{
|
||||
free(bitmap_buf);
|
||||
@@ -23,6 +22,14 @@ osd_op_t::~osd_op_t()
|
||||
// So we don't reuse it, but free it every time
|
||||
free(buf);
|
||||
}
|
||||
if (enc_buf)
|
||||
{
|
||||
free(enc_buf);
|
||||
}
|
||||
if (op_data)
|
||||
{
|
||||
free(op_data);
|
||||
}
|
||||
}
|
||||
|
||||
bool osd_op_t::is_recovery_related()
|
||||
|
||||
+22
-2
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -16,6 +18,8 @@
|
||||
|
||||
#define OSD_OP_INLINE_BUF_COUNT 16
|
||||
|
||||
#define AES_256_XTS_KEY_SIZE 64
|
||||
|
||||
// Kind of a vector with small-list-optimisation
|
||||
struct osd_op_buf_list_t
|
||||
{
|
||||
@@ -152,12 +156,24 @@ struct blockstore_op_t;
|
||||
|
||||
struct osd_primary_op_data_t;
|
||||
|
||||
struct osd_op_enc_t
|
||||
{
|
||||
// Keys may contain more information in the future, like encryption algorithm and key ID
|
||||
// In this case, key_chain will become inode_key_t* with inode_key_t also being a structure
|
||||
// Currently all keys are required to be 512 bit (64 byte) long, for AES-256-XTS
|
||||
// Raw pointers are convenient for messenger code; external users may use shared_ptr aliasing
|
||||
// to implement complex freeing of osd_op_enc_t along with their external inode cache info
|
||||
uint8_t** key_chain = NULL;
|
||||
size_t chain_size = 0;
|
||||
uint32_t read_chain_bitmap_pos = 0;
|
||||
uint32_t bitmap_granularity = 0;
|
||||
};
|
||||
|
||||
struct __attribute__((visibility("default"))) osd_op_t
|
||||
{
|
||||
timespec tv_begin = { 0 }, tv_end = { 0 };
|
||||
uint64_t op_type = OSD_OP_IN;
|
||||
uint64_t client_id = 0;
|
||||
osd_num_t osd_num = 0;
|
||||
osd_any_op_t req;
|
||||
osd_any_reply_t reply;
|
||||
blockstore_op_t *bs_op = NULL;
|
||||
@@ -166,8 +182,11 @@ struct __attribute__((visibility("default"))) osd_op_t
|
||||
void *bitmap = NULL;
|
||||
unsigned bitmap_len = 0;
|
||||
unsigned bmp_data = 0;
|
||||
void *bitmap_buf = NULL;
|
||||
uint8_t *bitmap_buf = NULL;
|
||||
void *rmw_buf = NULL;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
uint8_t *enc_buf = NULL;
|
||||
uint64_t csum = 0; // network layer checksum
|
||||
osd_primary_op_data_t* op_data = NULL;
|
||||
std::function<void(osd_op_t*)> callback;
|
||||
|
||||
@@ -177,4 +196,5 @@ struct __attribute__((visibility("default"))) osd_op_t
|
||||
void cancel();
|
||||
|
||||
bool is_recovery_related();
|
||||
uint64_t calc_data_checksum();
|
||||
};
|
||||
|
||||
+48
-80
@@ -568,28 +568,6 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
|
||||
cl->rdma_conn->cur_send++;
|
||||
}
|
||||
|
||||
static int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len)
|
||||
{
|
||||
auto rc = cl->rdma_conn;
|
||||
int total_dst_len = dst_len;
|
||||
while (dst_len > 0 && rc->send_pos < cl->send_list.size())
|
||||
{
|
||||
iovec & iov = cl->send_list[rc->send_pos];
|
||||
uint32_t len = (uint32_t)(iov.iov_len-rc->send_buf_pos < dst_len
|
||||
? iov.iov_len-rc->send_buf_pos : dst_len);
|
||||
memcpy(dst, (uint8_t*)iov.iov_base+rc->send_buf_pos, len);
|
||||
dst += len;
|
||||
dst_len -= len;
|
||||
rc->send_buf_pos += len;
|
||||
if (rc->send_buf_pos >= iov.iov_len)
|
||||
{
|
||||
rc->send_pos++;
|
||||
rc->send_buf_pos = 0;
|
||||
}
|
||||
}
|
||||
return total_dst_len-dst_len;
|
||||
}
|
||||
|
||||
void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
{
|
||||
auto rc = cl->rdma_conn;
|
||||
@@ -612,19 +590,51 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
while (!rc->send_out_full && copied > 0 && rc->cur_send < rc->max_send)
|
||||
{
|
||||
dst = (uint8_t*)rc->send_out.buf + rc->send_out_pos;
|
||||
dst_len = (rc->send_out_pos >= rc->send_done_pos
|
||||
? rc->send_out_size-rc->send_out_pos
|
||||
: rc->send_done_pos-rc->send_out_pos);
|
||||
if (rc->send_out_pos >= rc->send_done_pos)
|
||||
{
|
||||
dst_len = rc->send_out_size-rc->send_out_pos;
|
||||
if (dst_len < 4096)
|
||||
{
|
||||
// free end of the buffer is too small, skip
|
||||
rc->send_out_pos = 0;
|
||||
if (rc->send_out_pos >= rc->send_done_pos)
|
||||
rc->send_out_full = true;
|
||||
if (!rc->send_sizes.size())
|
||||
{
|
||||
rc->send_done_pos += dst_len;
|
||||
rc->send_out_full = false;
|
||||
if (rc->send_done_pos == rc->send_out_size)
|
||||
rc->send_done_pos = 0;
|
||||
}
|
||||
else
|
||||
rc->send_sizes.back() += dst_len;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dst_len = rc->send_done_pos-rc->send_out_pos;
|
||||
if (dst_len < 4096)
|
||||
{
|
||||
// too small buffer, stop
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dst_len > rc->max_msg)
|
||||
dst_len = rc->max_msg;
|
||||
copied = try_send_rdma_copy(cl, dst, dst_len);
|
||||
copied = copy_ops_to(cl, dst, dst_len);
|
||||
if (cl->io_error)
|
||||
{
|
||||
stop_client(cl->client_id);
|
||||
return;
|
||||
}
|
||||
if (copied > 0)
|
||||
{
|
||||
rc->send_out_pos += copied;
|
||||
if (rc->send_out_pos == rc->send_out_size)
|
||||
rc->send_out_pos = 0;
|
||||
assert(rc->send_out_pos < rc->send_out_size);
|
||||
if (rc->send_out_pos == rc->send_done_pos)
|
||||
if (rc->send_out_pos >= rc->send_done_pos)
|
||||
rc->send_out_full = true;
|
||||
ibv_sge sge = {
|
||||
.addr = (uintptr_t)dst,
|
||||
@@ -633,6 +643,7 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
|
||||
};
|
||||
try_send_rdma_wr(cl, &sge, 1);
|
||||
rc->send_sizes.push_back(copied);
|
||||
cl->send_free_ops.push_back(NULL); // end marker
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -729,9 +740,6 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
}
|
||||
if (!is_send)
|
||||
{
|
||||
// Reset OSD ping state - client is obviously alive
|
||||
cl->ping_time_remaining = 0;
|
||||
cl->idle_time_remaining = osd_idle_timeout;
|
||||
rc->cur_recv--;
|
||||
if (!handle_read_buffer(cl, rc->recv_buffers[rc->next_recv_buf], wc[i].byte_len))
|
||||
{
|
||||
@@ -743,67 +751,27 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
|
||||
else
|
||||
{
|
||||
rc->cur_send--;
|
||||
uint64_t sent_size = rc->send_sizes.at(0);
|
||||
rc->send_sizes.erase(rc->send_sizes.begin(), rc->send_sizes.begin()+1);
|
||||
// byte_len is not filled for send operations
|
||||
uint64_t sent_size = rc->send_sizes.front();
|
||||
rc->send_sizes.pop_front();
|
||||
rc->send_done_pos += sent_size;
|
||||
rc->send_out_full = false;
|
||||
if (rc->send_done_pos == rc->send_out_size)
|
||||
rc->send_done_pos = 0;
|
||||
assert(rc->send_done_pos < rc->send_out_size);
|
||||
int send_pos = 0, send_buf_pos = 0;
|
||||
while (sent_size > 0)
|
||||
while (cl->send_free_ops.front())
|
||||
{
|
||||
if (sent_size >= cl->send_list.at(send_pos).iov_len)
|
||||
{
|
||||
sent_size -= cl->send_list[send_pos].iov_len;
|
||||
send_pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
send_buf_pos = sent_size;
|
||||
sent_size = 0;
|
||||
}
|
||||
delete cl->send_free_ops.front();
|
||||
cl->send_free_ops.pop_front();
|
||||
}
|
||||
assert(rc->send_pos >= send_pos);
|
||||
if (rc->send_pos == send_pos)
|
||||
cl->send_free_ops.pop_front();
|
||||
if ((cl->proto_csum_status & MSGR_CSUM_NEG) && !cl->write_op && !cl->write_ops.size())
|
||||
{
|
||||
rc->send_buf_pos -= send_buf_pos;
|
||||
}
|
||||
rc->send_pos -= send_pos;
|
||||
for (int i = 0; i < send_pos; i++)
|
||||
{
|
||||
if (cl->outbox[i].flags & MSGR_SENDP_FREE)
|
||||
{
|
||||
// Reply fully sent
|
||||
delete cl->outbox[i].op;
|
||||
}
|
||||
}
|
||||
if (send_pos > 0)
|
||||
{
|
||||
cl->send_list.erase(cl->send_list.begin(), cl->send_list.begin()+send_pos);
|
||||
cl->outbox.erase(cl->outbox.begin(), cl->outbox.begin()+send_pos);
|
||||
}
|
||||
if (send_buf_pos > 0)
|
||||
{
|
||||
cl->send_list[0].iov_base = (uint8_t*)cl->send_list[0].iov_base + send_buf_pos;
|
||||
cl->send_list[0].iov_len -= send_buf_pos;
|
||||
// Checksums negotiated, enable
|
||||
cl->proto_csum_status = cl->proto_csum_status & (~MSGR_CSUM_NEG);
|
||||
}
|
||||
try_send_rdma(cl);
|
||||
}
|
||||
}
|
||||
} while (event_count > 0);
|
||||
handle_immediate_ops();
|
||||
}
|
||||
|
||||
void osd_messenger_t::destroy_rdma_conn(msgr_rdma_connection_t *rdma_conn)
|
||||
{
|
||||
if (rdma_conn->cmid)
|
||||
{
|
||||
auto rdma_it = rdmacm_connections.find(rdma_conn->cmid);
|
||||
if (rdma_it != rdmacm_connections.end() && rdma_it->second->rdma_conn == rdma_conn)
|
||||
{
|
||||
rdmacm_connections.erase(rdma_it);
|
||||
}
|
||||
}
|
||||
delete rdma_conn;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
#include <infiniband/verbs.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include "addr_util.h"
|
||||
|
||||
struct osd_op_t;
|
||||
|
||||
struct msgr_rdma_address_t
|
||||
{
|
||||
ibv_gid gid;
|
||||
@@ -72,11 +75,11 @@ struct msgr_rdma_connection_t
|
||||
int cur_send = 0, cur_recv = 0;
|
||||
int send_pos = 0, send_buf_pos = 0;
|
||||
int next_recv_buf = 0;
|
||||
std::vector<void*> recv_buffers;
|
||||
std::vector<uint8_t*> recv_buffers;
|
||||
msgr_rdma_buf_t recv_buf;
|
||||
std::vector<uint64_t> send_sizes;
|
||||
std::deque<uint64_t> send_sizes;
|
||||
msgr_rdma_buf_t send_out;
|
||||
int send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
|
||||
size_t send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
|
||||
bool send_out_full = false;
|
||||
|
||||
~msgr_rdma_connection_t();
|
||||
|
||||
@@ -495,7 +495,7 @@ void osd_messenger_t::rdmacm_established(rdma_cm_event *ev)
|
||||
cl->peer_state = PEER_RDMA;
|
||||
cl->connect_timeout_id = -1;
|
||||
cl->osd_num = peer_osd;
|
||||
cl->in_buf = malloc_or_die(receive_buffer_size);
|
||||
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
|
||||
cl->rdma_conn = rc;
|
||||
clients[conn->client_id] = cl;
|
||||
if (conn->timeout_id >= 0)
|
||||
|
||||
+881
-218
File diff suppressed because it is too large
Load Diff
+862
-127
File diff suppressed because it is too large
Load Diff
+100
-8
@@ -5,6 +5,16 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "messenger.h"
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#ifdef WITH_RDMA
|
||||
#include "msgr_rdma.h"
|
||||
#endif
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
void osd_client_t::cancel_ops()
|
||||
{
|
||||
@@ -76,6 +86,22 @@ void osd_messenger_t::stop_client(uint64_t client_id, bool force_delete)
|
||||
fprintf(stderr, "[OSD %ju] Stopping client %ju (regular client)\n", osd_num, client_id);
|
||||
}
|
||||
}
|
||||
if (cl->encrypt_ctx)
|
||||
{
|
||||
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
|
||||
destroy_aes_xts_encrypt(cl->encrypt_ctx);
|
||||
else
|
||||
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
|
||||
cl->encrypt_ctx = NULL;
|
||||
}
|
||||
if (cl->decrypt_ctx)
|
||||
{
|
||||
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
|
||||
destroy_aes_xts_decrypt(cl->decrypt_ctx);
|
||||
else
|
||||
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
|
||||
cl->decrypt_ctx = NULL;
|
||||
}
|
||||
// First set state to STOPPED so another stop_client() call doesn't try to free it again
|
||||
cl->refs++;
|
||||
int prev_state = cl->peer_state;
|
||||
@@ -89,11 +115,23 @@ void osd_messenger_t::stop_client(uint64_t client_id, bool force_delete)
|
||||
osd_peers.erase(osd_it);
|
||||
}
|
||||
}
|
||||
#ifdef WITH_RDMA
|
||||
if (cl->rdma_conn && cl->rdma_conn->cmid)
|
||||
{
|
||||
auto rdma_it = rdmacm_connections.find(cl->rdma_conn->cmid);
|
||||
if (rdma_it != rdmacm_connections.end() && rdma_it->second == cl)
|
||||
{
|
||||
rdmacm_connections.erase(rdma_it);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifndef __MOCK__
|
||||
if (cl->connect_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(cl->connect_timeout_id);
|
||||
cl->connect_timeout_id = -1;
|
||||
}
|
||||
#endif
|
||||
if (cl->in_osd_num && break_pg_locks)
|
||||
{
|
||||
// Break PG locks
|
||||
@@ -128,7 +166,9 @@ void osd_messenger_t::destroy_client(osd_client_t *cl)
|
||||
clients.erase(cl->client_id);
|
||||
if (cl->peer_fd >= 0)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
tfd->set_fd_handler(cl->peer_fd, false, NULL);
|
||||
#endif
|
||||
for (auto rit = read_ready_clients.begin(); rit != read_ready_clients.end(); rit++)
|
||||
{
|
||||
if (*rit == cl->client_id)
|
||||
@@ -147,13 +187,6 @@ void osd_messenger_t::destroy_client(osd_client_t *cl)
|
||||
}
|
||||
clients_by_fd.erase(cl->peer_fd);
|
||||
}
|
||||
#ifdef WITH_RDMA
|
||||
if (cl->rdma_conn)
|
||||
{
|
||||
destroy_rdma_conn(cl->rdma_conn);
|
||||
cl->rdma_conn = NULL;
|
||||
}
|
||||
#endif
|
||||
delete cl;
|
||||
}
|
||||
|
||||
@@ -177,13 +210,72 @@ osd_client_t::~osd_client_t()
|
||||
read_op->cancel();
|
||||
read_op = NULL;
|
||||
}
|
||||
while (unverified_ops.size())
|
||||
{
|
||||
auto op = unverified_ops.back();
|
||||
unverified_ops.pop_back();
|
||||
op->cancel();
|
||||
}
|
||||
// Cancel outbound ops
|
||||
cancel_ops();
|
||||
for (osd_op_t *op: zc_free_list)
|
||||
for (osd_op_t *op: send_free_ops)
|
||||
{
|
||||
if (op)
|
||||
{
|
||||
delete op;
|
||||
}
|
||||
}
|
||||
for (osd_op_t *op: zc_free_list)
|
||||
{
|
||||
if (op)
|
||||
{
|
||||
if (!((size_t)op & 7))
|
||||
delete op;
|
||||
else
|
||||
free((void*)((size_t)op & ~(size_t)7));
|
||||
}
|
||||
}
|
||||
#ifndef __MOCK__
|
||||
#ifdef WITH_RDMA
|
||||
if (rdma_conn)
|
||||
{
|
||||
delete rdma_conn;
|
||||
rdma_conn = NULL;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
if (read_csum_state)
|
||||
{
|
||||
XXH3_freeState(read_csum_state);
|
||||
read_csum_state = NULL;
|
||||
}
|
||||
if (write_csum_state)
|
||||
{
|
||||
XXH3_freeState(write_csum_state);
|
||||
write_csum_state = NULL;
|
||||
}
|
||||
#ifdef WITH_OPENSSL
|
||||
if (enc_ctx)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(enc_ctx);
|
||||
enc_ctx = NULL;
|
||||
}
|
||||
if (dec_ctx)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(dec_ctx);
|
||||
dec_ctx = NULL;
|
||||
}
|
||||
if (ssl_cli)
|
||||
{
|
||||
SSL_free(ssl_cli);
|
||||
ssl_cli = NULL;
|
||||
write_to_ssl = NULL;
|
||||
read_from_ssl = NULL;
|
||||
}
|
||||
if (ssl_out_buf)
|
||||
{
|
||||
free(ssl_out_buf);
|
||||
ssl_out_buf = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+17
-2
@@ -37,6 +37,7 @@
|
||||
|
||||
#define OSD_OP_RECOVERY_RELATED (uint32_t)1
|
||||
#define OSD_OP_IGNORE_PG_LOCK (uint32_t)2
|
||||
#define OSD_OP_RETURN_CHAIN (uint32_t)4
|
||||
|
||||
// Memory alignment for direct I/O (usually 512 bytes)
|
||||
#ifndef DIRECT_IO_ALIGNMENT
|
||||
@@ -228,13 +229,27 @@ struct __attribute__((__packed__)) osd_op_rw_t
|
||||
uint64_t offset;
|
||||
// length. 0 means to read all bitmaps of the specified range, but no data.
|
||||
uint32_t len;
|
||||
// flags (for future)
|
||||
// flags
|
||||
// OSD_OP_RETURN_CHAIN for chained reads: return parent number in chain for each block
|
||||
// read_chain size comes after bitmap, takes 0 bytes / 1 byte / 2 byte / 4 byte per each block,
|
||||
// depending on the number of parent inodes (0 parents = 0 bytes, up to 255 parents = 1 byte, etc)
|
||||
uint32_t flags;
|
||||
// inode metadata revision
|
||||
// inode metadata revision for chained reads
|
||||
uint64_t meta_revision;
|
||||
// object version for atomic "CAS" (compare-and-set) writes
|
||||
// writes and deletes fail with -EINTR if object version differs from (version-1)
|
||||
uint64_t version;
|
||||
|
||||
static inline size_t chain_info_bytes(size_t chain_size)
|
||||
{
|
||||
if (chain_size <= 1)
|
||||
return 0;
|
||||
if (chain_size <= 256)
|
||||
return 1;
|
||||
if (chain_size <= 65536)
|
||||
return 2;
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) osd_reply_rw_t
|
||||
|
||||
@@ -1049,7 +1049,7 @@ static int coroutine_fn vitastor_co_block_status(BlockDriverState *bs,
|
||||
{
|
||||
// Get larger allocated extents, possibly with false positives
|
||||
uint64_t bmp_pos = (offset-task.offset) / task.bitmap_granularity;
|
||||
uint64_t bmp_end = (offset+bytes-task.offset) / task.bitmap_granularity;
|
||||
uint64_t bmp_end = (offset+bytes-task.offset) / task.bitmap_granularity - bmp_pos;
|
||||
while (bmp_pos < bmp_end)
|
||||
{
|
||||
if (!(bmp_pos & 7) && bmp_end >= bmp_pos+8)
|
||||
|
||||
@@ -282,7 +282,7 @@ help:
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
const bool writeback = !cli->get_immediate_commit(inode);
|
||||
const bool writeback = cli->get_immediate_commit(inode);
|
||||
auto pool_it = cli->st_cli.pool_config.find(INODE_POOL(inode ? inode : watch->cfg.num));
|
||||
if (pool_it == cli->st_cli.pool_config.end())
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user