Compare commits

..
Author SHA1 Message Date
Vitaliy Filippov 32721046b8 WIP Switch back to TCP when RDMA send fails (does not work) 2026-05-01 22:26:41 +00:00
Vitaliy Filippov f8de201625 Close TCP connections for RDMA clients 2026-05-01 22:13:29 +00:00
Vitaliy Filippov e7f30c4939 Also check for "Received garbage" during test 2026-05-01 16:01:28 +03:00
Vitaliy Filippov 8e441f4173 Add a CI test with RDMA 2026-05-01 15:57:15 +03:00
Vitaliy Filippov 80a05c052a Add request size validation to prevent OOMDoS 2026-05-01 15:57:15 +03:00
Vitaliy Filippov a972c13571 Implement OSD-side authorization for operations 2026-04-29 02:42:23 +03:00
Vitaliy Filippov 9372df4beb Remove tls_ from option names 2026-04-27 15:41:00 +03:00
Vitaliy Filippov 24c27386b6 Remove TLS support (superseded by direct GCM) 2026-04-27 15:41:00 +03:00
Vitaliy Filippov 394de5e9aa Implement TLS 1.3-like handshake manually 2026-04-27 15:24:44 +03:00
Vitaliy Filippov be61e76df0 Extract openssl-related code, wire ssl implementation back 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 5cad126987 Coalesce entries in send_out_buf 2026-04-27 15:24:44 +03:00
Vitaliy Filippov afa50a05b8 Support isa-l_crypto for AES-XTS too 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 9ff833178e Fix xts+rdma encrypt errors 2026-04-27 15:24:44 +03:00
Vitaliy Filippov b152baa733 Fix "use-after-realloc" warning 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 8dbbf4097a Support isa-l_crypto for AES-GCM 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 17cb875e10 Remove WITH_OPENSSL from all files except http_client, always require OpenSSL 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 03e15338e5 Use pools for GCM contexts 2026-04-27 15:24:44 +03:00
Vitaliy Filippov dccc549e33 Try to run tests with AES-GCM 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 440a8bc33a Implement direct AES-256-GCM with a static key for benchmark 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 78a21542da Make sure to send all TLS data before continuing 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 75ba9509ad Parse standard TLS record headers 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 8b58fe9808 Omit msgr_tls_record_hdr_t for non-tls data 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 331f345a78 Allow 2 and 4 byte per block chain_info (allow more than 255 snapshots with encryption) 2026-04-27 15:24:44 +03:00
Vitaliy Filippov d0a4ac914b Implement OSD TLS support 2026-04-27 15:24:44 +03:00
Vitaliy Filippov e42b812323 Allow to skip checksums for headers 2026-04-27 15:24:44 +03:00
Vitaliy Filippov ac5c753735 Implement protocol-level checksums (xxhash3) 2026-04-27 15:24:44 +03:00
Vitaliy Filippov f9eb497105 Include xxhash3 x86dispatch 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 3489c99640 Do not use scrap_buffer in the client (it would block protocol checksum support) 2026-04-27 15:24:44 +03:00
Vitaliy Filippov a21dcd4480 Support TLS CN authentication and per-image permissions in vitastor-cli serve 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 340849248b Add VitastorAuthFilter 2026-04-27 15:24:44 +03:00
Vitaliy Filippov ee462c4375 Implement vitastor-cli ls-user, modify-user, remove-user commands 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 0053a0d825 Add image owner/owner_group/reader_group support (for antietcd VitastorAuthFilter) 2026-04-27 15:24:44 +03:00
Vitaliy Filippov cdeecead8d Add security parameter documentation 2026-04-27 15:24:44 +03:00
Vitaliy Filippov c0e34b19c9 Support inline (string PEM) certificates and pkeys 2026-04-27 15:24:44 +03:00
Vitaliy Filippov e8abe99205 Show encryption keys (only IDs) in the listing 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 8225b37f53 Support storing image encryption keys in Vault 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 62b29b03c5 Prefer local etcd addresses and correctly cycle over them even when they need resolving
Seems slightly overcomplicated...
2026-04-27 15:24:44 +03:00
Vitaliy Filippov ae2f570b58 Support DNS resolving via libc-ares 2026-04-27 15:24:44 +03:00
Vitaliy Filippov d7f854ab3e Batch handle_immediate_ops more 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 29640d1aac Add vitastor-cli create & modify --enc-key parameter 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 66a016f8a1 Support reading from snapshots encrypted with different keys 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 34d01f44db Support decryption with multiple keys 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 3956982112 Allow to return chain_info in response to reads 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 713b17e574 Add basic AES-XTS client-side encryption support 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 4efa48c9ca Rework msgr send/receive to allow encryption support 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 26d5e1ed00 Move fromhexstr() to str_util 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 9ba3444cfa Add openapi description 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 96c0001127 Slightly fix API return and input types 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 56a14cc0bd Implement vitastor-cli serve command to serve simple HTTP API 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 6ca1b9eca9 Implement HTTP server support O_o 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 876dbd730a Rename http_response_t to http_message_t 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 4398ee0377 Extract common HTTP context 2026-04-27 15:24:44 +03:00
Vitaliy Filippov 2c673a90bd Support xxhash 32-bit checksums (data_csum_type=xxh3_32) 2026-04-27 15:24:44 +03:00
Vitaliy Filippov a8371498e1 Detect block checksums using csum_block_size, not data_csum_type 2026-04-27 15:24:43 +03:00
Vitaliy Filippov 2a093cbfb5 Add client certificate support 2026-04-27 15:24:43 +03:00
Vitaliy Filippov 094a181491 Do not re-initialize TLS context every connection 2026-04-27 15:24:43 +03:00
Vitaliy Filippov fe30783135 Add https support to antietcd 2026-04-27 15:24:43 +03:00
Vitaliy Filippov e9a6c58418 Implement etcd SSL support via OpenSSL
Maybe I should remove all of this and use libwebsockets :)
2026-04-27 15:24:43 +03:00
Vitaliy Filippov 4acfe149cb Release 3.0.10
Important bug fixes (new store):
- Fix OSDs possibly refusing to start with "write metadata failed at offset xxx: Invalid argument"
  (fix buffer alignment during initial garbage collection)
- Rollback change from 3.0.4 - on-disk garbage entries are not skipped on start again. This change
  doesn't have any impact normally, but OSDs originally running 3.0.0-3.0.2 and then upgraded
  to 3.0.9 may hit a bug where 3.0.9 refuses to start due to entries marked as garbage too
  early and flushed to disk in 3.0.0-3.0.2.

Other changes:
- Auto-select the only RDMA device/port if there is only one
- Rollback one 3.0.9 change - there was no actual use-after-free :)
  (the problem was only relevant to an unstable development version)
- Fix `vitastor-nfs --trace` option
- Fix an unintended 1 second sleep in vitastor-cli rm-data
- Fix inode statistics not being cleared for a deleted pool
- Fix print to stdout in client
2026-04-27 13:53:48 +03:00
Vitaliy Filippov 008ed5b269 Rollback 25ecca7625 - there was no actual use-after-free :) 2026-04-22 01:35:38 +03:00
Vitaliy Filippov 4fffe0f032 Fix vitastor-nfs trace option 2026-04-20 21:51:47 +03:00
Vitaliy Filippov a76d5ccc0d Wakeup callers in rm-data 2026-04-17 13:53:32 +03:00
Vitaliy Filippov 8ed1e180e0 Clear inode_stats in mon 2026-04-14 02:44:46 +03:00
Vitaliy Filippov 8832fc3b14 Fix print to stdout in client 2026-04-14 02:43:50 +03:00
Vitaliy Filippov 0134934c99 Do not skip garbage entries on start (rollback change from 3.0.4) 2026-04-11 12:05:02 +03:00
Vitaliy Filippov 2e36f292bd Fix buffer alignment during metadata clearing on init 2026-04-10 21:56:12 +03:00
Vitaliy Filippov bcc6419760 Auto-select the only RDMA device/port if there is only one 2026-04-08 15:46:10 +03:00
Vitaliy Filippov dd5941b9a4 Release 3.0.9
- Fix broken garbage collection in the new store leading to very high memory usage
- Make the new store do full garbage collection on OSD startup by default (can be turned off with a new gc_on_start parameter)
- Make startup logging with the new store more informative, allow to log amount of garbage with log_level > 1
- Fix clients reading from snapshots incorrectly on retries (for example when OSDs were restarted during read)
- Fix a possible "send-after-free" in OSD which could probably also affect snapshot reads
- Fix OSD crashing when handling a read_bitmap operation from a deleted object with the new store
2026-04-04 18:38:40 +03:00
Vitaliy Filippov 4005b88865 Do not use node_type 2026-04-04 17:37:47 +03:00
Vitaliy Filippov 280b5cd675 Add gc_on_start docs 2026-04-04 17:19:32 +03:00
Vitaliy Filippov e5c505eaf4 Write 1 entry to blocks to collect garbage 2026-04-03 18:08:12 +00:00
Vitaliy Filippov c1d244d4f0 Fix init metadata entry loading logging 2026-04-03 18:08:12 +00:00
Vitaliy Filippov 9b264a212f Do full metadata GC on start (new store) 2026-04-03 18:08:12 +00:00
Vitaliy Filippov ff7f5cb4f4 Report live & garbage entry count / memory with log_level > 1 2026-04-02 23:45:13 +00:00
Vitaliy Filippov 25ecca7625 Fix use-after-free in OSD ops (ooops) 2026-04-02 13:48:17 +00:00
Vitaliy Filippov 99c4244004 Try to write into blocks with more garbage first 2026-04-02 13:48:17 +00:00
Vitaliy Filippov 9949b9fb4e Fix incorrect reads from snapshots on retries 2026-03-31 02:06:53 +03:00
Vitaliy Filippov e6881ad1d5 Fix read_bitmap from deleted objects with the new store 2026-03-31 01:59:53 +03:00
Vitaliy Filippov b30635b932 Release 3.0.8
- Surrogate increment-only peer IDs are now used instead of file descriptor numbers,
  and client cleanup sequence is reworked in another attempt to fix a possible PG hang
  in 'peering' states. The history of this bug is still unclear and it still hasn't been
  reproduced in tests, but several users have reported it since 3.0.4 (only with enabled RDMA).
- Fix a minor bug where an OSD could skip reporting misplaced PG state when moving
  an object in non-immediate_commit mode.
- Add Ubuntu 26.04 (Resolute Raccoon) support and patches for qemu 10.2 and libvirt 12.0
2026-03-30 02:09:02 +03:00
Vitaliy Filippov 0c1154833c Add Ubuntu 26.04 support 2026-03-30 02:06:10 +03:00
Vitaliy Filippov c227bb05b6 Fix incorrect patch contents for qemu 10.1, add patch for qemu 10.2 2026-03-30 02:06:10 +03:00
Vitaliy Filippov dd85315f22 Update CMake supported versions 2026-03-30 02:06:10 +03:00
Vitaliy Filippov 47d2f4e0be Add patch for libvirt 12.0 2026-03-30 02:06:10 +03:00
Vitaliy Filippov 2a5028d17f Use robin_hood_map in messenger 2026-03-30 02:06:10 +03:00
Vitaliy Filippov 07915c2881 Use surrogate peer IDs instead of FDs 2026-03-30 02:06:10 +03:00
Vitaliy Filippov 79141eb383 Fix commented out //this->pg_state_dirty.insert 2026-03-28 20:08:52 +03:00
Vitaliy Filippov f7cbb6ed56 Remove FD from the event loop in destroy_client, not in stop_client 2026-03-28 14:18:20 +03:00
Vitaliy Filippov 8f8172db99 clear_immediate_ops is now done by stop_client 2026-03-26 01:08:23 +03:00
Vitaliy Filippov 94be147e80 Clear received_ops - they are already freed 2026-03-26 01:08:02 +03:00
Vitaliy Filippov 538620b400 Release 3.0.6
- Fix one more bug in old store checksums O_o - checksum verification wasn't
  working correctly on reads quickly after small initial offsetted writes
  (when a BIG_WRITE was read from the journal).
- Fix clients possibly not destroyed after being switched to RDMA. Not 100%
  verified, but in theory it could rarely lead to PGs hanging in peering/stopping
  states with RDMA enabled.
- Use vitastor-cli in the Cinder driver for modifications instead of direct etcd
  interaction.
- Fix mon_https_ca parsing.
- Fix Docker install.sh, add instructions for Podman.
2026-03-22 22:49:51 +03:00
131 changed files with 7163 additions and 1867 deletions
+19 -1
View File
@@ -63,7 +63,7 @@ jobs:
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
# leak sanitizer sometimes crashes
- run: cd /root/vitastor/build && ASAN_OPTIONS=detect_leaks=0 make -j16 test
- run: cd /root/vitastor/build && ASAN_OPTIONS=detect_leaks=0 make -j16 build_tests test
npm_lint:
runs-on: ubuntu-latest
@@ -1278,6 +1278,24 @@ jobs:
echo ""
done
test_heal_ec_rdma:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: TEST_NAME=ec_rdma RDMA=1 SCHEME=ec /root/vitastor/tests/test_heal.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_checksum:
runs-on: ubuntu-latest
needs: build
+7 -7
View File
@@ -1,20 +1,20 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
set(VITASTOR_VERSION "3.0.5")
set(VITASTOR_VERSION "3.0.10")
include(CTest)
add_custom_target(build_tests)
add_custom_target(test
COMMAND
echo leak:tcmalloc > ${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt &&
env LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt ${CMAKE_CTEST_COMMAND}
set_property(TEST PROPERTY ENVIRONMENT LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt)
add_test(gen_lsan_suppress
${CMAKE_COMMAND} -E echo leak:tcmalloc > "${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt"
)
set_tests_properties(gen_lsan_suppress PROPERTIES FIXTURES_SETUP f_lsan_suppress)
set_property(TEST PROPERTY FIXTURES_REQUIRED f_lsan_suppress)
# make -j16 -C ../../build test_heap && ../../build/src/test/test_heap
# make -j16 -C ../../build test_heap && rm -f $(find ../../build -name '*.gcda') && ctest -V -T test -T coverage -R heap --test-dir ../../build && (cd ../../build; gcovr -f ../src --html --html-nested -o coverage/index.html; cd ../src/test)
# make -j16 -C ../../build test_blockstore && rm -f $(find ../../build -name '*.gcda') && ctest -V -T test -T coverage -R blockstore --test-dir ../../build && (cd ../../build; gcovr -f ../src --html --html-nested -o coverage/index.html; cd ../src/test)
# kcov --include-path=../../../src ../../kcov ./test_blockstore
add_dependencies(test build_tests)
add_subdirectory(src)
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.5
VITASTOR_VERSION ?= v3.0.10
all: build push
+1 -1
View File
@@ -49,7 +49,7 @@ spec:
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: vitalif/vitastor-csi:v3.0.5
image: vitalif/vitastor-csi:v3.0.10
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -121,7 +121,7 @@ spec:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
image: vitalif/vitastor-csi:v3.0.5
image: vitalif/vitastor-csi:v3.0.10
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -5,7 +5,7 @@ package vitastor
const (
vitastorCSIDriverName = "csi.vitastor.io"
vitastorCSIDriverVersion = "3.0.5"
vitastorCSIDriverVersion = "3.0.10"
)
// Config struct fills the parameters of request or user input
+1 -1
View File
@@ -1,4 +1,4 @@
vitastor (3.0.5-1) unstable; urgency=medium
vitastor (3.0.10-1) unstable; urgency=medium
* Bugfixes
+1 -1
View File
@@ -44,7 +44,7 @@ curl -s https://git.yourcmc.ru/vitalif/antietcd/archive/master.tar.gz | tar -zx
curl -s https://git.yourcmc.ru/vitalif/tinyraft/archive/master.tar.gz | tar -zx
cd /root/vitastor/packages/vitastor-$REL
if [[ "$REL" = "trixie" && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
if [[ ( "$REL" = "trixie" || "$REL" = "resolute" ) && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
# Fucking shit, archives differ between bookworm (xz 5.4.1) and trixie (xz 5.8.1)
cp ../vitastor-bookworm/vitastor_$VER.orig.tar.xz .
else
+6 -8
View File
@@ -12,20 +12,18 @@ ARG REL=
WORKDIR /root
RUN set -e -x; \
if [ "$REL" = "buster" ]; then \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
fi; \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
RUN apt-get update && \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
apt-get -y build-dep fio && \
apt-get --download-only source fio
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.5
VITASTOR_VERSION ?= v3.0.10
all: build push
+1 -1
View File
@@ -4,7 +4,7 @@
#
# Desired Vitastor version
VITASTOR_VERSION=v3.0.5
VITASTOR_VERSION=v3.0.10
# Additional arguments for all containers
# For example, you may want to specify a custom logging driver here
+7
View File
@@ -70,6 +70,7 @@ with an OSD restart or, for some of them, even without restarting by updating co
- [use_atomic_flag](#use_atomic_flag)
- [pg_reshard_chunk_size](#pg_reshard_chunk_size)
- [pg_reshard_chunk_pause_ms](#pg_reshard_chunk_pause_ms)
- [gc_on_start](#gc_on_start)
## bind_address
@@ -753,3 +754,9 @@ This option sets the maximum number of object is a chunk. Moving 100k objects us
- Default: 100
This option sets the interval between handling two PG count change chunks.
## gc_on_start
- Type: boolean
Forcibly clean all garbage entries in the new store on every OSD restart.
+7
View File
@@ -71,6 +71,7 @@
- [use_atomic_flag](#use_atomic_flag)
- [pg_reshard_chunk_size](#pg_reshard_chunk_size)
- [pg_reshard_chunk_pause_ms](#pg_reshard_chunk_pause_ms)
- [gc_on_start](#gc_on_start)
## bind_address
@@ -793,3 +794,9 @@ pg_minsize OSD во время переключений, что может по
- Значение по умолчанию: 100
Данная опция задаёт интервал между обработкой двух порций изменения числа PG пулов.
## gc_on_start
- Тип: булево (да/нет)
Принудительно очищать все мусорные записи в новом хранилище при каждом запуске OSD.
+4 -4
View File
@@ -25,7 +25,7 @@ Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't su
- [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)
- [max_cipher_pool_size](#max_cipher_pool_size)
## etcd_client_cert
@@ -141,10 +141,10 @@ Time (in seconds) to wait before retrying after receiving an error from Vault.
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
in case of system clock drift.
## max_aes_xts_pool_size
## max_cipher_pool_size
- Type: integer
- Default: 256
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
doesn't require modification.
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
for each cipher and for encryption/decryption. Probably doesn't require modification.
+4 -4
View File
@@ -27,7 +27,7 @@ OSD, мониторами и клиентами.
- [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)
- [max_cipher_pool_size](#max_cipher_pool_size)
## etcd_client_cert
@@ -145,10 +145,10 @@ OSD, клиенты и мониторы должны иметь разные п
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
## max_aes_xts_pool_size
## max_cipher_pool_size
- Тип: целое число
- Значение по умолчанию: 256
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
Вряд ли требует изменения.
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
+4
View File
@@ -938,3 +938,7 @@
This option sets the interval between handling two PG count change chunks.
info_ru: |
Данная опция задаёт интервал между обработкой двух порций изменения числа PG пулов.
- name: gc_on_start
type: bool
info: Forcibly clean all garbage entries in the new store on every OSD restart.
info_ru: Принудительно очищать все мусорные записи в новом хранилище при каждом запуске OSD.
+5 -5
View File
@@ -120,12 +120,12 @@
info_ru: |
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
- name: max_aes_xts_pool_size
- name: max_cipher_pool_size
type: int
default: 256
info: |
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
doesn't require modification.
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
for each cipher and for encryption/decryption. Probably doesn't require modification.
info_ru: |
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
Вряд ли требует изменения.
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
+2 -2
View File
@@ -26,9 +26,9 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
The instruction is very simple.
1. Download a Docker image of the desired version: \
`docker pull vitalif/vitastor:v3.0.5`
`docker pull vitalif/vitastor:v3.0.10`
2. Install scripts to the host system: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.5 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.10 install.sh`
3. Reload udev rules: \
`udevadm control --reload-rules`
4. Enable the vitastor-host service: \
+2 -2
View File
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
Инструкция по установке максимально простая.
1. Скачайте Docker-образ желаемой версии: \
`docker pull vitalif/vitastor:v3.0.5`
`docker pull vitalif/vitastor:v3.0.10`
2. Установите скрипты в хост-систему командой: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.5 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.10 install.sh`
3. Перезагрузите правила udev: \
`udevadm control --reload-rules`
4. Включите сервис vitastor-host: \
+1
View File
@@ -17,6 +17,7 @@
- Debian 10 (Buster): `deb https://vitastor.io/debian buster main`
- Ubuntu 22.04 (Jammy): `deb https://vitastor.io/debian jammy main`
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
- Add `-oldstable` to bookworm/bullseye/buster in this line to install the last
stable version from 0.9.x branch instead of 1.x
- To always prefer vitastor-patched QEMU and Libvirt versions, add the following to `/etc/apt/preferences`:
+1
View File
@@ -17,6 +17,7 @@
- Debian 10 (Buster): `deb https://vitastor.io/debian buster main`
- Ubuntu 22.04 (Jammy): `deb https://vitastor.io/debian jammy main`
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
- Добавьте `-oldstable` к слову bookworm/bullseye/buster в этой строке, чтобы
установить последнюю стабильную версию из ветки 0.9.x вместо 1.x
- Чтобы всегда предпочитались версии пакетов QEMU и Libvirt с патчами Vitastor, добавьте в `/etc/apt/preferences`:
+1
View File
@@ -262,3 +262,4 @@ Options:
| `--logfile <FILE>` | log to the specified file |
| `--enforce 1` | enforce permissions at the server side (no by default) |
| `--foreground 1` | stay in foreground, do not daemonize |
| `--trace` | trace all NFS requests |
+1
View File
@@ -274,3 +274,4 @@ VitastorFS из GPUDirect.
| `--logfile <FILE>` | записывать логи в заданный файл |
| `--enforce 1` | проверять права доступа на стороне сервера (по умолчанию нет) |
| `--foreground 1` | не уходить в фон после запуска |
| `--trace` | логгировать все запросы NFS |
+1 -1
Submodule json11 updated: fd37016cf8...edcd85b8bd
+12 -1
View File
@@ -49,6 +49,17 @@ class AntiEtcdAdapter
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_')
@@ -173,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);
+13 -2
View File
@@ -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*',
@@ -208,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?: '',
},
...
}, */
@@ -225,11 +228,20 @@ const etcd_tree = {
readonly?: boolean,
deleted?: boolean,
enc_key?: string,
meta?: any,
owner?: string,
owner_group?: string,
reader_group?: string,
}
}
}, */
inode: {},
/* user: {
<username>: {
type: 'osd'|'mon'|'admin'|'client',
groups: string[],
},
}, */
user: {},
},
osd: {
state: {
@@ -392,7 +404,6 @@ const etcd_tree = {
/* <name>: {
id: uint64_t,
pool_id: uint64_t,
// ...plus a copy of everything from config/inode/x/y
}, */
},
maxid: {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor-mon",
"version": "3.0.5",
"version": "3.0.10",
"description": "Vitastor SDS monitor service",
"main": "mon-main.js",
"scripts": {
+1
View File
@@ -37,6 +37,7 @@ function derive_osd_stats(st, prev, prev_diff)
const n = c.count - BigInt(pr && pr.count||0);
diff.recovery_stats[op] = { ...c, bps: n > 0 ? b*1000n/timediff : 0n, iops: n > 0 ? n*1000n/timediff : 0n };
}
diff.inode_stats = {};
for (const pool_id in st.inode_stats||{})
{
diff.inode_stats[pool_id] = {};
+471
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor",
"version": "3.0.5",
"version": "3.0.10",
"description": "Low-level native bindings to Vitastor client library",
"main": "index.js",
"keywords": [
+1 -1
View File
@@ -50,7 +50,7 @@ from cinder.volume import configuration
from cinder.volume import driver
from cinder.volume import volume_utils
VITASTOR_VERSION = '3.0.5'
VITASTOR_VERSION = '3.0.10'
LOG = logging.getLogger(__name__)
+637
View File
@@ -0,0 +1,637 @@
diff --git a/include/libvirt/libvirt-storage.h b/include/libvirt/libvirt-storage.h
index aaad4a3da1..5f5daa8341 100644
--- a/include/libvirt/libvirt-storage.h
+++ b/include/libvirt/libvirt-storage.h
@@ -326,6 +326,7 @@ typedef enum {
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS = 1 << 17, /* (Since: 1.2.8) */
VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE = 1 << 18, /* (Since: 3.1.0) */
VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI_DIRECT = 1 << 19, /* (Since: 5.6.0) */
+ VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR = 1 << 20, /* (Since: 5.0.0) */
} virConnectListAllStoragePoolsFlags;
int virConnectListAllStoragePools(virConnectPtr conn,
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 9ca5c2450c..cc52f00c0c 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -7453,7 +7453,8 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
src->configFile = virXPathString("string(./config/@file)", ctxt);
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
- src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS)
+ src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
+ src->protocol == VIR_STORAGE_NET_PROTOCOL_VITASTOR)
src->query = virXMLPropString(node, "query");
if (virDomainStorageNetworkParseHosts(node, ctxt, &src->hosts, &src->nhosts) < 0)
@@ -32187,6 +32188,7 @@ virDomainStorageSourceTranslateSourcePool(virStorageSource *src,
case VIR_STORAGE_POOL_MPATH:
case VIR_STORAGE_POOL_RBD:
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_SHEEPDOG:
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_LAST:
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index 7346a61731..83e94d762e 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -520,6 +520,7 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
case VIR_STORAGE_NET_PROTOCOL_RBD:
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
@@ -592,7 +593,7 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
}
}
- /* internal snapshots and config files are currently supported only with rbd: */
+ /* internal snapshots are currently supported only with rbd: */
if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK &&
src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD) {
if (src->snapshot) {
@@ -600,10 +601,14 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
_("<snapshot> element is currently supported only with 'rbd' disks"));
return -1;
}
-
+ }
+ /* config files are currently supported only with rbd and vitastor: */
+ if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK &&
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD &&
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_VITASTOR) {
if (src->configFile) {
virReportError(VIR_ERR_XML_ERROR, "%s",
- _("<config> element is currently supported only with 'rbd' disks"));
+ _("<config> element is currently supported only with 'rbd' and 'vitastor' disks"));
return -1;
}
}
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 114dd3f96f..c71f9a3277 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -2093,6 +2093,35 @@
</element>
</define>
+ <define name="diskSourceNetworkProtocolVitastor">
+ <element name="source">
+ <interleave>
+ <attribute name="protocol">
+ <value>vitastor</value>
+ </attribute>
+ <ref name="diskSourceCommon"/>
+ <optional>
+ <attribute name="name"/>
+ </optional>
+ <optional>
+ <attribute name="query"/>
+ </optional>
+ <zeroOrMore>
+ <ref name="diskSourceNetworkHost"/>
+ </zeroOrMore>
+ <optional>
+ <element name="config">
+ <attribute name="file">
+ <ref name="absFilePath"/>
+ </attribute>
+ <empty/>
+ </element>
+ </optional>
+ <empty/>
+ </interleave>
+ </element>
+ </define>
+
<define name="diskSourceNetworkProtocolISCSI">
<element name="source">
<attribute name="protocol">
@@ -2443,6 +2472,7 @@
<ref name="diskSourceNetworkProtocolSimple"/>
<ref name="diskSourceNetworkProtocolVxHS"/>
<ref name="diskSourceNetworkProtocolNFS"/>
+ <ref name="diskSourceNetworkProtocolVitastor"/>
</choice>
</define>
diff --git a/src/conf/storage_conf.c b/src/conf/storage_conf.c
index 1dc9365bf2..a8a736be81 100644
--- a/src/conf/storage_conf.c
+++ b/src/conf/storage_conf.c
@@ -56,7 +56,7 @@ VIR_ENUM_IMPL(virStoragePool,
"logical", "disk", "iscsi",
"iscsi-direct", "scsi", "mpath",
"rbd", "sheepdog", "gluster",
- "zfs", "vstorage",
+ "zfs", "vstorage", "vitastor",
);
VIR_ENUM_IMPL(virStoragePoolFormatFileSystem,
@@ -242,6 +242,18 @@ static virStoragePoolTypeInfo poolTypeInfo[] = {
.formatToString = virStorageFileFormatTypeToString,
}
},
+ {.poolType = VIR_STORAGE_POOL_VITASTOR,
+ .poolOptions = {
+ .flags = (VIR_STORAGE_POOL_SOURCE_HOST |
+ VIR_STORAGE_POOL_SOURCE_NETWORK |
+ VIR_STORAGE_POOL_SOURCE_NAME),
+ },
+ .volOptions = {
+ .defaultFormat = VIR_STORAGE_FILE_RAW,
+ .formatFromString = virStorageVolumeFormatFromString,
+ .formatToString = virStorageFileFormatTypeToString,
+ }
+ },
{.poolType = VIR_STORAGE_POOL_SHEEPDOG,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
@@ -538,6 +550,11 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
_("element 'name' is mandatory for RBD pool"));
return -1;
}
+ if (pool_type == VIR_STORAGE_POOL_VITASTOR && source->name == NULL) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("element 'name' is mandatory for Vitastor pool"));
+ return -1;
+ }
if (options->formatFromString) {
g_autofree char *format = NULL;
@@ -1127,6 +1144,7 @@ virStoragePoolDefFormatBuf(virBuffer *buf,
/* RBD, Sheepdog, Gluster and Iscsi-direct devices are not local block devs nor
* files, so they don't have a target */
if (def->type != VIR_STORAGE_POOL_RBD &&
+ def->type != VIR_STORAGE_POOL_VITASTOR &&
def->type != VIR_STORAGE_POOL_SHEEPDOG &&
def->type != VIR_STORAGE_POOL_GLUSTER &&
def->type != VIR_STORAGE_POOL_ISCSI_DIRECT) {
diff --git a/src/conf/storage_conf.h b/src/conf/storage_conf.h
index fc67957cfe..720c07ef74 100644
--- a/src/conf/storage_conf.h
+++ b/src/conf/storage_conf.h
@@ -103,6 +103,7 @@ typedef enum {
VIR_STORAGE_POOL_GLUSTER, /* Gluster device */
VIR_STORAGE_POOL_ZFS, /* ZFS */
VIR_STORAGE_POOL_VSTORAGE, /* Virtuozzo Storage */
+ VIR_STORAGE_POOL_VITASTOR, /* Vitastor */
VIR_STORAGE_POOL_LAST,
} virStoragePoolType;
@@ -454,6 +455,7 @@ VIR_ENUM_DECL(virStoragePartedFs);
VIR_CONNECT_LIST_STORAGE_POOLS_SCSI | \
VIR_CONNECT_LIST_STORAGE_POOLS_MPATH | \
VIR_CONNECT_LIST_STORAGE_POOLS_RBD | \
+ VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR | \
VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG | \
VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER | \
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS | \
diff --git a/src/conf/storage_source_conf.c b/src/conf/storage_source_conf.c
index d7b9bdfecb..38aefd0dd4 100644
--- a/src/conf/storage_source_conf.c
+++ b/src/conf/storage_source_conf.c
@@ -90,6 +90,7 @@ VIR_ENUM_IMPL(virStorageNetProtocol,
"ssh",
"vxhs",
"nfs",
+ "vitastor",
);
@@ -1317,6 +1318,7 @@ virStorageSourceNetworkDefaultPort(virStorageNetProtocol protocol)
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
return 24007;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_RBD:
/* we don't provide a default for RBD */
return 0;
diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h
index 22c35d420d..f1e32ea83d 100644
--- a/src/conf/storage_source_conf.h
+++ b/src/conf/storage_source_conf.h
@@ -131,6 +131,7 @@ typedef enum {
VIR_STORAGE_NET_PROTOCOL_SSH,
VIR_STORAGE_NET_PROTOCOL_VXHS,
VIR_STORAGE_NET_PROTOCOL_NFS,
+ VIR_STORAGE_NET_PROTOCOL_VITASTOR,
VIR_STORAGE_NET_PROTOCOL_LAST
} virStorageNetProtocol;
diff --git a/src/conf/virstorageobj.c b/src/conf/virstorageobj.c
index 59fa5da372..4739167f5f 100644
--- a/src/conf/virstorageobj.c
+++ b/src/conf/virstorageobj.c
@@ -1438,6 +1438,7 @@ virStoragePoolObjSourceFindDuplicateCb(const void *payload,
return 1;
break;
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_ISCSI_DIRECT:
case VIR_STORAGE_POOL_RBD:
case VIR_STORAGE_POOL_LAST:
@@ -1921,6 +1922,8 @@ virStoragePoolObjMatch(virStoragePoolObj *obj,
(obj->def->type == VIR_STORAGE_POOL_MPATH)) ||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_RBD) &&
(obj->def->type == VIR_STORAGE_POOL_RBD)) ||
+ (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR) &&
+ (obj->def->type == VIR_STORAGE_POOL_VITASTOR)) ||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG) &&
(obj->def->type == VIR_STORAGE_POOL_SHEEPDOG)) ||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER) &&
diff --git a/src/libvirt-storage.c b/src/libvirt-storage.c
index db7660aac4..561df34709 100644
--- a/src/libvirt-storage.c
+++ b/src/libvirt-storage.c
@@ -94,6 +94,7 @@ virStoragePoolGetConnect(virStoragePoolPtr pool)
* VIR_CONNECT_LIST_STORAGE_POOLS_SCSI
* VIR_CONNECT_LIST_STORAGE_POOLS_MPATH
* VIR_CONNECT_LIST_STORAGE_POOLS_RBD
+ * VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR
* VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG
* VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER
* VIR_CONNECT_LIST_STORAGE_POOLS_ZFS
diff --git a/src/libxl/libxl_conf.c b/src/libxl/libxl_conf.c
index 2b988157fa..9d0eb47b25 100644
--- a/src/libxl/libxl_conf.c
+++ b/src/libxl/libxl_conf.c
@@ -1069,6 +1069,7 @@ libxlMakeNetworkDiskSrcStr(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
virReportError(VIR_ERR_NO_SUPPORT,
diff --git a/src/libxl/xen_xl.c b/src/libxl/xen_xl.c
index e72e7d7f44..8482c21805 100644
--- a/src/libxl/xen_xl.c
+++ b/src/libxl/xen_xl.c
@@ -1461,6 +1461,7 @@ xenFormatXLDiskSrcNet(virStorageSource *src)
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
virReportError(VIR_ERR_NO_SUPPORT,
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
index 9b43279797..459d8e8a65 100644
--- a/src/qemu/qemu_block.c
+++ b/src/qemu/qemu_block.c
@@ -743,6 +743,38 @@ qemuBlockStorageSourceGetRBDProps(virStorageSource *src,
}
+static virJSONValue *
+qemuBlockStorageSourceGetVitastorProps(virStorageSource *src)
+{
+ virJSONValue *ret = NULL;
+ virStorageNetHostDef *host;
+ size_t i;
+ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+ g_autofree char *etcd = NULL;
+
+ for (i = 0; i < src->nhosts; i++) {
+ host = src->hosts + i;
+ if ((virStorageNetHostTransport)host->transport != VIR_STORAGE_NET_HOST_TRANS_TCP) {
+ return NULL;
+ }
+ virBufferAsprintf(&buf, i > 0 ? ",%s:%u" : "%s:%u", host->name, host->port);
+ }
+ if (src->nhosts > 0) {
+ etcd = virBufferContentAndReset(&buf);
+ }
+
+ if (virJSONValueObjectAdd(&ret,
+ "S:etcd-host", etcd,
+ "S:etcd-prefix", src->query,
+ "S:config-path", src->configFile,
+ "s:image", src->path,
+ NULL) < 0)
+ return NULL;
+
+ return ret;
+}
+
+
static virJSONValue *
qemuBlockStorageSourceGetSshProps(virStorageSource *src)
{
@@ -1094,6 +1126,12 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
return NULL;
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
+ driver = "vitastor";
+ if (!(fileprops = qemuBlockStorageSourceGetVitastorProps(src)))
+ return NULL;
+ break;
+
case VIR_STORAGE_NET_PROTOCOL_SSH:
driver = "ssh";
if (!(fileprops = qemuBlockStorageSourceGetSshProps(src)))
@@ -1997,6 +2035,7 @@ qemuBlockGetBackingStoreString(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
case VIR_STORAGE_NET_PROTOCOL_SSH:
@@ -2377,6 +2416,12 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src,
return -1;
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
+ driver = "vitastor";
+ if (!(location = qemuBlockStorageSourceGetVitastorProps(src)))
+ return -1;
+ break;
+
case VIR_STORAGE_NET_PROTOCOL_SSH:
if (srcPriv->nbdkitProcess) {
/* disk creation not yet supported with nbdkit, and even if it
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index ac56fc7cb4..9e407b4aab 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -4677,7 +4677,8 @@ qemuDomainValidateStorageSource(virStorageSource *src,
if (src->query &&
(actualType != VIR_STORAGE_TYPE_NETWORK ||
(src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS &&
- src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP))) {
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP &&
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_VITASTOR))) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("query is supported only with HTTP(S) protocols"));
return -1;
@@ -9103,6 +9104,7 @@ qemuDomainPrepareStorageSourceTLS(virStorageSource *src,
break;
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
index e738afffc3..37d64f469b 100644
--- a/src/qemu/qemu_snapshot.c
+++ b/src/qemu/qemu_snapshot.c
@@ -665,6 +665,7 @@ qemuSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDef *snapdisk,
case VIR_STORAGE_NET_PROTOCOL_NONE:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
@@ -893,6 +894,7 @@ qemuSnapshotPrepareDiskInternal(virDomainDiskDef *disk,
case VIR_STORAGE_NET_PROTOCOL_NONE:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c
index e19e032427..59f91f4710 100644
--- a/src/storage/storage_driver.c
+++ b/src/storage/storage_driver.c
@@ -1626,6 +1626,7 @@ storageVolLookupByPathCallback(virStoragePoolObj *obj,
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_RBD:
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_SHEEPDOG:
case VIR_STORAGE_POOL_ZFS:
case VIR_STORAGE_POOL_LAST:
diff --git a/src/storage_file/storage_source_backingstore.c b/src/storage_file/storage_source_backingstore.c
index 821378883c..2211f6891b 100644
--- a/src/storage_file/storage_source_backingstore.c
+++ b/src/storage_file/storage_source_backingstore.c
@@ -264,6 +264,75 @@ virStorageSourceParseRBDColonString(const char *rbdstr,
}
+static int
+virStorageSourceParseVitastorColonString(const char *colonstr,
+ virStorageSource *src)
+{
+ char *p, *e, *next;
+ g_autofree char *options = NULL;
+
+ /* optionally skip the "vitastor:" prefix if provided */
+ if (STRPREFIX(colonstr, "vitastor:"))
+ colonstr += strlen("vitastor:");
+
+ options = g_strdup(colonstr);
+
+ p = options;
+ while (*p) {
+ /* find : delimiter or end of string */
+ for (e = p; *e && *e != ':'; ++e) {
+ if (*e == '\\') {
+ e++;
+ if (*e == '\0')
+ break;
+ }
+ }
+ if (*e == '\0') {
+ next = e; /* last kv pair */
+ } else {
+ next = e + 1;
+ *e = '\0';
+ }
+
+ if (STRPREFIX(p, "image=")) {
+ src->path = g_strdup(p + strlen("image="));
+ } else if (STRPREFIX(p, "etcd-prefix=")) {
+ src->query = g_strdup(p + strlen("etcd-prefix="));
+ } else if (STRPREFIX(p, "config-path=")) {
+ src->configFile = g_strdup(p + strlen("config-path="));
+ } else if (STRPREFIX(p, "etcd-host=")) {
+ char *h, *sep;
+
+ h = p + strlen("etcd-host=");
+ while (h < e) {
+ for (sep = h; sep < e; ++sep) {
+ if (*sep == '\\' && (sep[1] == ',' ||
+ sep[1] == ';' ||
+ sep[1] == ' ')) {
+ *sep = '\0';
+ sep += 2;
+ break;
+ }
+ }
+
+ if (virStorageSourceRBDAddHost(src, h) < 0)
+ return -1;
+
+ h = sep;
+ }
+ }
+
+ p = next;
+ }
+
+ if (!src->path) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
static int
virStorageSourceParseNBDColonString(const char *nbdstr,
virStorageSource *src)
@@ -379,6 +448,11 @@ virStorageSourceParseBackingColon(virStorageSource *src,
return -1;
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
+ if (virStorageSourceParseVitastorColonString(path, src) < 0)
+ return -1;
+ break;
+
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
@@ -953,6 +1027,54 @@ virStorageSourceParseBackingJSONRBD(virStorageSource *src,
return 0;
}
+static int
+virStorageSourceParseBackingJSONVitastor(virStorageSource *src,
+ virJSONValue *json,
+ const char *jsonstr G_GNUC_UNUSED,
+ int opaque G_GNUC_UNUSED)
+{
+ const char *filename;
+ const char *image = virJSONValueObjectGetString(json, "image");
+ const char *conf = virJSONValueObjectGetString(json, "config-path");
+ const char *etcd_prefix = virJSONValueObjectGetString(json, "etcd-prefix");
+ virJSONValue *servers = virJSONValueObjectGetArray(json, "server");
+ size_t nservers;
+ size_t i;
+
+ src->type = VIR_STORAGE_TYPE_NETWORK;
+ src->protocol = VIR_STORAGE_NET_PROTOCOL_VITASTOR;
+
+ /* legacy syntax passed via 'filename' option */
+ if ((filename = virJSONValueObjectGetString(json, "filename")))
+ return virStorageSourceParseVitastorColonString(filename, src);
+
+ if (!image) {
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
+ _("missing image name in Vitastor backing volume "
+ "JSON specification"));
+ return -1;
+ }
+
+ src->path = g_strdup(image);
+ src->configFile = g_strdup(conf);
+ src->query = g_strdup(etcd_prefix);
+
+ if (servers) {
+ nservers = virJSONValueArraySize(servers);
+
+ src->hosts = g_new0(virStorageNetHostDef, nservers);
+ src->nhosts = nservers;
+
+ for (i = 0; i < nservers; i++) {
+ if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i,
+ virJSONValueArrayGet(servers, i)) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
static int
virStorageSourceParseBackingJSONRaw(virStorageSource *src,
virJSONValue *json,
@@ -1130,6 +1252,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = {
{"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0},
{"ssh", false, virStorageSourceParseBackingJSONSSH, 0},
{"rbd", false, virStorageSourceParseBackingJSONRBD, 0},
+ {"vitastor", false, virStorageSourceParseBackingJSONVitastor, 0},
{"raw", true, virStorageSourceParseBackingJSONRaw, 0},
{"nfs", false, virStorageSourceParseBackingJSONNFS, 0},
{"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0},
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index 1165689de7..bba846351c 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -7345,6 +7345,7 @@ testStorageVolumeTypeForPool(int pooltype)
case VIR_STORAGE_POOL_ISCSI_DIRECT:
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_RBD:
+ case VIR_STORAGE_POOL_VITASTOR:
return VIR_STORAGE_VOL_NETWORK;
case VIR_STORAGE_POOL_LOGICAL:
case VIR_STORAGE_POOL_DISK:
diff --git a/tests/storagepoolcapsschemadata/poolcaps-fs.xml b/tests/storagepoolcapsschemadata/poolcaps-fs.xml
index eee75af746..8bd0a57bdd 100644
--- a/tests/storagepoolcapsschemadata/poolcaps-fs.xml
+++ b/tests/storagepoolcapsschemadata/poolcaps-fs.xml
@@ -204,4 +204,11 @@
</enum>
</volOptions>
</pool>
+ <pool type='vitastor' supported='no'>
+ <volOptions>
+ <defaultFormat type='raw'/>
+ <enum name='targetFormatType'>
+ </enum>
+ </volOptions>
+ </pool>
</storagepoolCapabilities>
diff --git a/tests/storagepoolcapsschemadata/poolcaps-full.xml b/tests/storagepoolcapsschemadata/poolcaps-full.xml
index 805950a937..852df0de16 100644
--- a/tests/storagepoolcapsschemadata/poolcaps-full.xml
+++ b/tests/storagepoolcapsschemadata/poolcaps-full.xml
@@ -204,4 +204,11 @@
</enum>
</volOptions>
</pool>
+ <pool type='vitastor' supported='yes'>
+ <volOptions>
+ <defaultFormat type='raw'/>
+ <enum name='targetFormatType'>
+ </enum>
+ </volOptions>
+ </pool>
</storagepoolCapabilities>
diff --git a/tests/storagepoolxml2argvtest.c b/tests/storagepoolxml2argvtest.c
index d5c2531ab8..b19308ac38 100644
--- a/tests/storagepoolxml2argvtest.c
+++ b/tests/storagepoolxml2argvtest.c
@@ -57,6 +57,7 @@ testCompareXMLToArgvFiles(bool shouldFail,
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_ZFS:
case VIR_STORAGE_POOL_VSTORAGE:
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_LAST:
default:
VIR_TEST_DEBUG("pool type '%s' has no xml2argv test", defTypeStr);
diff --git a/tools/virsh-pool.c b/tools/virsh-pool.c
index 2010ef1356..072e2ff9e8 100644
--- a/tools/virsh-pool.c
+++ b/tools/virsh-pool.c
@@ -1187,6 +1187,9 @@ cmdPoolList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
case VIR_STORAGE_POOL_VSTORAGE:
flags |= VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE;
break;
+ case VIR_STORAGE_POOL_VITASTOR:
+ flags |= VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR;
+ break;
case VIR_STORAGE_POOL_LAST:
break;
}
+171 -28
View File
@@ -1,29 +1,172 @@
diff --git a/src/client/qemu_driver.c b/src/client/qemu_driver.c
index d8356dab..5f4cd50d 100644
--- a/src/client/qemu_driver.c
+++ b/src/client/qemu_driver.c
@@ -974,14 +974,21 @@ static void vitastor_co_read_bitmap_cb(void *opaque, long retval, uint8_t *bitma
#endif
}
diff --git a/block/meson.build b/block/meson.build
index 34b1b2a306..24ca0f1e52 100644
--- a/block/meson.build
+++ b/block/meson.build
@@ -114,6 +114,7 @@ foreach m : [
[libnfs, 'nfs', files('nfs.c')],
[libssh, 'ssh', files('ssh.c')],
[rbd, 'rbd', files('rbd.c')],
+ [vitastor, 'vitastor', files('vitastor.c')],
]
if m[0].found()
module_ss = ss.source_set()
diff --git a/meson.build b/meson.build
index 50c774a195..e5c7a3a4b1 100644
--- a/meson.build
+++ b/meson.build
@@ -1652,6 +1652,26 @@ if not get_option('rbd').auto() or have_block
endif
endif
-static int coroutine_fn vitastor_co_block_status(
- BlockDriverState *bs, bool want_zero, int64_t offset, int64_t bytes,
- int64_t *pnum, int64_t *map, BlockDriverState **file)
+static int coroutine_fn vitastor_co_block_status(BlockDriverState *bs,
+#if QEMU_VERSION_MAJOR > 10 || QEMU_VERSION_MAJOR == 10 && QEMU_VERSION_MINOR >= 1
+ unsigned int mode,
+#else
+ bool want_zero,
+#endif
+ int64_t offset, int64_t bytes, int64_t *pnum, int64_t *map, BlockDriverState **file)
{
// Allocated => return BDRV_BLOCK_DATA|BDRV_BLOCK_OFFSET_VALID
// Not allocated => return 0
// Error => return -errno
// Set pnum to length of the extent, `*map` = `offset`, `*file` = `bs`
+#if QEMU_VERSION_MAJOR > 10 || QEMU_VERSION_MAJOR == 10 && QEMU_VERSION_MINOR >= 1
+ int want_zero = (mode == BDRV_WANT_PRECISE);
+#endif
VitastorRPC task;
VitastorClient *client = bs->opaque;
uint64_t inode = client->watch ? vitastor_c_inode_get_num(client->watch) : client->inode;
+vitastor = not_found
+if not get_option('vitastor').auto() or have_block
+ libvitastor_client = cc.find_library('vitastor_client', has_headers: ['vitastor_c.h'],
+ required: get_option('vitastor'))
+ if libvitastor_client.found()
+ if cc.links('''
+ #include <vitastor_c.h>
+ int main(void) {
+ vitastor_c_create_qemu(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ return 0;
+ }''', dependencies: libvitastor_client)
+ vitastor = declare_dependency(dependencies: libvitastor_client)
+ elif get_option('vitastor').enabled()
+ error('could not link libvitastor_client')
+ else
+ warning('could not link libvitastor_client, disabling')
+ endif
+ endif
+endif
+
glusterfs = not_found
glusterfs_ftruncate_has_stat = false
glusterfs_iocb_has_stat = false
@@ -2547,6 +2567,7 @@ endif
config_host_data.set('CONFIG_OPENGL', opengl.found())
config_host_data.set('CONFIG_PLUGIN', get_option('plugins'))
config_host_data.set('CONFIG_RBD', rbd.found())
+config_host_data.set('CONFIG_VITASTOR', vitastor.found())
config_host_data.set('CONFIG_RDMA', rdma.found())
config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
@@ -4972,6 +4993,7 @@ summary_info += {'fdt support': fdt_opt == 'internal' ? 'internal' : fdt}
summary_info += {'libcap-ng support': libcap_ng}
summary_info += {'bpf support': libbpf}
summary_info += {'rbd support': rbd}
+summary_info += {'vitastor support': vitastor}
summary_info += {'smartcard support': cacard}
summary_info += {'U2F support': u2f}
summary_info += {'libusb': libusb}
diff --git a/meson_options.txt b/meson_options.txt
index fff1521e58..f0844c0e00 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -202,6 +202,8 @@ option('pvg', type: 'feature', value: 'auto',
description: 'macOS paravirtualized graphics support')
option('rbd', type : 'feature', value : 'auto',
description: 'Ceph block device driver')
+option('vitastor', type : 'feature', value : 'auto',
+ description: 'Vitastor block device driver')
option('opengl', type : 'feature', value : 'auto',
description: 'OpenGL support')
option('rdma', type : 'feature', value : 'auto',
diff --git a/qapi/block-core.json b/qapi/block-core.json
index dc6eb4ae23..d043f4340e 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3280,7 +3280,7 @@
'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum',
'raw', 'rbd',
{ 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
- 'ssh', 'throttle', 'vdi', 'vhdx',
+ 'ssh', 'throttle', 'vdi', 'vhdx', 'vitastor',
{ 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' },
{ 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' },
{ 'name': 'virtio-blk-vhost-vdpa', 'if': 'CONFIG_BLKIO' },
@@ -4363,6 +4363,28 @@
'*key-secret': 'str',
'*server': ['InetSocketAddressBase'] } }
+##
+# @BlockdevOptionsVitastor:
+#
+# Driver specific block device options for vitastor
+#
+# @image: Image name
+# @inode: Inode number
+# @pool: Pool ID
+# @size: Desired image size in bytes
+# @config-path: Path to Vitastor configuration
+# @etcd-host: etcd connection address(es)
+# @etcd-prefix: etcd key/value prefix
+##
+{ 'struct': 'BlockdevOptionsVitastor',
+ 'data': { '*inode': 'uint64',
+ '*pool': 'uint64',
+ '*size': 'uint64',
+ '*image': 'str',
+ '*config-path': 'str',
+ '*etcd-host': 'str',
+ '*etcd-prefix': 'str' } }
+
##
# @ReplicationMode:
#
@@ -4831,6 +4853,7 @@
'throttle': 'BlockdevOptionsThrottle',
'vdi': 'BlockdevOptionsGenericFormat',
'vhdx': 'BlockdevOptionsGenericFormat',
+ 'vitastor': 'BlockdevOptionsVitastor',
'virtio-blk-vfio-pci':
{ 'type': 'BlockdevOptionsVirtioBlkVfioPci',
'if': 'CONFIG_BLKIO' },
@@ -5304,6 +5327,20 @@
'*cluster-size' : 'size',
'*encrypt' : 'RbdEncryptionCreateOptions' } }
+##
+# @BlockdevCreateOptionsVitastor:
+#
+# Driver specific image creation options for Vitastor.
+#
+# @location: Where to store the new image file. This location cannot
+# point to a snapshot.
+#
+# @size: Size of the virtual disk in bytes
+##
+{ 'struct': 'BlockdevCreateOptionsVitastor',
+ 'data': { 'location': 'BlockdevOptionsVitastor',
+ 'size': 'size' } }
+
##
# @BlockdevVmdkSubformat:
#
@@ -5526,6 +5563,7 @@
'ssh': 'BlockdevCreateOptionsSsh',
'vdi': 'BlockdevCreateOptionsVdi',
'vhdx': 'BlockdevCreateOptionsVhdx',
+ 'vitastor': 'BlockdevCreateOptionsVitastor',
'vmdk': 'BlockdevCreateOptionsVmdk',
'vpc': 'BlockdevCreateOptionsVpc'
} }
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 0ebe6bc52a..2c37ad3892 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -175,6 +175,7 @@ meson_options_help() {
printf "%s\n" ' qga-vss build QGA VSS support (broken with MinGW)'
printf "%s\n" ' qpl Query Processing Library support'
printf "%s\n" ' rbd Ceph block device driver'
+ printf "%s\n" ' vitastor Vitastor block device driver'
printf "%s\n" ' rdma Enable RDMA-based migration'
printf "%s\n" ' replication replication support'
printf "%s\n" ' rust Rust support'
@@ -459,6 +460,8 @@ _meson_option_parse() {
--disable-qpl) printf "%s" -Dqpl=disabled ;;
--enable-rbd) printf "%s" -Drbd=enabled ;;
--disable-rbd) printf "%s" -Drbd=disabled ;;
+ --enable-vitastor) printf "%s" -Dvitastor=enabled ;;
+ --disable-vitastor) printf "%s" -Dvitastor=disabled ;;
--enable-rdma) printf "%s" -Drdma=enabled ;;
--disable-rdma) printf "%s" -Drdma=disabled ;;
--enable-relocatable) printf "%s" -Drelocatable=true ;;
+172
View File
@@ -0,0 +1,172 @@
diff --git a/block/meson.build b/block/meson.build
index 34b1b2a306..24ca0f1e52 100644
--- a/block/meson.build
+++ b/block/meson.build
@@ -114,6 +114,7 @@ foreach m : [
[libnfs, 'nfs', files('nfs.c')],
[libssh, 'ssh', files('ssh.c')],
[rbd, 'rbd', files('rbd.c')],
+ [vitastor, 'vitastor', files('vitastor.c')],
]
if m[0].found()
module_ss = ss.source_set()
diff --git a/meson.build b/meson.build
index d9293294d8..776a5becc6 100644
--- a/meson.build
+++ b/meson.build
@@ -1665,6 +1665,26 @@ if not get_option('rbd').auto() or have_block
endif
endif
+vitastor = not_found
+if not get_option('vitastor').auto() or have_block
+ libvitastor_client = cc.find_library('vitastor_client', has_headers: ['vitastor_c.h'],
+ required: get_option('vitastor'))
+ if libvitastor_client.found()
+ if cc.links('''
+ #include <vitastor_c.h>
+ int main(void) {
+ vitastor_c_create_qemu(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ return 0;
+ }''', dependencies: libvitastor_client)
+ vitastor = declare_dependency(dependencies: libvitastor_client)
+ elif get_option('vitastor').enabled()
+ error('could not link libvitastor_client')
+ else
+ warning('could not link libvitastor_client, disabling')
+ endif
+ endif
+endif
+
glusterfs = not_found
glusterfs_ftruncate_has_stat = false
glusterfs_iocb_has_stat = false
@@ -2509,6 +2529,7 @@ endif
config_host_data.set('CONFIG_OPENGL', opengl.found())
config_host_data.set('CONFIG_PLUGIN', get_option('plugins'))
config_host_data.set('CONFIG_RBD', rbd.found())
+config_host_data.set('CONFIG_VITASTOR', vitastor.found())
config_host_data.set('CONFIG_RDMA', rdma.found())
config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
@@ -4948,6 +4969,7 @@ summary_info += {'fdt support': fdt_opt == 'internal' ? 'internal' : fdt}
summary_info += {'libcap-ng support': libcap_ng}
summary_info += {'bpf support': libbpf}
summary_info += {'rbd support': rbd}
+summary_info += {'vitastor support': vitastor}
summary_info += {'smartcard support': cacard}
summary_info += {'U2F support': u2f}
summary_info += {'libusb': libusb}
diff --git a/meson_options.txt b/meson_options.txt
index 2836156257..148086cc6f 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -206,6 +206,8 @@ option('pvg', type: 'feature', value: 'auto',
description: 'macOS paravirtualized graphics support')
option('rbd', type : 'feature', value : 'auto',
description: 'Ceph block device driver')
+option('vitastor', type : 'feature', value : 'auto',
+ description: 'Vitastor block device driver')
option('opengl', type : 'feature', value : 'auto',
description: 'OpenGL support')
option('rdma', type : 'feature', value : 'auto',
diff --git a/qapi/block-core.json b/qapi/block-core.json
index b82af74256..f25a6f5ce8 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3351,7 +3351,7 @@
'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum',
'raw', 'rbd',
{ 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
- 'ssh', 'throttle', 'vdi', 'vhdx',
+ 'ssh', 'throttle', 'vdi', 'vhdx', 'vitastor',
{ 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' },
{ 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' },
{ 'name': 'virtio-blk-vhost-vdpa', 'if': 'CONFIG_BLKIO' },
@@ -4434,6 +4434,28 @@
'*key-secret': 'str',
'*server': ['InetSocketAddressBase'] } }
+##
+# @BlockdevOptionsVitastor:
+#
+# Driver specific block device options for vitastor
+#
+# @image: Image name
+# @inode: Inode number
+# @pool: Pool ID
+# @size: Desired image size in bytes
+# @config-path: Path to Vitastor configuration
+# @etcd-host: etcd connection address(es)
+# @etcd-prefix: etcd key/value prefix
+##
+{ 'struct': 'BlockdevOptionsVitastor',
+ 'data': { '*inode': 'uint64',
+ '*pool': 'uint64',
+ '*size': 'uint64',
+ '*image': 'str',
+ '*config-path': 'str',
+ '*etcd-host': 'str',
+ '*etcd-prefix': 'str' } }
+
##
# @ReplicationMode:
#
@@ -4902,6 +4924,7 @@
'throttle': 'BlockdevOptionsThrottle',
'vdi': 'BlockdevOptionsGenericFormat',
'vhdx': 'BlockdevOptionsGenericFormat',
+ 'vitastor': 'BlockdevOptionsVitastor',
'virtio-blk-vfio-pci':
{ 'type': 'BlockdevOptionsVirtioBlkVfioPci',
'if': 'CONFIG_BLKIO' },
@@ -5376,6 +5399,20 @@
'*cluster-size' : 'size',
'*encrypt' : 'RbdEncryptionCreateOptions' } }
+##
+# @BlockdevCreateOptionsVitastor:
+#
+# Driver specific image creation options for Vitastor.
+#
+# @location: Where to store the new image file. This location cannot
+# point to a snapshot.
+#
+# @size: Size of the virtual disk in bytes
+##
+{ 'struct': 'BlockdevCreateOptionsVitastor',
+ 'data': { 'location': 'BlockdevOptionsVitastor',
+ 'size': 'size' } }
+
##
# @BlockdevVmdkSubformat:
#
@@ -5598,6 +5635,7 @@
'ssh': 'BlockdevCreateOptionsSsh',
'vdi': 'BlockdevCreateOptionsVdi',
'vhdx': 'BlockdevCreateOptionsVhdx',
+ 'vitastor': 'BlockdevCreateOptionsVitastor',
'vmdk': 'BlockdevCreateOptionsVmdk',
'vpc': 'BlockdevCreateOptionsVpc'
} }
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 3d0d132344..65ee8c855e 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -177,6 +177,7 @@ meson_options_help() {
printf "%s\n" ' qga-vss build QGA VSS support (broken with MinGW)'
printf "%s\n" ' qpl Query Processing Library support'
printf "%s\n" ' rbd Ceph block device driver'
+ printf "%s\n" ' vitastor Vitastor block device driver'
printf "%s\n" ' rdma Enable RDMA-based migration'
printf "%s\n" ' replication replication support'
printf "%s\n" ' rust Rust support'
@@ -464,6 +465,8 @@ _meson_option_parse() {
--disable-qpl) printf "%s" -Dqpl=disabled ;;
--enable-rbd) printf "%s" -Drbd=enabled ;;
--disable-rbd) printf "%s" -Drbd=disabled ;;
+ --enable-vitastor) printf "%s" -Dvitastor=enabled ;;
+ --disable-vitastor) printf "%s" -Dvitastor=disabled ;;
--enable-rdma) printf "%s" -Drdma=enabled ;;
--disable-rdma) printf "%s" -Drdma=disabled ;;
--enable-relocatable) printf "%s" -Drelocatable=true ;;
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.5
Version: 3.0.10
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.5.el10.tar.gz
Source0: vitastor-3.0.10.el10.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.5
Version: 3.0.10
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.5.el7.tar.gz
Source0: vitastor-3.0.10.el7.tar.gz
BuildRequires: gperftools-devel
BuildRequires: devtoolset-9-gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.5
Version: 3.0.10
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.5.el8.tar.gz
Source0: vitastor-3.0.10.el8.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-toolset-9-gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.5
Version: 3.0.10
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.5.el9.tar.gz
Source0: vitastor-3.0.10.el9.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
+7 -6
View File
@@ -1,9 +1,8 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
include(GNUInstallDirs)
include(CTest)
include(CheckIncludeFile)
find_package(PkgConfig)
@@ -21,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.5")
add_definitions(-DVITASTOR_VERSION="3.0.10")
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})
@@ -70,15 +69,17 @@ pkg_check_modules(ISAL libisal)
if (ISAL_LIBRARIES)
add_definitions(-DWITH_ISAL)
endif (ISAL_LIBRARIES)
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
if (ISAL_CRYPTO_LIBRARIES)
add_definitions(-DWITH_ISAL_CRYPTO)
endif (ISAL_CRYPTO_LIBRARIES)
pkg_check_modules(RDMACM librdmacm)
if (RDMACM_LIBRARIES)
add_definitions(-DWITH_RDMACM)
endif (RDMACM_LIBRARIES)
find_package(OpenSSL REQUIRED)
if (OPENSSL_FOUND)
add_definitions(-DWITH_OPENSSL)
endif (OPENSSL_FOUND)
add_definitions(-DWITH_OPENSSL)
pkg_check_modules(CARES REQUIRED libcares)
include_directories(${CARES_INCLUDE_DIRS})
+2 -2
View File
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
# libvitastor_blk.a
add_library(vitastor_blk STATIC
../util/allocator.cpp ../util/crc32c.c ../util/xxhash.c ../util/ringloop.cpp
../util/allocator.cpp ../util/crc32c.c ../util/ringloop.cpp
multilist.cpp blockstore_heap.cpp blockstore_disk.cpp
blockstore.cpp blockstore_impl.cpp blockstore_init.cpp blockstore_open.cpp
blockstore_flush.cpp blockstore_read.cpp blockstore_stable.cpp blockstore_sync.cpp blockstore_write.cpp
+5
View File
@@ -228,4 +228,9 @@ public:
virtual uint64_t get_journal_size() = 0;
virtual uint32_t get_bitmap_granularity() = 0;
virtual uint64_t get_live_entries() = 0;
virtual uint64_t get_live_memory() = 0;
virtual uint64_t get_garbage_entries() = 0;
virtual uint64_t get_garbage_memory() = 0;
};
+2
View File
@@ -98,6 +98,8 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
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");
min_discard_size = parse_size(config["min_discard_size"]);
if (!min_discard_size)
min_discard_size = 1024*1024;
+2
View File
@@ -58,6 +58,8 @@ struct blockstore_disk_t
bool inmemory_journal = true;
// Data discard granularity and minimum size (for the sake of performance)
bool discard_on_start = false;
// GC on start (new store)
bool gc_on_start = true;
uint64_t min_discard_size = 1024*1024;
uint64_t discard_granularity = 0;
+193 -78
View File
@@ -12,7 +12,7 @@
#include "blockstore_heap.h"
#include "../util/allocator.h"
#include "../util/crc32c.h"
#include "../util/xxhash.h"
#include "../util/xxh_x86dispatch.h"
#include "../util/malloc_or_die.h"
#define BS_HEAP_FREE_MVCC 1
@@ -30,6 +30,8 @@
#define IMAP_MALLOC_LOW_BITS ((size_t)0x0F)
#define IMAP_MAX_LOW 16
#define list_item_overhead(a) (((a) + sizeof(heap_list_item_t) - sizeof(heap_entry_t) + sizeof(void*) + 15) & ~15)
void inode_map_put(void* & inode_idx, heap_list_item_t* li);
void inode_map_get(void *inode_idx, heap_inode_map_t::iterator & li_it, heap_list_item_t* & li, uint64_t stripe);
void inode_map_free(void* inode_idx);
@@ -254,7 +256,7 @@ blockstore_heap_t::blockstore_heap_t(blockstore_disk_t *dsk, uint8_t *buffer_are
assert(dsk->meta_block_size < 32768);
assert(dsk->meta_area_size > 0);
assert(dsk->journal_len > 0);
meta_alloc = new multilist_index_t(meta_block_count, META_ALLOC_LEVELS+1, 0);
meta_alloc = new multilist_index_t(meta_block_count, META_ALLOC_LEVELS+1, 2);
block_info.resize(meta_block_count);
assert(dsk->block_count <= 0xFFFF0000);
data_alloc = new allocator_t(dsk->block_count);
@@ -342,19 +344,12 @@ corrupted_block:
block_num, block_offset, wr->size, sizeof(heap_entry_t));
goto corrupted_block;
}
if (wr->is_garbage())
{
// Garbage collection is only performed when writing new entries into the block
// because it needs a fake LSN and modified blocks require consecutive modified LSNs
// That's why garbage entries may persist on disk
if (log_level > 5)
{
fprintf(stderr, "Notice: skipping garbage entry %jx:%jx v%ju l%ju in metadata block %u at %u\n",
wr->inode, wr->stripe, wr->version, wr->lsn, block_num, block_offset);
}
block_offset += wr->size;
continue;
}
// Garbage collection is only performed when writing new entries into the block
// because it needs a fake LSN and modified blocks require consecutive modified LSNs
// At the same time, further modifications _after_ putting new entries into the block,
// but _before_ writing it, may mark some entries in it as garbage. That's why garbage
// entries may still be present on disk.
wr->entry_type &= ~BS_HEAP_GARBAGE;
if ((wr->entry_type & BS_HEAP_TYPE) < BS_HEAP_BIG_WRITE ||
(wr->entry_type & BS_HEAP_TYPE) > BS_HEAP_ROLLBACK ||
(wr->entry_type & ~(BS_HEAP_TYPE|BS_HEAP_STABLE)) ||
@@ -436,7 +431,10 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
entries_loaded = 0;
return read_blocks(disk_offset, size, buf, allow_corrupted, [&](uint32_t block_num, heap_entry_t *wr_orig)
{
heap_list_item_t *li = (heap_list_item_t*)malloc_or_die(wr_orig->size + sizeof(heap_list_item_t) - sizeof(heap_entry_t));
auto alloc_size = wr_orig->size + sizeof(heap_list_item_t) - sizeof(heap_entry_t);
heap_list_item_t *li = (heap_list_item_t*)malloc_or_die(alloc_size);
live_entries++;
live_memory += list_item_overhead(wr_orig->size);
li->block_num = block_num;
li->prev = li->next = NULL;
memcpy(&li->entry, wr_orig, wr_orig->size);
@@ -621,10 +619,11 @@ int blockstore_heap_t::mark_used_blocks()
if (wr->entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE) && !li->prev)
{
wr->set_garbage();
garbage_entries++;
garbage_memory += list_item_overhead(wr->size);
modify_alloc(li->block_num, [&](heap_block_info_t & inf)
{
inf.used_space -= wr->size;
inf.has_garbage = true;
inf.garbage_space += wr->size;
});
li = NULL;
}
@@ -634,10 +633,11 @@ int blockstore_heap_t::mark_used_blocks()
if (overwritten)
{
wr->set_garbage();
garbage_entries++;
garbage_memory += list_item_overhead(wr->size);
modify_alloc(li->block_num, [&](heap_block_info_t & inf)
{
inf.used_space -= wr->size;
inf.has_garbage = true;
inf.garbage_space += wr->size;
});
continue;
}
@@ -676,15 +676,66 @@ int blockstore_heap_t::mark_used_blocks()
});
}
}
if (dsk->gc_on_start)
{
recheck_full_gc();
}
return res;
}
void blockstore_heap_t::recheck_full_gc()
{
uint32_t block_num = 0;
for (auto & inf: block_info)
{
// Instantly collect all garbage on restart
if (inf.garbage_space > 0)
{
if (log_level > 5)
{
fprintf(stderr, "Clearing %u out of %u garbage bytes in block %u\n", inf.garbage_space, inf.used_space, block_num);
}
uint32_t collected_garbage = 0;
size_t i = 0, j = 0;
for (; i < inf.entries.size(); i++)
{
if (inf.entries[i]->entry.is_garbage())
{
collected_garbage += inf.entries[i]->entry.size;
remove_list_item(inf.entries[i]);
}
else
{
if (j != i)
inf.entries[j] = inf.entries[i];
j++;
}
}
inf.entries.resize(j);
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
inf.used_space -= collected_garbage;
inf.garbage_space -= collected_garbage;
});
recheck_modified_blocks.insert(block_num);
}
block_num++;
}
}
void blockstore_heap_t::recheck_buffer(heap_entry_t *cwr, uint8_t *buf)
{
auto free_entry = [&](heap_list_item_t *li)
{
uint32_t block_num = li->block_num;
auto wr_size = li->entry.size;
if (li->entry.is_garbage())
{
garbage_entries--;
garbage_memory -= list_item_overhead(wr_size);
}
live_entries--;
live_memory -= list_item_overhead(wr_size);
free(li);
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
@@ -722,7 +773,12 @@ void blockstore_heap_t::recheck_buffer(heap_entry_t *cwr, uint8_t *buf)
assert(li->entry.entry_type == cwr->entry_type);
auto prev = li->prev;
li->next = li->prev = NULL;
li->entry.set_garbage();
if (!li->entry.is_garbage())
{
garbage_entries++;
garbage_memory += list_item_overhead(li->entry.size);
li->entry.set_garbage();
}
li = prev;
rolled_back++;
}
@@ -1238,7 +1294,7 @@ heap_entry_t *blockstore_heap_t::read_entry(object_id oid)
void blockstore_heap_t::gc_block(heap_block_info_t & inf)
{
if (inf.has_garbage)
if (inf.garbage_space > 0)
{
size_t i = 0, j = 0;
for (; i < inf.entries.size(); i++)
@@ -1261,7 +1317,8 @@ void blockstore_heap_t::gc_block(heap_block_info_t & inf)
}
}
inf.entries.resize(j);
inf.has_garbage = false;
inf.used_space -= inf.garbage_space;
inf.garbage_space = 0;
}
}
@@ -1271,11 +1328,12 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
{
// First try to write into the same block as the previous time
auto & inf = block_info.at(last_allocated_block);
auto free_space = dsk->meta_block_size - inf.used_space;
if (inf.is_writing || free_space < entry_size ||
if (inf.is_writing ||
inf.used_space - inf.garbage_space + entry_size > dsk->meta_block_size ||
// Do not allow to make the last non-nearfull block nearfull
!allow_last_free && meta_nearfull_blocks >= meta_block_count-1 &&
free_space >= max_entry_size && free_space < max_entry_size+entry_size)
inf.used_space - inf.garbage_space <= dsk->meta_block_size-max_entry_size &&
inf.used_space - inf.garbage_space + entry_size > dsk->meta_block_size-max_entry_size)
{
last_allocated_block = UINT32_MAX;
}
@@ -1292,8 +1350,8 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
{
// Do not allow to make the last non-nearfull block nearfull
auto & inf = block_info.at(last_allocated_block);
auto free_space = dsk->meta_block_size - inf.used_space;
if (free_space >= max_entry_size && free_space < max_entry_size+entry_size)
if (inf.used_space - inf.garbage_space <= dsk->meta_block_size-max_entry_size &&
inf.used_space - inf.garbage_space + entry_size > dsk->meta_block_size-max_entry_size)
{
last_allocated_block = UINT32_MAX;
}
@@ -1304,8 +1362,7 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
for (uint32_t b = meta_alloc->find(META_ALLOC_LEVELS-1); b != UINT32_MAX; b = meta_alloc->next(b))
{
auto & inf = block_info.at(b);
auto free_space = dsk->meta_block_size - inf.used_space;
if (free_space >= entry_size)
if (inf.used_space - inf.garbage_space + entry_size <= dsk->meta_block_size)
{
last_allocated_block = b;
break;
@@ -1322,20 +1379,27 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
{
// Do not allow to make the last non-nearfull block nearfull
auto & inf = block_info.at(last_allocated_block);
if (dsk->meta_block_size-inf.used_space >= max_entry_size &&
dsk->meta_block_size-inf.used_space+entry_size < max_entry_size)
if (inf.used_space - inf.garbage_space <= dsk->meta_block_size-max_entry_size &&
inf.used_space - inf.garbage_space + entry_size > dsk->meta_block_size-max_entry_size)
{
last_allocated_block = UINT32_MAX;
return ENOSPC;
}
}
// Write into the same block
auto & inf = block_info.at(last_allocated_block);
gc_block(inf);
*block_num = last_allocated_block;
modify_alloc(last_allocated_block, [&](heap_block_info_t & inf)
{
// Write just 1 entry to the block to collect garbage
if (inf.garbage_space > (inf.used_space-inf.garbage_space)/2)
last_allocated_block = UINT32_MAX;
gc_block(inf);
inf.used_space += entry_size;
assert(inf.used_space - inf.garbage_space <= dsk->meta_block_size);
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn);
++next_lsn;
inf.mod_lsn = inf.mod_lsn ? inf.mod_lsn : next_lsn;
inf.mod_lsn_to = next_lsn;
});
return 0;
}
@@ -1394,16 +1458,15 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
*modified_block = block_num;
}
auto li = (heap_list_item_t*)malloc_or_die(wr_size + sizeof(heap_list_item_t) - sizeof(heap_entry_t));
live_entries++;
live_memory += list_item_overhead(wr_size);
auto new_wr = &li->entry;
auto & inf = block_info.at(block_num);
if (!inf.entries.size())
inf.entries.reserve(dsk->meta_block_size / max_entry_size);
inf.entries.push_back(li);
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn);
new_wr->lsn = ++next_lsn;
new_wr->lsn = next_lsn;
fill_entry(new_wr);
inf.mod_lsn = inf.mod_lsn ? inf.mod_lsn : next_lsn;
inf.mod_lsn_to = next_lsn;
// Remember the object as dirty and remove older entries when this block is written and fsynced
push_inflight_lsn(next_lsn, new_wr,
(explicit_complete ? HEAP_INFLIGHT_EXPLICIT : 0) |
@@ -1742,28 +1805,38 @@ int blockstore_heap_t::add_delete(heap_entry_t *obj, uint32_t *modified_block)
uint32_t blockstore_heap_t::meta_alloc_pos(const heap_block_info_t & inf)
{
if (inf.is_writing || inf.used_space > dsk->meta_block_size-sizeof(heap_entry_t))
auto real_used = (inf.used_space-inf.garbage_space);
if (inf.is_writing || inf.mod_lsn || real_used > dsk->meta_block_size-sizeof(heap_entry_t))
{
// 100% full - no entry can be written into this block at all
return META_ALLOC_LEVELS;
}
if (inf.used_space > dsk->meta_block_size-max_entry_size)
if (real_used > dsk->meta_block_size-max_entry_size)
{
// nearfull - big_entries won't fit into this block so it can't be used for compaction
return META_ALLOC_LEVELS-1;
}
// normal block
return inf.used_space / ((dsk->meta_block_size-max_entry_size+META_ALLOC_LEVELS-2) / (META_ALLOC_LEVELS-1));
// First we want to write to blocks with most garbage:
// >= 2*used, >= used/2
// (i.e. 66% garbage, 33% garbage)
// Then to mostly free blocks:
// >= 75% free, >= 50% free, >= 25% free
if (inf.garbage_space > real_used*2)
return 0;
if (inf.garbage_space > real_used/2)
return 1;
// META_ALLOC_LEVELS-3 levels left
return 2 + real_used / ((dsk->meta_block_size-max_entry_size+META_ALLOC_LEVELS-4) / (META_ALLOC_LEVELS-3));
}
void blockstore_heap_t::modify_alloc(uint32_t block_num, std::function<void(heap_block_info_t &)> change_cb)
{
auto & inf = block_info.at(block_num);
uint32_t old_pos = meta_alloc_pos(inf);
uint32_t old_used = inf.used_space;
uint32_t old_used = inf.used_space-inf.garbage_space;
change_cb(inf);
uint32_t new_pos = meta_alloc_pos(inf);
uint32_t new_used = inf.used_space;
uint32_t new_used = inf.used_space-inf.garbage_space;
meta_alloc->change(block_num, old_pos, new_pos);
meta_used_space -= old_used;
meta_used_space += new_used;
@@ -1775,11 +1848,19 @@ void blockstore_heap_t::modify_alloc(uint32_t block_num, std::function<void(heap
void blockstore_heap_t::start_block_write(uint32_t block_num)
{
modify_alloc(block_num, [&](heap_block_info_t & inf)
auto & inf = block_info.at(block_num);
assert(!inf.is_writing);
if (!inf.mod_lsn)
{
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
inf.is_writing = true;
});
}
else
{
assert(!inf.is_writing);
inf.is_writing = true;
});
}
}
void blockstore_heap_t::complete_block_write(uint32_t block_num)
@@ -1844,6 +1925,8 @@ void blockstore_heap_t::mark_garbage_up_to(heap_entry_t *wr)
void blockstore_heap_t::mark_garbage(uint32_t block_num, heap_entry_t *prev_wr, uint32_t used_big)
{
prev_wr->set_garbage();
garbage_entries++;
garbage_memory += list_item_overhead(prev_wr->size);
// And this is the moment when we can free the data reference
if (prev_wr->type() == BS_HEAP_SMALL_WRITE && prev_wr->small().len > 0)
{
@@ -1859,8 +1942,7 @@ void blockstore_heap_t::mark_garbage(uint32_t block_num, heap_entry_t *prev_wr,
}
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
inf.used_space -= prev_wr->size;
inf.has_garbage = true;
inf.garbage_space += prev_wr->size;
});
}
@@ -2230,7 +2312,7 @@ void blockstore_heap_t::fill_block_empty_space(uint8_t *buffer, uint32_t pos)
uint32_t blockstore_heap_t::get_meta_block_used_space(uint32_t block_num)
{
auto & inf = block_info.at(block_num);
return inf.used_space;
return inf.used_space - inf.garbage_space;
}
uint64_t blockstore_heap_t::get_data_used_space()
@@ -2273,6 +2355,26 @@ uint64_t blockstore_heap_t::get_compacted_count()
return compacted_count;
}
uint64_t blockstore_heap_t::get_live_entries()
{
return live_entries-garbage_entries;
}
uint64_t blockstore_heap_t::get_live_memory()
{
return live_memory-garbage_memory;
}
uint64_t blockstore_heap_t::get_garbage_entries()
{
return garbage_entries;
}
uint64_t blockstore_heap_t::get_garbage_memory()
{
return garbage_memory;
}
void blockstore_heap_t::push_inflight_lsn(uint64_t lsn, heap_entry_t *wr, uint64_t flags)
{
uint64_t next_inf = first_inflight_lsn + inflight_lsn.size();
@@ -2359,37 +2461,50 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
{
// Remove entry
auto li = list_item(wr);
auto prev = li->prev;
auto next = li->next;
if (prev)
{
prev->next = next;
}
if (!next)
{
// The last freed entry must be a deletion
assert(!prev);
assert(wr->entry_type == BS_HEAP_DELETE|BS_HEAP_STABLE);
auto & pg_idx = block_index[get_pg_id(wr->inode, wr->stripe)];
auto & inode_idx = pg_idx[wr->inode];
heap_inode_map_t::iterator li_it;
heap_list_item_t *old_li = NULL;
inode_map_get(inode_idx, li_it, old_li, wr->stripe);
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
}
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);
}
}
free(li);
remove_list_item(li);
}
}
void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
{
auto prev = li->prev;
auto next = li->next;
if (prev)
{
prev->next = next;
}
if (!next)
{
// The last freed entry must be a deletion
assert(!prev);
auto wr = &li->entry;
assert(wr->entry_type == BS_HEAP_DELETE|BS_HEAP_STABLE);
auto & pg_idx = block_index[get_pg_id(wr->inode, wr->stripe)];
auto & inode_idx = pg_idx[wr->inode];
heap_inode_map_t::iterator li_it;
heap_list_item_t *old_li = NULL;
inode_map_get(inode_idx, li_it, old_li, wr->stripe);
inode_map_erase(pg_idx, inode_idx, li_it, old_li);
}
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())
{
garbage_entries--;
garbage_memory -= list_item_overhead(li->entry.size);
}
live_entries--;
live_memory -= list_item_overhead(li->entry.size);
free(li);
}
bool blockstore_heap_t::is_lsn_completed(uint64_t lsn)
{
if (lsn <= completed_lsn)
+17 -3
View File
@@ -118,10 +118,13 @@ struct heap_object_mvcc_t
struct heap_block_info_t
{
uint32_t used_space = 0;
struct __attribute__((__packed__))
{
uint32_t used_space = 0;
uint32_t garbage_space = 0;
};
uint64_t mod_lsn = 0, mod_lsn_to = 0; // only 1 block write of LSN sequence is allowed at a moment
bool is_writing: 1;
bool has_garbage: 1;
bool is_writing = false;
std::vector<heap_list_item_t*> entries;
};
@@ -185,6 +188,11 @@ class blockstore_heap_t
uint64_t buffer_area_used_space = 0;
uint64_t data_used_space = 0;
uint64_t live_entries = 0;
uint64_t live_memory = 0;
uint64_t garbage_entries = 0;
uint64_t garbage_memory = 0;
uint64_t next_lsn = 0;
uint32_t last_allocated_block = UINT32_MAX;
heap_mvcc_map_t object_mvcc;
@@ -213,6 +221,7 @@ class blockstore_heap_t
bool validate_object(heap_entry_t *obj);
void fill_recheck_queue();
int mark_used_blocks();
void recheck_full_gc();
void recheck_buffer(heap_entry_t *cwr, uint8_t *buf);
void defragment_block(uint32_t block_num);
void reshard_add(heap_reshard_state_t *st, heap_list_item_t *li);
@@ -220,6 +229,7 @@ class blockstore_heap_t
void gc_block(heap_block_info_t & inf);
int allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free);
void insert_list_item(heap_list_item_t *li);
void remove_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);
@@ -346,6 +356,10 @@ public:
uint32_t get_compact_queue_size();
uint32_t get_to_compact_count();
uint64_t get_compacted_count();
uint64_t get_live_entries();
uint64_t get_live_memory();
uint64_t get_garbage_entries();
uint64_t get_garbage_memory();
uint64_t entry_pos(uint32_t block_num, uint32_t offset);
heap_entry_t *entry_from_pos(uint64_t entry_pos, bool allow_unallocated = false);
+5
View File
@@ -229,4 +229,9 @@ public:
uint64_t get_free_block_count();
inline uint32_t get_bitmap_granularity() { return dsk.bitmap_granularity; }
inline uint64_t get_journal_size() { return dsk.journal_len; }
inline uint64_t get_live_entries() { return heap->get_live_entries(); }
inline uint64_t get_live_memory() { return heap->get_live_memory(); }
inline uint64_t get_garbage_entries() { return heap->get_garbage_entries(); }
inline uint64_t get_garbage_memory() { return heap->get_garbage_memory(); }
};
+11 -2
View File
@@ -240,7 +240,7 @@ resume_4:
}
// metadata read finished
bs->heap->finish_load();
printf("Metadata entries loaded: %ju, used blocks: %ju / %ju\n", entries_loaded, bs->heap->get_data_used_space() / bs->dsk.data_block_size, bs->dsk.block_count);
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();
@@ -293,6 +293,11 @@ resume_7:
if (bs->readonly)
{
recheck_mod.clear();
printf("Actual metadata entries: %ju\n", bs->heap->get_live_entries());
}
else
{
printf("Actual metadata entries: %ju, clearing garbage in %zu metadata blocks\n", bs->heap->get_live_entries(), recheck_mod.size());
}
for (i = 0; i < recheck_mod.size(); i++)
{
@@ -306,7 +311,7 @@ resume_8:
uint32_t block_num = recheck_mod[i];
uint64_t block_offset = bs->dsk.meta_offset + (uint64_t)(block_num+1) * bs->dsk.meta_block_size;
data = ((ring_data_t*)sqe->user_data);
uint8_t *buf = (uint8_t*)malloc_or_die(bs->dsk.meta_block_size);
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, bs->dsk.meta_block_size);
bs->heap->get_meta_block(block_num, buf);
data->iov = { buf, bs->dsk.meta_block_size };
data->callback = [this, buf, block_offset](ring_data_t *data)
@@ -332,5 +337,9 @@ resume_9:
}
free(metadata_buffer);
metadata_buffer = NULL;
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(),
format_size(bs->dsk.block_count * bs->dsk.data_block_size).c_str());
return 0;
}
+4
View File
@@ -462,6 +462,10 @@ int blockstore_impl_t::read_bitmap(object_id oid, uint64_t target_version, void
{
if (target_version >= wr->version)
{
if (wr->type() == BS_HEAP_DELETE)
{
return false;
}
found = true;
if (result_version)
{
+25
View File
@@ -855,4 +855,29 @@ std::string blockstore_impl_t::get_op_diag(blockstore_op_t *op)
return std::string(buf);
}
uint64_t blockstore_impl_t::get_live_entries()
{
return used_blocks;
}
uint64_t blockstore_impl_t::get_live_memory()
{
uint64_t used = 0;
for (auto & kv: clean_db_shards)
{
used += kv.second.size() * sizeof(blockstore_clean_db_t::value_type);
}
return used;
}
uint64_t blockstore_impl_t::get_garbage_entries()
{
return dirty_db.size();
}
uint64_t blockstore_impl_t::get_garbage_memory()
{
return (sizeof(obj_ver_id) + sizeof(dirty_entry) + 32) * dirty_db.size();
}
} // namespace v1
+4
View File
@@ -332,6 +332,10 @@ public:
inline uint64_t get_free_block_count() { return dsk.block_count - used_blocks; }
inline uint32_t get_bitmap_granularity() { return dsk.disk_alignment; }
inline uint64_t get_journal_size() { return dsk.journal_len; }
uint64_t get_live_entries();
uint64_t get_live_memory();
uint64_t get_garbage_entries();
uint64_t get_garbage_memory();
};
} // namespace v1
+7 -6
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
@@ -12,11 +12,11 @@ if (RDMACM_LIBRARIES)
set(MSGR_RDMACM "msgr_rdmacm.cpp")
endif (RDMACM_LIBRARIES)
add_library(vitastor_common STATIC
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp
msgr_encrypt.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 ../util/openssl_util.cpp
msgr_encrypt.cpp msgr_handshake.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
)
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES})
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
target_compile_options(vitastor_common PUBLIC -fPIC)
# libvitastor_client.so
@@ -35,6 +35,7 @@ target_link_libraries(vitastor_client
${IBVERBS_LIBRARIES}
${RDMACM_LIBRARIES}
${OPENSSL_LIBRARIES}
${ISAL_CRYPTO_LIBRARIES}
)
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
configure_file(vitastor.pc.in vitastor.pc @ONLY)
@@ -101,9 +102,9 @@ 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 cluster_client_icache.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp ../../json11/json11.cpp
)
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES})
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
add_dependencies(build_tests test_cluster_client)
+49 -58
View File
@@ -27,7 +27,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
msgr.ringloop = ringloop;
msgr.repeer_pgs = [this](osd_num_t peer_osd)
{
if (msgr.osd_peer_fds.find(peer_osd) != msgr.osd_peer_fds.end())
if (msgr.osd_peers.find(peer_osd) != msgr.osd_peers.end())
{
// peer_osd just connected
continue_ops();
@@ -47,11 +47,11 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
msgr.exec_op = [this](osd_op_t *op)
{
// Garbage in
fprintf(stderr, "Incoming garbage from peer %d\n", op->peer_fd);
msgr.stop_client(op->peer_fd);
fprintf(stderr, "Can't handle incoming operation from client %lu\n", op->client_id);
msgr.stop_client(op->client_id);
delete op;
};
msgr.parse_config(config);
msgr.parse_config(config, true);
st_cli.tfd = tfd;
st_cli.on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
@@ -71,9 +71,6 @@ 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()
@@ -96,7 +93,6 @@ cluster_client_t::~cluster_client_t()
{
ringloop->unregister_consumer(&consumer);
}
free(scrap_buffer);
delete wb;
wb = NULL;
}
@@ -158,7 +154,7 @@ void cluster_client_t::continue_raw_ops(osd_num_t peer_osd)
{
auto op = it->second;
op->op_type = OSD_OP_OUT;
op->peer_fd = msgr.osd_peer_fds.at(peer_osd);
op->client_id = msgr.osd_peers.at(peer_osd)->client_id;
msgr.outbox_push(op);
raw_ops.erase(it++);
}
@@ -485,7 +481,7 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
}
// vault
vault_parse_config();
msgr.parse_config(config);
msgr.parse_config(config, false);
st_cli.parse_config(config);
st_cli.load_pgs();
}
@@ -594,7 +590,7 @@ void cluster_client_t::on_change_pool_config_hook()
{
if (log_level > 2 && pg_counts[pool_item.first])
{
printf("Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
fprintf(stderr, "Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
pg_counts[pool_item.first], pool_item.second.real_pg_count);
}
// At this point, all pool operations should have been suspended
@@ -882,13 +878,13 @@ void cluster_client_t::execute_cas(cluster_op_t *op)
if (op->retval != expected && op->retval >= 0)
op->retval = -EIO;
op->retval = op->retval == -EPIPE ? -EINTR : op->retval;
auto peer_it = msgr.osd_peer_fds.find(op->parts[0].osd_num);
auto peer_it = msgr.osd_peers.find(op->parts[0].osd_num);
if (op->retval != 0 || (op->flags & OP_IMMEDIATE_COMMIT))
{
auto cb = std::move(op->callback);
cb(op);
}
else if (peer_it == msgr.osd_peer_fds.end())
else if (peer_it == msgr.osd_peers.end())
{
// Care must be taken to make sure that the client doesn't reconnect to the OSD
// before executing the previously completed operation callback (!)
@@ -899,10 +895,10 @@ void cluster_client_t::execute_cas(cluster_op_t *op)
else
{
// CAS writes have a built-in sync
auto peer_fd = peer_it->second;
osd_client_t *cl = peer_it->second;
*part = (osd_op_t){
.op_type = OSD_OP_OUT,
.peer_fd = peer_fd,
.client_id = cl->client_id,
.req = {
.hdr = {
.magic = SECONDARY_OSD_OP_MAGIC,
@@ -1051,11 +1047,11 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
void cluster_client_t::execute_raw(osd_num_t osd_num, osd_op_t *op)
{
auto fd_it = msgr.osd_peer_fds.find(osd_num);
if (fd_it != msgr.osd_peer_fds.end())
auto peer_it = msgr.osd_peers.find(osd_num);
if (peer_it != msgr.osd_peers.end())
{
op->op_type = OSD_OP_OUT;
op->peer_fd = fd_it->second;
op->client_id = peer_it->second->client_id;
msgr.outbox_push(op);
}
else
@@ -1245,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)
@@ -1253,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);
}
@@ -1263,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);
}
@@ -1272,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);
}
}
@@ -1304,8 +1294,8 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
unsigned bitmap_mem = object_bitmap_size +
op->parts.size() * pg_data_size *
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
// read chain info - 1 byte per block
+ (op->enc ? op->len/pool_cfg.bitmap_granularity : 0));
// 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);
@@ -1347,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;
@@ -1361,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++;
@@ -1374,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;
@@ -1454,15 +1444,18 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
primary_osd = nearest_osd;
}
part->osd_num = primary_osd;
auto peer_it = msgr.osd_peer_fds.find(primary_osd);
if (peer_it != msgr.osd_peer_fds.end())
auto peer_it = msgr.osd_peers.find(primary_osd);
if (peer_it != msgr.osd_peers.end())
{
int peer_fd = peer_it->second;
osd_client_t *cl = peer_it->second;
part->flags |= PART_SENT|PART_VALID;
op->inflight_count++;
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
+ (op->opcode == OSD_OP_READ && op->enc ? pool_cfg.data_block_size/pool_cfg.bitmap_granularity : 0));
// 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)
{
@@ -1472,7 +1465,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
}
part->op = (osd_op_t){
.op_type = OSD_OP_OUT,
.peer_fd = peer_fd,
.client_id = cl->client_id,
.req = { .rw = {
.header = {
.magic = SECONDARY_OSD_OP_MAGIC,
@@ -1523,8 +1516,8 @@ int cluster_client_t::continue_sync(cluster_op_t *op)
for (auto do_it = dirty_osds.begin(); do_it != dirty_osds.end(); )
{
osd_num_t sync_osd = *do_it;
auto peer_it = msgr.osd_peer_fds.find(sync_osd);
if (peer_it == msgr.osd_peer_fds.end())
auto peer_it = msgr.osd_peers.find(sync_osd);
if (peer_it == msgr.osd_peers.end())
dirty_osds.erase(do_it++);
else
do_it++;
@@ -1577,12 +1570,12 @@ resume_1:
void cluster_client_t::send_sync(cluster_op_t *op, cluster_op_part_t *part)
{
auto peer_fd = msgr.osd_peer_fds.at(part->osd_num);
osd_client_t *cl = msgr.osd_peers.at(part->osd_num);
part->flags |= PART_SENT;
op->inflight_count++;
part->op = (osd_op_t){
.op_type = OSD_OP_OUT,
.peer_fd = peer_fd,
.client_id = cl->client_id,
.req = {
.hdr = {
.magic = SECONDARY_OSD_OP_MAGIC,
@@ -1607,6 +1600,9 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
}
}
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
{
cluster_op_t *op = part->parent;
@@ -1615,17 +1611,12 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
{
// Operation failed, retry
part->flags |= PART_ERROR;
if (!op->retval || op->retval == -EPIPE ||
part->op.reply.hdr.retval == -ENOSPC && op->retval == -ETIMEDOUT ||
part->op.reply.hdr.retval == -EIO)
{
// Error priority: EIO > ENOSPC > ETIMEDOUT > EPIPE
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
op->retval = part->op.reply.hdr.retval;
}
int stop_fd = -1;
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
uint64_t stop_client_id = 0;
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
{
stop_fd = part->op.peer_fd;
stop_client_id = part->op.client_id;
if (op->retval != -EPIPE || log_level > 0)
{
fprintf(
@@ -1652,9 +1643,9 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
op->retry_after = op->retval != -EPIPE ? client_eio_retry_interval : client_retry_interval;
}
reset_retry_timer(op->retry_after);
if (stop_fd >= 0)
if (stop_client_id)
{
msgr.stop_client(stop_fd);
msgr.stop_client(stop_client_id);
}
op->inflight_count--;
if (op->inflight_count == 0 && !op->retry_after)
@@ -1689,7 +1680,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
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))
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)
+1 -4
View File
@@ -84,7 +84,7 @@ class writeback_cache_t;
struct inode_cache_t
{
std::vector<inode_t> chain;
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;
@@ -153,9 +153,6 @@ public:
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;
-1
View File
@@ -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
+2 -2
View File
@@ -295,7 +295,7 @@ int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
bool conn = true;
for (osd_num_t peer_osd: all_peers)
{
if (msgr.osd_peer_fds.find(peer_osd) == msgr.osd_peer_fds.end())
if (msgr.osd_peers.find(peer_osd) == msgr.osd_peers.end())
{
// Initiate connection
if (st_cli.peer_states[peer_osd].is_null())
@@ -340,7 +340,7 @@ void cluster_client_t::send_list(inode_list_osd_t *cur_list)
osd_op_t *op = new osd_op_t();
op->op_type = OSD_OP_OUT;
// Already checked that it exists above, but anyway
op->peer_fd = msgr.osd_peer_fds.at(cur_list->osd_num);
op->client_id = msgr.osd_peers.at(cur_list->osd_num)->client_id;
op->req = (osd_any_op_t){
.sec_list = {
.header = {
+114 -67
View File
@@ -12,6 +12,7 @@
#include "http_client.h"
#endif
#include "str_util.h"
#include "json_util.h"
etcd_state_client_t::~etcd_state_client_t()
{
@@ -80,6 +81,46 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
return addrs;
}
std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & username)
{
auto user_it = user_info.find(username);
if (user_it != user_info.end())
{
return user_it->second;
}
auto inf = std::make_shared<user_info_t>();
inf->name = username;
return inf;
}
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
{
if (user_info->type == user_type_t::ADMIN)
{
return true;
}
auto cache_it = user_info->perm_cache.find(inode_num);
if (cache_it != user_info->perm_cache.end() &&
cache_it->second.mod_revision == user_perm_cache_revision)
{
return write ? (cache_it->second.perm == user_perm_t::OWNER) : (cache_it->second.perm != user_perm_t::DENY);
}
auto inode_it = inode_config.find(inode_num);
if (inode_it == inode_config.end())
{
return false;
}
// FIXME Implement cache reset after reworking etcd interaction to not keep everything in memory
auto & perm_item = user_info->perm_cache[inode_num];
perm_item.mod_revision = user_perm_cache_revision;
perm_item.perm = (user_info->name == inode_it->second.owner || inode_it->second.owner_group != "" &&
user_info->groups.find(inode_it->second.owner_group) != user_info->groups.end()
? user_perm_t::OWNER : (inode_it->second.reader_group != "" &&
user_info->groups.find(inode_it->second.reader_group) != user_info->groups.end()
? user_perm_t::READER : user_perm_t::DENY));
return write ? (perm_item.perm == user_perm_t::OWNER) : (perm_item.perm != user_perm_t::DENY);
}
http_context_t *etcd_state_client_t::get_http_ctx()
{
if (!http_ctx)
@@ -252,10 +293,15 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
}
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();
this->etcd_client_cert = config["osd_cert"].string_value();
this->etcd_client_key = config["osd_pkey"].string_value();
}
else
{
this->etcd_client_cert = config["cert"].string_value();
this->etcd_client_key = config["pkey"].string_value();
}
if (this->etcd_client_cert == "")
{
this->etcd_client_cert = config["etcd_client_cert"].string_value();
this->etcd_client_key = config["etcd_client_key"].string_value();
@@ -304,7 +350,6 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
{
this->etcd_min_reload_interval = 50;
}
this->use_image_index_for_auth = config["use_image_index_for_auth"].bool_value();
if (this->etcd_ws_keepalive_interval != old_etcd_ws_keepalive_interval && ws_keepalive_timer >= 0)
{
#ifndef __MOCK__
@@ -436,7 +481,6 @@ void etcd_state_client_t::start_etcd_watcher_selected()
etcd_watch_ws = http_init(get_http_ctx());
else
http_close(etcd_watch_ws);
etcd_total_watches = (use_image_index_for_auth ? 4 : 3);
open_websocket(etcd_watch_ws, url.addr, url.hostname, url.path+"/watch", { .timeout = etcd_slow_timeout, .ssl = url.ssl },
[this, cur_addr = url.addr](http_message_t *msg)
{
@@ -456,12 +500,11 @@ void etcd_state_client_t::start_etcd_watcher_selected()
{
if (watch_id == ETCD_CONFIG_WATCH_ID ||
watch_id == ETCD_PG_STATE_WATCH_ID ||
watch_id == ETCD_OSD_STATE_WATCH_ID ||
watch_id == ETCD_INDEX_WATCH_ID)
watch_id == ETCD_OSD_STATE_WATCH_ID)
{
etcd_watches_initialised++;
}
if (etcd_watches_initialised == etcd_total_watches && this->log_level > 0)
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && this->log_level > 0)
{
fprintf(stderr, "Successfully subscribed to etcd at %s, revision %ju/%ju/%ju\n", cur_addr.c_str(),
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
@@ -482,7 +525,7 @@ void etcd_state_client_t::start_etcd_watcher_selected()
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
data["result"]["compact_revision"].uint64_value());
http_close(etcd_watch_ws);
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = etcd_watch_revision_index = 0;
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
on_reload_hook();
}
return;
@@ -503,7 +546,7 @@ void etcd_state_client_t::start_etcd_watcher_selected()
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
// Also don't save revision from the initial created: true messages because they always contain the latest revision
if (etcd_watches_initialised == etcd_total_watches &&
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES &&
!data["result"]["header"]["revision"].is_null() &&
!data["result"]["created"].bool_value())
{
@@ -526,8 +569,6 @@ void etcd_state_client_t::start_etcd_watcher_selected()
etcd_watch_revision_pg = watch_rev;
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
etcd_watch_revision_osd = watch_rev;
else if (watch_id == ETCD_INDEX_WATCH_ID)
etcd_watch_revision_index = watch_rev;
etcd_urls_to_try.clear();
}
// First gather all changes into a hash to remove multiple overwrites
@@ -603,18 +644,6 @@ void etcd_state_client_t::start_etcd_watcher_selected()
{ "progress_notify", true },
} }
}).dump());
if (use_image_index_for_auth)
{
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
{ "create_request", json11::Json::object {
{ "key", base64_encode(etcd_prefix+"/index/image/") },
{ "range_end", base64_encode(etcd_prefix+"/index/image0") },
{ "start_revision", etcd_watch_revision_index },
{ "watch_id", ETCD_INDEX_WATCH_ID },
{ "progress_notify", true },
} }
}).dump());
}
// FIXME: Do not watch /pg/history/ at all in client code (not in OSD)
if (on_start_watcher_hook)
{
@@ -638,7 +667,7 @@ void etcd_state_client_t::start_ws_keepalive()
{
ws_keepalive_timer = tfd->set_timer(etcd_ws_keepalive_interval*1000, true, [this](int)
{
if (!etcd_watch_ws || etcd_watches_initialised < etcd_total_watches)
if (!etcd_watch_ws || etcd_watches_initialised < ETCD_TOTAL_WATCHES)
{
// Do nothing
}
@@ -784,15 +813,6 @@ void etcd_state_client_t::load_pgs()
} }
},
};
if (use_image_index_for_auth)
{
txn.push_back(json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(etcd_prefix+"/index/image/") },
{ "range_end", base64_encode(etcd_prefix+"/index/image0") },
} }
});
}
json11::Json::object req = { { "success", txn } };
json11::Json checks = load_pgs_checks_hook != NULL ? load_pgs_checks_hook() : json11::Json();
if (checks.array_items().size() > 0)
@@ -1043,6 +1063,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")
@@ -1362,6 +1384,10 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
{
on_inode_change_hook(inode_num, true);
}
if (this->inode_config.find(inode_num) != this->inode_config.end())
{
user_perm_cache_revision = kv.mod_revision;
}
this->inode_config.erase(inode_num);
}
else
@@ -1370,39 +1396,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
}
}
}
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/index/image/")
{
// <etcd_prefix>/index/image/%s
// Used for environments based on etcd authentication to grant client image access
// by names - granting by ID is inconvenient because of snapshots
std::string inode_name = key.substr(etcd_prefix.length()+13);
if (!value.is_object())
{
auto n_it = this->inode_by_name.find(inode_name);
if (n_it != this->inode_by_name.end())
{
uint64_t inode_num = n_it->second;
if (on_inode_change_hook != NULL)
{
on_inode_change_hook(inode_num, true);
}
this->inode_config.erase(inode_num);
this->inode_by_name.erase(n_it);
}
}
else if (value["name"] == inode_name)
{
uint64_t inode_num = INODE_WITH_POOL(value["pool_id"].uint64_value(), value["id"].uint64_value());
if (!INODE_POOL(inode_num) || !INODE_NO_POOL(inode_num))
{
fprintf(stderr, "Invalid inode ID in key %s: %ju\n", key.c_str(), inode_num);
}
else
{
insert_inode_config(deserialize_inode_cfg(inode_num, value, kv.mod_revision));
}
}
}
else if (key == etcd_prefix+"/config/node_placement")
{
// <etcd_prefix>/config/node_placement
@@ -1410,6 +1403,40 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
if (on_change_node_placement_hook)
on_change_node_placement_hook();
}
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
{
// <etcd_prefix>/config/user/<username>
auto name = key.substr(etcd_prefix.length()+13);
auto & inf = user_info[name];
if (!value.is_object())
{
if (inf)
{
inf->type = user_type_t::CLIENT;
inf->groups.clear();
inf->perm_cache.clear();
}
user_info.erase(name);
}
else
{
if (!inf)
{
inf = std::make_shared<user_info_t>();
inf->name = name;
}
inf->type = value["type"] == "admin" ? user_type_t::ADMIN :
(value["type"] == "mon" ? user_type_t::MON :
(value["type"] == "osd" ? user_type_t::OSD : user_type_t::CLIENT));
inf->groups.clear();
for (auto & group: value["groups"].array_items())
{
if (group.string_value() != "")
inf->groups.insert(group.string_value());
}
inf->perm_cache.clear();
}
}
}
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
@@ -1432,7 +1459,12 @@ uint32_t etcd_state_client_t::parse_scheme(const std::string & scheme)
void etcd_state_client_t::insert_inode_config(const inode_config_t & cfg)
{
this->inode_config[cfg.num] = cfg;
auto & cfg_ref = this->inode_config[cfg.num];
if (cfg_ref.mod_revision != cfg.mod_revision)
{
user_perm_cache_revision = cfg.mod_revision;
}
cfg_ref = cfg;
if (cfg.name != "")
{
this->inode_by_name[cfg.name] = cfg.num;
@@ -1500,6 +1532,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;
@@ -1546,6 +1590,9 @@ inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, js
.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,
};
+35 -4
View File
@@ -9,11 +9,12 @@
#include "json11/json11.hpp"
#include "object_id.h"
#include "timerfd_manager.h"
#include "../util/robin_hood.h"
#define ETCD_CONFIG_WATCH_ID 1
#define ETCD_OSD_STATE_WATCH_ID 2
#define ETCD_PG_STATE_WATCH_ID 3
#define ETCD_INDEX_WATCH_ID 4
#define ETCD_TOTAL_WATCHES 3
#define DEFAULT_BLOCK_SIZE 128*1024
#define MIN_DATA_BLOCK_SIZE 4*1024
@@ -69,6 +70,7 @@ struct pool_config_t
std::map<pg_num_t, pg_config_t> pg_config;
uint64_t scrub_interval = 0;
std::string used_for_app;
std::string creator_group;
int backfillfull = 0;
int local_reads = 0;
@@ -87,6 +89,8 @@ struct inode_config_t
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
@@ -107,6 +111,32 @@ struct http_url_t
std::string path;
};
enum class user_type_t
{
CLIENT = 0,
ADMIN = 1,
MON = 2,
OSD = 3,
};
struct user_perm_t
{
enum class perm_type_t: uint8_t;
constexpr static perm_type_t DENY = (perm_type_t)0;
constexpr static perm_type_t READER = (perm_type_t)1;
constexpr static perm_type_t OWNER = (perm_type_t)2;
uint64_t mod_revision = 0;
perm_type_t perm = DENY;
};
struct user_info_t
{
std::string name;
user_type_t type;
robin_hood::unordered_flat_set<std::string> groups;
robin_hood::unordered_flat_map<inode_t, user_perm_t> perm_cache;
};
struct http_co_t;
struct http_context_t;
@@ -126,7 +156,6 @@ protected:
std::vector<inode_watch_t*> watches;
std::vector<std::function<void()>> on_resolve_queue;
bool new_pg_config = false;
bool use_image_index_for_auth = false;
int ws_keepalive_timer = -1;
int ws_alive = 0;
bool rand_initialized = false;
@@ -158,11 +187,9 @@ public:
http_context_t *http_ctx = NULL;
http_co_t *etcd_watch_ws = NULL, *keepalive_client = NULL;
int etcd_watches_initialised = 0;
int etcd_total_watches = 0;
uint64_t etcd_watch_revision_config = 0;
uint64_t etcd_watch_revision_osd = 0;
uint64_t etcd_watch_revision_pg = 0;
uint64_t etcd_watch_revision_index = 0;
timespec etcd_last_reload = {};
int load_pgs_timer_id = -1;
std::map<pool_id_t, pool_config_t> pool_config;
@@ -170,6 +197,8 @@ public:
std::set<osd_num_t> seen_peers;
std::map<inode_t, inode_config_t> inode_config;
std::map<std::string, inode_t> inode_by_name;
robin_hood::unordered_flat_map<std::string, std::shared_ptr<user_info_t>> user_info;
uint64_t user_perm_cache_revision = 0;
json11::Json node_placement;
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
@@ -191,6 +220,8 @@ public:
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
std::vector<std::string> get_addresses();
std::shared_ptr<user_info_t> get_user(const std::string & username);
bool check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write);
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);
+14 -66
View File
@@ -19,6 +19,7 @@
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include "openssl_util.h"
#endif
// libc-ares
@@ -48,6 +49,7 @@ struct http_context_t
#ifdef WITH_OPENSSL
SSL_CTX *ssl_ctx = NULL;
std::string ssl_cn;
#endif
~http_context_t()
@@ -162,71 +164,6 @@ void http_ares_cb(void *data, ares_socket_t socket_fd, int readable, int writabl
});
}
#ifdef WITH_OPENSSL
bool openssl_ctx_use_ca(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
{
if (file_or_pem.substr(0, 5) == "-----")
{
BIO *bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size());
if (!bio)
return false;
X509 *x509 = PEM_read_bio_X509(bio, NULL, 0, NULL);
bool ok = !!x509;
if (x509)
{
X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
X509_STORE_add_cert(store, x509);
X509_free(x509);
}
BIO_free(bio);
return ok;
}
return file_or_pem.empty()
? !!SSL_CTX_set_default_verify_paths(ssl_ctx)
: !!SSL_CTX_load_verify_locations(ssl_ctx, file_or_pem.c_str(), NULL);
}
bool openssl_ctx_use_cert(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
{
if (file_or_pem.substr(0, 5) == "-----")
{
BIO *bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size());
if (!bio)
return false;
X509 *x509 = PEM_read_bio_X509(bio, NULL, 0, NULL);
bool ok = !!x509;
if (x509)
{
ok = SSL_CTX_use_certificate(ssl_ctx, x509);
X509_free(x509);
}
BIO_free(bio);
return ok;
}
return !!SSL_CTX_use_certificate_file(ssl_ctx, file_or_pem.c_str(), SSL_FILETYPE_PEM);
}
bool openssl_ctx_use_key(SSL_CTX *ssl_ctx, const std::string & file_or_pem)
{
if (file_or_pem.substr(0, 5) == "-----")
{
BIO *bio = BIO_new_mem_buf(file_or_pem.data(), file_or_pem.size());
if (!bio)
return false;
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
bool ok = !!pkey;
if (pkey)
{
ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey);
EVP_PKEY_free(pkey);
}
BIO_free(bio);
return ok;
}
return !!SSL_CTX_use_PrivateKey_file(ssl_ctx, file_or_pem.c_str(), SSL_FILETYPE_PEM);
}
#endif
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)
{
@@ -243,6 +180,7 @@ http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ss
ctx->ssl_key = ssl_key;
ctx->ssl_ca = ssl_ca;
ctx->ssl_ctx = ssl_ctx;
ctx->ssl_cn = "";
if (!ssl_ctx)
goto init_err;
SSL_CTX_set_verify(ssl_ctx, verify_peer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
@@ -251,7 +189,7 @@ http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ss
if (!openssl_ctx_use_ca(ssl_ctx, ssl_ca))
goto init_err;
if (ssl_cert != "" && ssl_key != "" &&
(!openssl_ctx_use_cert(ssl_ctx, ssl_cert) ||
(!openssl_ctx_use_cert(ssl_ctx, ssl_cert, ctx->ssl_cn) ||
!openssl_ctx_use_key(ssl_ctx, ssl_key)))
goto init_err;
#endif
@@ -262,6 +200,11 @@ init_err:
return NULL;
}
std::string http_context_get_ssl_cn(http_context_t *ctx)
{
return ctx->ssl_cn;
}
struct http_ctx_resolve_t
{
std::function<void(const std::string & error, const std::vector<std::string> & addrs)> cb;
@@ -1112,6 +1055,11 @@ bool http_co_t::handle_read()
}
state = HTTP_CO_REQ_HDR_RECEIVED;
parse_http_headers(response, &parsed, true);
if (ssl)
{
auto x509 = SSL_get0_peer_certificate(ssl_cli);
parsed.headers["_tls_common_name"] = openssl_get_cn(x509);
}
auto conn_it = parsed.headers.find("connection");
keepalive = (conn_it != parsed.headers.end() && conn_it->second == "keep-alive");
auto enc_it = parsed.headers.find("transfer-encoding");
+1
View File
@@ -48,6 +48,7 @@ struct http_co_t;
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);
+199 -86
View File
@@ -10,6 +10,7 @@
#include <stdexcept>
#include "addr_util.h"
#include "str_util.h"
#include "messenger.h"
#ifdef WITH_RDMA
#include "msgr_rdma.h"
@@ -117,6 +118,7 @@ void msgr_iothread_t::run()
void osd_messenger_t::init()
{
init_tls();
#ifdef WITH_RDMACM
if (use_rdmacm)
{
@@ -182,15 +184,19 @@ void osd_messenger_t::init()
}
keepalive_timer_id = tfd->set_timer(1000, true, [this](int)
{
std::vector<uint64_t> clients_to_stop;
std::vector<osd_op_t*> ops_to_send;
auto cl_it = clients.begin();
while (cl_it != clients.end())
{
auto cl = cl_it->second;
cl_it++;
auto peer_fd = cl->peer_fd;
if (!cl->osd_num && !cl->in_osd_num || cl->peer_state != PEER_CONNECTED && cl->peer_state != PEER_RDMA)
if (!cl->osd_num && !cl->in_osd_num ||
cl->peer_state != PEER_CONNECTED &&
cl->peer_state != PEER_RDMA_CONNECTING_OUT &&
cl->peer_state != PEER_RDMA)
{
// Do not run keepalive on regular clients
// Do not run keepalive on regular and unconnected clients
continue;
}
if (cl->ping_time_remaining > 0)
@@ -198,11 +204,21 @@ void osd_messenger_t::init()
cl->ping_time_remaining--;
if (!cl->ping_time_remaining)
{
// Ping timed out, stop the client
fprintf(stderr, "Ping timed out for OSD %ju (client %d), disconnecting peer\n", cl->in_osd_num ? cl->in_osd_num : cl->osd_num, cl->peer_fd);
stop_client(peer_fd, true);
// Restart iterator because it may be invalidated
cl_it = clients.upper_bound(peer_fd);
// Ping timed out
if (cl->peer_state == PEER_RDMA_CONNECTING_OUT && cl->peer_fd >= 0)
{
fprintf(stderr, "Ping timed out for OSD %ju over RDMA (client %ju), switching back to TCP\n",
cl->in_osd_num ? cl->in_osd_num : cl->osd_num, cl->client_id);
delete cl->rdma_conn;
cl->rdma_conn = NULL;
cl->peer_state = PEER_CONNECTED;
}
else
{
fprintf(stderr, "Ping timed out for OSD %ju (client %ju), disconnecting peer\n",
cl->in_osd_num ? cl->in_osd_num : cl->osd_num, cl->client_id);
clients_to_stop.push_back(cl->client_id);
}
}
}
else if (cl->idle_time_remaining > 0)
@@ -213,37 +229,46 @@ void osd_messenger_t::init()
// Connection is idle for <osd_idle_time>, send ping
osd_op_t *op = new osd_op_t();
op->op_type = OSD_OP_OUT;
op->peer_fd = cl->peer_fd;
op->client_id = cl->client_id;
op->req = (osd_any_op_t){
.hdr = {
.magic = SECONDARY_OSD_OP_MAGIC,
.opcode = OSD_OP_PING,
},
};
op->callback = [this, cl](osd_op_t *op)
op->callback = [this](osd_op_t *op)
{
auto cl_it = clients.find(op->peer_fd);
if (cl_it == clients.end() || cl_it->second != cl)
auto cl_it = clients.find(op->client_id);
if (cl_it == clients.end())
{
// client is already dropped
delete op;
return;
}
int fail_fd = (op->reply.hdr.retval != 0 ? op->peer_fd : -1);
auto fail_osd_num = cl->in_osd_num ? cl->in_osd_num : cl->osd_num;
auto cl = cl_it->second;
bool failed = (op->reply.hdr.retval != 0);
cl->ping_time_remaining = 0;
delete op;
if (fail_fd >= 0)
if (failed)
{
fprintf(stderr, "Ping failed for OSD %ju (client %d), disconnecting peer\n", fail_osd_num, fail_fd);
stop_client(fail_fd, true);
auto fail_osd_num = cl->in_osd_num ? cl->in_osd_num : cl->osd_num;
if (cl->peer_state == PEER_RDMA_CONNECTING_OUT && cl->peer_fd >= 0)
{
fprintf(stderr, "Ping failed for OSD %ju over RDMA (client %ju), switching back to TCP\n", fail_osd_num, cl->client_id);
delete cl->rdma_conn;
cl->rdma_conn = NULL;
cl->peer_state = PEER_CONNECTED;
}
else
{
fprintf(stderr, "Ping failed for OSD %ju (client %ju), disconnecting peer\n", fail_osd_num, cl->client_id);
stop_client(cl->client_id);
}
}
};
cl->ping_time_remaining = osd_ping_timeout;
cl->idle_time_remaining = osd_idle_timeout;
outbox_push(op);
// Restart iterator because it may be invalidated
cl_it = clients.upper_bound(peer_fd);
ops_to_send.push_back(op);
}
}
else
@@ -251,6 +276,14 @@ void osd_messenger_t::init()
cl->idle_time_remaining = osd_idle_timeout;
}
}
for (uint64_t client_id: clients_to_stop)
{
stop_client(client_id);
}
for (osd_op_t *op: ops_to_send)
{
outbox_push(op);
}
});
}
@@ -263,7 +296,7 @@ osd_messenger_t::~osd_messenger_t()
}
while (clients.size() > 0)
{
stop_client(clients.begin()->first, true, true);
stop_client(clients.begin()->first, true);
}
if (iothreads.size())
{
@@ -289,18 +322,52 @@ osd_messenger_t::~osd_messenger_t()
rdmacm_evch = NULL;
}
#endif
for (auto encrypt_ctx: encrypt_ctx_pool)
for (auto encrypt_ctx: encrypt_xts_pool)
{
destroy_aes_xts_encrypt(encrypt_ctx);
}
for (auto decrypt_ctx: decrypt_ctx_pool)
for (auto decrypt_ctx: decrypt_xts_pool)
{
destroy_aes_xts_decrypt(decrypt_ctx);
}
destroy_tls();
}
void osd_messenger_t::parse_config(const json11::Json & config)
void osd_messenger_t::parse_config(const json11::Json & config, bool init)
{
this->max_cipher_pool_size = config["max_cipher_pool_size"].uint64_value();
if (!this->max_cipher_pool_size)
this->max_cipher_pool_size = 256;
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;
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
this->receive_buffer_size = 65536;
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
: (int)config["min_zerocopy_send_size"].int64_value();
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
if (!this->peer_connect_interval)
this->peer_connect_interval = 5;
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
if (!this->peer_connect_timeout)
this->peer_connect_timeout = 5;
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
if (!this->osd_idle_timeout)
this->osd_idle_timeout = 5;
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
if (!this->osd_ping_timeout)
this->osd_ping_timeout = 5;
this->log_level = config["log_level"].uint64_value();
// All other parameters are only set on init
if (!init)
return;
#ifdef WITH_RDMA
if (!config["use_rdma"].is_null())
{
@@ -331,34 +398,25 @@ 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 (!osd_num)
{
tls_cert = config["cert"].string_value();
tls_key = config["pkey"].string_value();
osd_tls_ca = config["osd_ca"].string_value();
}
else
{
tls_cert = config["osd_cert"].string_value();
tls_key = config["osd_pkey"].string_value();
osd_tls_ca = config["osd_ca"].string_value();
client_tls_ca = config["client_ca"].string_value();
}
if (!osd_num)
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
else
this->iothread_count = (uint32_t)config["osd_iothread_count"].uint64_value();
this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value();
if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024)
this->receive_buffer_size = 65536;
this->use_sync_send_recv = config["use_sync_send_recv"].bool_value() ||
config["use_sync_send_recv"].uint64_value();
this->min_zerocopy_send_size = config["min_zerocopy_send_size"].is_null()
? DEFAULT_MIN_ZEROCOPY_SEND_SIZE
: (int)config["min_zerocopy_send_size"].int64_value();
this->peer_connect_interval = config["peer_connect_interval"].uint64_value();
if (!this->peer_connect_interval)
this->peer_connect_interval = 5;
this->peer_connect_timeout = config["peer_connect_timeout"].uint64_value();
if (!this->peer_connect_timeout)
this->peer_connect_timeout = 5;
this->osd_idle_timeout = config["osd_idle_timeout"].uint64_value();
if (!this->osd_idle_timeout)
this->osd_idle_timeout = 5;
this->osd_ping_timeout = config["osd_ping_timeout"].uint64_value();
if (!this->osd_ping_timeout)
this->osd_ping_timeout = 5;
this->log_level = config["log_level"].uint64_value();
config["use_sync_send_recv"].uint64_value() || !ringloop;
// OSD public & cluster networks
this->osd_networks.clear();
if (config["osd_network"].is_string())
@@ -451,7 +509,7 @@ void osd_messenger_t::try_connect_peer(uint64_t peer_osd)
{
return;
}
if (osd_peer_fds.find(peer_osd) != osd_peer_fds.end())
if (osd_peers.find(peer_osd) != osd_peers.end())
{
wanted_peers.erase(peer_osd);
return;
@@ -478,20 +536,20 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
#ifdef WITH_RDMACM
if (disable_tcp)
{
on_connect_peer(peer_osd, -EINVAL);
on_connect_peer(peer_osd, -EINVAL, 0);
return;
}
#endif
struct sockaddr_storage addr;
if (!string_to_addr(peer_host, 0, peer_port, &addr))
{
on_connect_peer(peer_osd, -EINVAL);
on_connect_peer(peer_osd, -EINVAL, 0);
return;
}
int peer_fd = socket(addr.ss_family, SOCK_STREAM, 0);
if (peer_fd < 0)
{
on_connect_peer(peer_osd, -errno);
on_connect_peer(peer_osd, -errno, 0);
return;
}
fcntl(peer_fd, F_SETFL, fcntl(peer_fd, F_GETFL, 0) | O_NONBLOCK);
@@ -499,21 +557,25 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
if (r < 0 && errno != EINPROGRESS)
{
close(peer_fd);
on_connect_peer(peer_osd, -errno);
on_connect_peer(peer_osd, -errno, 0);
return;
}
clients[peer_fd] = new osd_client_t();
const uint64_t client_id = next_client_id++;
osd_client_t *cl = new osd_client_t();
if (log_level > 0)
{
fprintf(stderr, "Connecting to OSD %ju at %s:%d (client %d)\n", peer_osd, peer_host, peer_port, peer_fd);
fprintf(stderr, "Connecting to OSD %ju at %s:%d (client %ju, FD %d)\n", peer_osd, peer_host, peer_port, client_id, peer_fd);
}
clients[peer_fd]->peer_addr = addr;
clients[peer_fd]->peer_port = peer_port;
clients[peer_fd]->peer_fd = peer_fd;
clients[peer_fd]->peer_state = PEER_CONNECTING;
clients[peer_fd]->connect_timeout_id = -1;
clients[peer_fd]->osd_num = peer_osd;
clients[peer_fd]->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
cl->client_id = client_id;
cl->peer_addr = addr;
cl->peer_port = peer_port;
cl->peer_fd = peer_fd;
cl->peer_state = PEER_CONNECTING;
cl->connect_timeout_id = -1;
cl->osd_num = peer_osd;
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)
{
// Either OUT (connected) or HUP
@@ -521,11 +583,11 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
});
if (peer_connect_timeout > 0)
{
clients[peer_fd]->connect_timeout_id = tfd->set_timer(1000*peer_connect_timeout, false, [this, peer_fd](int timer_id)
cl->connect_timeout_id = tfd->set_timer(1000*peer_connect_timeout, false, [this, client_id](int timer_id)
{
osd_num_t peer_osd = clients.at(peer_fd)->osd_num;
stop_client(peer_fd, true);
on_connect_peer(peer_osd, -EPIPE);
osd_num_t peer_osd = clients.at(client_id)->osd_num;
stop_client(client_id);
on_connect_peer(peer_osd, -EPIPE, 0);
return;
});
}
@@ -533,7 +595,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
void osd_messenger_t::handle_connect_epoll(int peer_fd)
{
auto cl = clients[peer_fd];
auto cl = clients_by_fd.at(peer_fd);
if (cl->connect_timeout_id >= 0)
{
tfd->clear_timer(cl->connect_timeout_id);
@@ -548,8 +610,8 @@ void osd_messenger_t::handle_connect_epoll(int peer_fd)
}
if (result != 0)
{
stop_client(peer_fd, true);
on_connect_peer(peer_osd, -result);
stop_client(cl->client_id);
on_connect_peer(peer_osd, -result, 0);
return;
}
int one = 1;
@@ -560,29 +622,41 @@ void osd_messenger_t::handle_connect_epoll(int peer_fd)
handle_peer_epoll(peer_fd, epoll_events);
});
// Check OSD number
init_tls_client(cl);
check_peer_config(cl);
}
void osd_messenger_t::handle_peer_epoll(int peer_fd, int epoll_events)
{
// Mark client as ready (i.e. some data is available)
auto cl = clients_by_fd.at(peer_fd);
if (epoll_events & EPOLLRDHUP)
{
// Stop client
if (cl->rdma_close_tcp && (cl->peer_state == PEER_RDMA ||
cl->peer_state == PEER_RDMA_CONNECTING_IN ||
cl->peer_state == PEER_RDMA_CONNECTING_OUT))
{
// It's allowed to stop the TCP socket during RDMA transition
tfd->set_fd_handler(cl->peer_fd, false, NULL);
clients_by_fd.erase(cl->peer_fd);
close(cl->peer_fd);
cl->peer_fd = -1;
return;
}
if (log_level > 0)
{
fprintf(stderr, "[OSD %ju] client %d disconnected\n", this->osd_num, peer_fd);
fprintf(stderr, "[OSD %ju] client %ju disconnected\n", this->osd_num, cl->client_id);
}
stop_client(peer_fd, true);
stop_client(cl->client_id);
}
else if (epoll_events & EPOLLIN)
{
// Mark client as ready (i.e. some data is available)
auto cl = clients[peer_fd];
cl->read_ready++;
if (cl->read_ready == 1)
{
read_ready_clients.push_back(cl->peer_fd);
read_ready_clients.push_back(cl->client_id);
if (ringloop)
ringloop->wakeup();
else
@@ -591,13 +665,13 @@ void osd_messenger_t::handle_peer_epoll(int peer_fd, int epoll_events)
}
}
void osd_messenger_t::on_connect_peer(osd_num_t peer_osd, int peer_fd)
void osd_messenger_t::on_connect_peer(osd_num_t peer_osd, int errcode, uint64_t client_id)
{
auto & wp = wanted_peers.at(peer_osd);
wp.connecting = false;
if (peer_fd < 0)
if (errcode < 0)
{
fprintf(stderr, "Failed to connect to peer OSD %ju address %s port %d: %s\n", peer_osd, wp.cur_addr.c_str(), wp.cur_port, strerror(-peer_fd));
fprintf(stderr, "Failed to connect to peer OSD %ju address %s port %d: %s\n", peer_osd, wp.cur_addr.c_str(), wp.cur_port, strerror(-errcode));
if (wp.address_changed)
{
wp.address_changed = false;
@@ -624,7 +698,7 @@ void osd_messenger_t::on_connect_peer(osd_num_t peer_osd, int peer_fd)
}
if (log_level > 0)
{
fprintf(stderr, "[OSD %ju] Connected with peer OSD %ju (client %d)\n", osd_num, peer_osd, peer_fd);
fprintf(stderr, "[OSD %ju] Connected with peer OSD %ju (client %ju)\n", osd_num, peer_osd, client_id);
}
wanted_peers.erase(peer_osd);
repeer_pgs(peer_osd);
@@ -634,7 +708,7 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
{
osd_op_t *op = new osd_op_t();
op->op_type = OSD_OP_OUT;
op->peer_fd = cl->peer_fd;
op->client_id = cl->client_id;
op->req = (osd_any_op_t){
.show_conf = {
.header = {
@@ -649,7 +723,15 @@ 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 },
{ "rdma_close_tcp", true },
};
if (use_proto_checksums)
{
features["proto_checksums"] = use_proto_checksums;
}
payload["features"] = features;
#ifdef WITH_RDMA
if (!use_rdmacm && rdma_contexts.size())
{
@@ -658,7 +740,7 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
if (!selected_ctx)
{
if (log_level > 0)
fprintf(stderr, "No RDMA context for OSD %ju connection (peer %d), using only TCP\n", cl->osd_num, cl->peer_fd);
fprintf(stderr, "No RDMA context for OSD %ju connection (client %ju), using only TCP\n", cl->osd_num, cl->client_id);
}
else
{
@@ -689,6 +771,11 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
err = true;
fprintf(stderr, "Failed to get config from OSD %ju (retval=%jd), disconnecting peer\n", cl->osd_num, op->reply.hdr.retval);
}
else if (cl->gcm_enabled && !cl->hs_result.peer_is_osd)
{
err = true;
fprintf(stderr, "Client %ju is not authenticated as an OSD, disconnecting peer\n", cl->client_id);
}
else
{
config = json11::Json::parse(std::string((char*)op->buf), json_err);
@@ -719,12 +806,24 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
if (err)
{
osd_num_t peer_osd = cl->osd_num;
stop_client(op->peer_fd);
on_connect_peer(peer_osd, -EINVAL);
stop_client(op->client_id);
on_connect_peer(peer_osd, -EINVAL, 0);
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 (config["features"]["rdma_close_tcp"].bool_value())
{
cl->rdma_close_tcp = true;
}
if (!use_rdmacm && cl->rdma_conn && config["rdma_address"].is_string())
{
msgr_rdma_address_t addr;
@@ -749,14 +848,19 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
{
fprintf(stderr, "Connected to OSD %ju using RDMA\n", cl->osd_num);
}
cl->peer_state = PEER_RDMA;
cl->peer_state = (cl->rdma_close_tcp ? PEER_RDMA_CONNECTING_OUT : PEER_RDMA);
// Add the initial receive request
init_recv_rdma(cl);
// Check the connection by using a ping
cl->ping_time_remaining = osd_ping_timeout;
cl->idle_time_remaining = 0;
delete op;
return;
}
}
#endif
osd_peer_fds[cl->osd_num] = cl->peer_fd;
on_connect_peer(cl->osd_num, cl->peer_fd);
osd_peers[cl->osd_num] = cl;
on_connect_peer(cl->osd_num, 0, cl->client_id);
delete op;
};
outbox_push(op);
@@ -771,13 +875,16 @@ void osd_messenger_t::accept_connections(int listen_fd)
while ((peer_fd = accept(listen_fd, (sockaddr*)&addr, &peer_addr_size)) >= 0)
{
assert(peer_fd != 0);
fprintf(stderr, "[OSD %ju] new client %d: connection from %s\n", this->osd_num, peer_fd,
const uint64_t client_id = next_client_id++;
fprintf(stderr, "[OSD %ju] new client %ju (FD %d): connection from %s\n", this->osd_num, client_id, peer_fd,
addr_to_string(addr).c_str());
fcntl(peer_fd, F_SETFL, fcntl(peer_fd, F_GETFL, 0) | O_NONBLOCK);
int one = 1;
setsockopt(peer_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
auto cl = new osd_client_t();
clients[peer_fd] = cl;
cl->client_id = client_id;
clients[cl->client_id] = cl;
clients_by_fd[peer_fd] = cl;
cl->is_incoming = true;
cl->peer_addr = addr;
cl->peer_addr = addr;
@@ -785,6 +892,7 @@ void osd_messenger_t::accept_connections(int listen_fd)
cl->peer_fd = peer_fd;
cl->peer_state = PEER_CONNECTED;
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
init_tls_client(cl);
// Add FD to epoll
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
{
@@ -827,6 +935,11 @@ bool osd_messenger_t::is_use_rdmacm()
}
#endif
bool osd_messenger_t::is_encryption_enabled()
{
return tls_cert != "" || tls_key != "" || osd_tls_ca != "";
}
json11::Json::object osd_messenger_t::read_config(const json11::Json & config)
{
json11::Json::object file_config;
+126 -38
View File
@@ -12,11 +12,20 @@
#include <deque>
#include <vector>
#include <openssl/types.h>
#ifdef WITH_ISAL_CRYPTO
#include <isa-l_crypto/aes_gcm.h>
#endif
#include "../util/xxh_x86dispatch.h"
#include "../util/robin_hood.h"
#include "malloc_or_die.h"
#include "json11/json11.hpp"
#include "msgr_op.h"
#include "timerfd_manager.h"
#include "addr_util.h"
#include "msgr_handshake.h"
#include <ringloop.h>
#define CL_READ_HDR 1
@@ -26,14 +35,24 @@
#define PEER_CONNECTING 1
#define PEER_CONNECTED 2
#define PEER_RDMA_CONNECTING 3
#define PEER_RDMA 4
#define PEER_STOPPED 5
#define PEER_RDMA_CONNECTING_IN 3
#define PEER_RDMA_CONNECTING_OUT 4
#define PEER_RDMA 5
#define PEER_STOPPED 6
#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 AES_256_GCM_KEY_SIZE 32
#define AES_256_GCM_IV_SIZE 12
#define MAX_SIMPLE_PAYLOAD_SIZE 1048576
struct msgr_sendp_t
{
osd_op_t *op;
@@ -50,8 +69,11 @@ struct op_aes_xts_decrypt_t;
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
struct user_info_t;
struct osd_client_t
{
uint64_t client_id = 0;
int refs = 0;
sockaddr_storage peer_addr = {};
@@ -71,7 +93,30 @@ struct osd_client_t
msgr_rdma_connection_t *rdma_conn = NULL;
#endif
uint8_t *ssl_out_buf = NULL;
size_t ssl_out_buf_size = 0, ssl_out_buf_cap = 0;
bool gcm_enabled = false;
msgr_handshake_i *hs = NULL;
msgr_handshake_result_t hs_result;
std::shared_ptr<user_info_t> user_info;
std::vector<uint8_t> my_key, peer_key;
uint64_t my_iv_ctr = 0, peer_iv_ctr = 0;
#ifdef WITH_ISAL_CRYPTO
isal_gcm_key_data my_key_isal, peer_key_isal;
isal_gcm_context_data *enc_ctx = NULL;
isal_gcm_context_data *dec_ctx = NULL;
#else
EVP_CIPHER_CTX *enc_ctx = NULL;
EVP_CIPHER_CTX *dec_ctx = NULL;
#endif
uint8_t enc_tag[16];
size_t enc_tag_size = 0;
uint8_t dec_tag[16];
size_t dec_tag_size = 0;
// Read state
bool io_error = false;
int read_ready = 0;
osd_op_t *read_op = NULL;
size_t read_op_size = 0;
@@ -79,19 +124,22 @@ struct osd_client_t
iovec read_iov = { 0 };
msghdr read_msg = { 0 };
std::vector<iovec> recv_list;
size_t recv_list_size = 0;
std::vector<int> recv_flags;
uint64_t read_op_id = 1;
bool check_sequencing = false;
bool rdma_close_tcp = false;
bool enable_pg_locks = false;
op_aes_xts_decrypt_t *decrypt_ctx = NULL;
op_aes_xts_decrypt_t *xts_dec_ctx = NULL;
size_t read_op_inline_decrypt_pos = 0;
size_t read_op_inline_decrypt_in = 0;
int proto_csum_status = 0;
XXH3_state_t* read_csum_state = NULL;
// Incoming operations
std::vector<osd_op_t*> received_ops;
// Outbound operations
std::map<uint64_t, osd_op_t*> sent_ops;
robin_hood::unordered_flat_map<uint64_t, osd_op_t*> sent_ops;
uint64_t send_op_id = 0;
// PGs dirtied by this client's primary-writes
@@ -107,7 +155,8 @@ struct osd_client_t
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;
op_aes_xts_encrypt_t *xts_enc_ctx = NULL;
XXH3_state_t* write_csum_state = NULL;
~osd_client_t();
void cancel_ops();
@@ -185,9 +234,19 @@ struct osd_messenger_t;
struct rdmacm_connecting_t;
#endif
class msgr_op_reader_t;
class msgr_op_writer_t;
struct __attribute__((visibility("default"))) osd_messenger_t
{
protected:
friend class copy_op_reader_t;
friend class gcm_op_reader_t;
friend class get_op_reader_t;
friend class copy_op_writer_t;
friend class gcm_op_writer_t;
friend class get_op_writer_t;
int keepalive_timer_id = -1;
uint32_t receive_buffer_size = 0;
@@ -199,7 +258,12 @@ 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;
int max_cipher_pool_size = 256;
std::string tls_cert;
std::string tls_key;
std::string osd_tls_ca;
std::string client_tls_ca;
#ifdef WITH_RDMA
bool use_rdma = true;
@@ -213,46 +277,71 @@ protected:
uint64_t rdma_max_sge = 0, rdma_max_send = 0, rdma_max_recv = 0;
uint64_t rdma_max_msg = 0;
rdma_event_channel *rdmacm_evch = NULL;
std::map<rdma_cm_id*, osd_client_t*> rdmacm_connections;
std::map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
robin_hood::unordered_flat_map<rdma_cm_id*, osd_client_t*> rdmacm_connections;
robin_hood::unordered_flat_map<rdma_cm_id*, rdmacm_connecting_t*> rdmacm_connecting;
#endif
bool gcm_enabled = false;
msgr_handshake_ctx_i *hs_ctx = NULL;
void init_tls();
void destroy_tls();
void init_tls_client(osd_client_t *cl);
bool do_tls_handshake(osd_client_t *cl, bool from_recv = false);
bool derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer);
std::vector<msgr_iothread_t*> iothreads;
std::vector<int> read_ready_clients;
std::vector<int> write_ready_clients;
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;
std::vector<op_aes_xts_encrypt_t*> encrypt_xts_pool;
std::vector<op_aes_xts_decrypt_t*> decrypt_xts_pool;
#ifdef WITH_ISAL_CRYPTO
std::vector<isal_gcm_context_data*> encrypt_gcm_pool;
std::vector<isal_gcm_context_data*> decrypt_gcm_pool;
#else
std::vector<EVP_CIPHER_CTX*> encrypt_gcm_pool;
std::vector<EVP_CIPHER_CTX*> decrypt_gcm_pool;
#endif
public:
timerfd_manager_t *tfd = NULL;
ring_loop_t *ringloop = NULL;
bool has_sendmsg_zc = false;
// osd_num_t is only for logging and asserts
osd_num_t osd_num;
std::map<int, osd_client_t*> clients;
std::map<osd_num_t, osd_wanted_peer_t> wanted_peers;
std::map<uint64_t, int> osd_peer_fds;
uint64_t next_client_id = 1;
// osd_num = 0 for client messenger, osd_num > 0 for OSD messenger
osd_num_t osd_num = 0;
uint32_t clean_entry_bitmap_size = 0;
uint32_t bs_block_size = 0;
uint32_t max_write_request_size = 0;
robin_hood::unordered_flat_map<uint64_t, osd_client_t*> clients;
robin_hood::unordered_flat_map<uint64_t, osd_client_t*> osd_peers;
robin_hood::unordered_flat_map<int, osd_client_t*> clients_by_fd;
robin_hood::unordered_flat_map<osd_num_t, osd_wanted_peer_t> wanted_peers;
std::vector<std::string> osd_networks;
std::vector<addr_mask_t> osd_network_masks;
std::vector<std::string> osd_cluster_networks;
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 parse_config(const json11::Json & config);
void parse_config(const json11::Json & config, bool init);
void connect_peer(uint64_t osd_num, json11::Json peer_state);
void stop_client(int peer_fd, bool force = false, bool force_delete = false);
void stop_client(uint64_t client_id, bool force_delete = false);
void destroy_client(osd_client_t *cl);
void outbox_push(osd_op_t *cur_op);
std::function<void(osd_op_t*)> exec_op;
std::function<void(osd_num_t)> repeer_pgs;
std::function<void(osd_num_t)> break_pg_locks;
std::function<bool(osd_client_t*, json11::Json)> check_config_hook;
std::function<void(osd_client_t*)> handshake_hook;
void read_requests();
void send_replies();
void accept_connections(int listen_fd);
@@ -266,13 +355,14 @@ public:
#ifdef WITH_RDMA
bool is_rdma_enabled();
bool connect_rdma(int peer_fd, std::string rdma_address, uint64_t client_max_msg);
bool connect_rdma(uint64_t client_id, std::string rdma_address, uint64_t client_max_msg);
#endif
#ifdef WITH_RDMACM
bool is_use_rdmacm();
rdma_cm_id *rdmacm_listen(const std::string & bind_address, int rdmacm_port, int *bound_port, int log_level);
void rdmacm_destroy_listener(rdma_cm_id *listener);
#endif
bool is_encryption_enabled();
void inc_op_stats(osd_op_stats_t & stats, uint64_t opcode, timespec & tv_begin, timespec & tv_end, uint64_t len);
void measure_exec(osd_op_t *cur_op);
@@ -282,41 +372,39 @@ protected:
void try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_host, int peer_port);
void handle_peer_epoll(int peer_fd, int epoll_events);
void handle_connect_epoll(int peer_fd);
void on_connect_peer(osd_num_t peer_osd, int peer_fd);
void on_connect_peer(osd_num_t peer_osd, int errcode, uint64_t client_id);
void check_peer_config(osd_client_t *cl);
void cancel_osd_ops(osd_client_t *cl);
void cancel_op(osd_op_t *op);
bool try_send(osd_client_t *cl);
void handle_send(int result, bool prev, bool more, osd_client_t *cl);
size_t op_copy_to(osd_client_t *cl, uint8_t *dst, size_t dst_len);
void op_get_write_buffers(osd_client_t *cl, std::vector<iovec> & lst);
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);
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);
size_t op_copy_from(osd_client_t *cl, uint8_t *src, size_t src_len, size_t & done);
size_t op_get_read_buffers(osd_client_t *cl, std::vector<iovec> & lst);
void handle_finished_op(osd_client_t *cl);
bool op_read_from(osd_client_t *cl, msgr_op_reader_t & rdr);
bool handle_finished_op(osd_client_t *cl);
void handle_immediate_ops();
bool op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done);
bool op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done);
void op_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);
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_immediate_ops();
void clear_immediate_ops(int peer_fd);
#ifdef WITH_RDMA
void try_send_rdma(osd_client_t *cl);
int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len);
bool init_recv_rdma(osd_client_t *cl);
void handle_rdma_events(msgr_rdma_context_t *rdma_context);
msgr_rdma_context_t* choose_rdma_context(osd_client_t *cl);
+238 -111
View File
@@ -3,13 +3,23 @@
#include <assert.h>
#ifdef WITH_ISAL_CRYPTO
#include <isa-l_crypto/isal_crypto_api.h>
#endif
#include "str_util.h"
#include "etcd_state_client.h"
#include "messenger.h"
#include "msgr_encrypt.h"
#include "http_client.h"
#include "openssl_util.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
@@ -21,16 +31,13 @@ op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
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
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX_free(ctx);
#endif
if (tmp)
@@ -52,7 +59,7 @@ void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t blo
tmp = NULL;
tmp_size = 0;
}
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL) != 1)
{
ERR_print_errors_fp(stderr);
@@ -63,9 +70,12 @@ void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t blo
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;
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_xts_enc_256(key+32, key, iv, block_size, in, out);
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
#else
if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) != 1)
{
ERR_print_errors_fp(stderr);
@@ -81,7 +91,6 @@ void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
#endif
}
// FIXME: Copy-paste
void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
{
// Fucking AES-XTS implementations (all of them) don't have streaming support,
@@ -99,7 +108,10 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
done_out += max_out;
tmp_pos += max_out;
if (tmp_pos >= block_size)
{
encrypted = false;
done_in += 1;
}
}
else if (max_in < block_size - offset%block_size)
{
@@ -127,7 +139,7 @@ void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
encrypted = true;
memcpy(out, tmp, max_out);
tmp_pos = max_out;
done_in += max_in;
done_in += max_in-1;
offset += max_in;
done_out += max_out;
}
@@ -159,7 +171,7 @@ void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
@@ -171,29 +183,27 @@ op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
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
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX_free(ctx);
#endif
if (tmp)
free(tmp);
}
void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, uint8_t *key_indexes, uint64_t start_offset, size_t block_size)
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_chain = key_chain;
this->chain_size = chain_size;
this->key_indexes = key_indexes;
this->key_index_bytes = osd_op_rw_t::chain_info_bytes(chain_size);
assert(chain_size <= 1 || key_indexes != NULL);
this->block_size = block_size;
this->offset = 0;
@@ -204,7 +214,7 @@ void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, uint8_t
tmp = NULL;
tmp_size = 0;
}
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
if (chain_size == 1 && key_chain[0] && EVP_DecryptInit_ex(ctx, NULL, NULL, key_chain[0], NULL) != 1)
{
ERR_print_errors_fp(stderr);
@@ -216,21 +226,35 @@ void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, uint8_t
void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
{
uint8_t *key = NULL;
if (chain_size)
if (chain_size > 1)
{
assert(key_indexes[offset/block_size] < chain_size);
key = key_chain[key_indexes[offset/block_size]];
if (!key)
{
if (in != out)
memcpy(out, in, block_size);
return;
}
uint32_t key_index = key_index_bytes == 1
? ((uint8_t*)key_indexes)[offset/block_size]
: (key_index_bytes == 2
? ((uint16_t*)key_indexes)[offset/block_size]
: (key_index_bytes == 4
? ((uint32_t*)key_indexes)[offset/block_size]
: UINT32_MAX));
assert(key_index < chain_size);
key = key_chain[key_index];
}
else
{
key = key_chain[0];
}
if (!key)
{
if (in != out)
memcpy(out, in, block_size);
return;
}
#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)
#ifdef WITH_ISAL_CRYPTO
int r = isal_aes_xts_dec_256(key+32, key, iv, block_size, in, out);
assert(r == 0 || r == ISAL_CRYPTO_ERR_XTS_SAME_KEYS);
#else
if (EVP_DecryptInit_ex(ctx, NULL, NULL, chain_size == 1 ? NULL : key, iv) != 1)
{
ERR_print_errors_fp(stderr);
abort();
@@ -245,6 +269,8 @@ void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
#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,
@@ -258,11 +284,15 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
assert(tmp);
if (max_out > block_size - tmp_pos)
max_out = block_size - tmp_pos;
memcpy(out, tmp + tmp_pos, max_out);
if (out)
memcpy(out, tmp + tmp_pos, max_out);
done_out += max_out;
tmp_pos += max_out;
if (tmp_pos >= block_size)
{
decrypted = false;
done_in += 1;
}
}
else if (max_in < block_size - offset%block_size)
{
@@ -276,7 +306,7 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
done_in += max_in;
offset += max_in;
}
else if (max_out < block_size)
else if (max_out < block_size || !out)
{
// Accumulate and decrypt input in <tmp>, then copy part of it to <out>
if (!tmp)
@@ -288,16 +318,18 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
memcpy(tmp + offset%block_size, in, max_in);
decrypt_block(tmp, tmp);
decrypted = true;
memcpy(out, tmp, max_out);
if (out)
memcpy(out, tmp, max_out);
tmp_pos = max_out;
done_in += max_in;
done_in += max_in-1;
offset += max_in;
done_out += max_out;
}
else if (!(offset%block_size))
{
// Full block - simplest case
decrypt_block(in, out);
if (out)
decrypt_block(in, out);
done_in += block_size;
offset += block_size;
done_out += block_size;
@@ -308,6 +340,7 @@ void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size
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;
@@ -320,96 +353,65 @@ void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
delete decrypt_ctx;
}
bool osd_messenger_t::op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done)
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)
{
auto op = cl->write_op;
auto & op_pos = cl->write_op_pos;
assert(op->req.hdr.opcode == OSD_OP_WRITE);
if (!from)
if (!cl->xts_enc_ctx)
{
if (!cl->encrypt_ctx)
if (encrypt_xts_pool.size())
{
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();
cl->xts_enc_ctx = encrypt_xts_pool.back();
encrypt_xts_pool.pop_back();
}
assert(op->enc->key_chain[0]);
cl->encrypt_ctx->start(op->enc->key_chain[0], op->req.rw.offset, op->enc->bitmap_granularity);
}
for (int i = 0; i < op->iov.count; i++)
{
uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base;
size_t plain_len = op->iov.buf[i].iov_len;
while (from < plain_len || cl->encrypt_ctx->has_buffered())
{
if (done >= enc_len)
return false;
size_t done_in = 0;
size_t done_out = 0;
cl->encrypt_ctx->update(plain+from, plain_len-from, enc_buf+done, enc_len-done, done_in, done_out);
done += done_out;
op_pos += done_in;
from += done_in;
}
from -= plain_len;
}
if (cl->encrypt_ctx)
{
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->encrypt_ctx;
else
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
cl->encrypt_ctx = NULL;
cl->xts_enc_ctx = new op_aes_xts_encrypt_t();
assert(cl->write_op->enc->key_chain[0]);
cl->xts_enc_ctx->start(cl->write_op->enc->key_chain[0], cl->write_op->req.rw.offset, cl->write_op->enc->bitmap_granularity);
}
while (done_plain < plain_len && done_enc < enc_len)
{
size_t done_in = 0;
size_t done_out = 0;
cl->xts_enc_ctx->update(plain+done_plain, plain_len-done_plain, enc_buf+done_enc, enc_len-done_enc, done_in, done_out);
if (cl->write_csum_state && done_out > 0)
XXH3_64bits_update(cl->write_csum_state, enc_buf+done_enc, done_out);
done_enc += done_out;
cl->write_op_pos += done_in;
done_plain += done_in;
}
return true;
}
bool osd_messenger_t::op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done)
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);
auto op = cl->read_op;
assert(op->req.hdr.opcode == OSD_OP_READ);
for (int i = 0; i < op->iov.count; i++)
while (done_plain < plain_len && done_enc < enc_len)
{
uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base;
size_t plain_len = op->iov.buf[i].iov_len;
while (from < plain_len)
{
if (done >= enc_len)
return false;
size_t done_in = 0;
size_t done_out = 0;
cl->decrypt_ctx->update(enc_buf+done, enc_len-done, plain+from, plain_len-from, done_in, done_out);
done += done_in;
cl->read_op_pos += done_out;
cl->read_op_inline_decrypt_in += done_in;
from += done_out;
if (!done_out)
return false;
}
from -= plain_len;
size_t done_in = 0;
size_t done_out = 0;
// plain == NULL means skip output
cl->xts_dec_ctx->update(enc_buf+done_enc, enc_len-done_enc, plain ? plain+done_plain : NULL, plain_len-done_plain, done_in, done_out);
if (cl->read_csum_state && done_in > 0)
XXH3_64bits_update(cl->read_csum_state, enc_buf+done_enc, done_in);
done_enc += done_in;
cl->read_op_pos += done_out;
cl->read_op_inline_decrypt_in += done_in;
done_plain += done_out;
}
op_decrypt_free(cl);
return true;
}
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
{
if (!cl->decrypt_ctx)
if (!cl->xts_dec_ctx)
{
if (decrypt_ctx_pool.size())
if (decrypt_xts_pool.size())
{
cl->decrypt_ctx = decrypt_ctx_pool.back();
decrypt_ctx_pool.pop_back();
cl->xts_dec_ctx = decrypt_xts_pool.back();
decrypt_xts_pool.pop_back();
}
else
cl->decrypt_ctx = new op_aes_xts_decrypt_t();
cl->xts_dec_ctx = new op_aes_xts_decrypt_t();
auto & enc = cl->read_op->enc;
cl->decrypt_ctx->start(enc->key_chain, enc->chain_size,
assert(cl->read_op->req.hdr.opcode == OSD_OP_READ);
cl->xts_dec_ctx->start(enc->key_chain, enc->chain_size,
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
cl->read_op->req.rw.offset, enc->bitmap_granularity);
}
@@ -441,7 +443,7 @@ void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
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);
cl->xts_dec_ctx->update(in, in_len, out, out_len, done_in, done_out);
if (done_in >= in_len)
{
i++;
@@ -458,17 +460,142 @@ void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
from_out += done_out;
}
assert(j >= op->iov.count);
op_decrypt_free(cl);
}
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
{
if (cl->decrypt_ctx)
if (cl->xts_dec_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->decrypt_ctx;
if (decrypt_xts_pool.size() > max_cipher_pool_size)
delete cl->xts_dec_ctx;
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
cl->xts_dec_ctx = NULL;
}
}
void osd_messenger_t::op_encrypt_free(osd_client_t* cl)
{
if (cl->xts_enc_ctx)
{
if (encrypt_xts_pool.size() > max_cipher_pool_size)
delete cl->xts_enc_ctx;
else
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
cl->xts_enc_ctx = NULL;
}
}
bool osd_messenger_t::derive_aes_keys(osd_client_t *cl, bool update_my, bool update_peer)
{
if (!cl->hs_result.shared_secret.size())
{
cl->hs_result = cl->hs->get_result();
if (handshake_hook)
{
handshake_hook(cl);
}
}
std::vector<uint8_t> old_my = cl->my_key, old_peer = cl->peer_key;
// Both keys include AES key and iv + xxhash3 secret
const auto len = AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE + XXH_SECRET_DEFAULT_SIZE;
cl->my_key.resize(len);
cl->peer_key.resize(len);
bool ok = true;
if (update_my || !old_my.size())
{
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
old_my.size() ? old_my.data() : NULL, old_my.size(),
cl->is_incoming ? "server key" : "client key", cl->my_key.data(), len);
#ifdef WITH_ISAL_CRYPTO
if (ok)
isal_aes_gcm_pre_256(cl->my_key.data(), &cl->my_key_isal);
#endif
cl->my_iv_ctr = 0;
}
if (update_peer || !old_peer.size())
{
ok = ok && hs_ctx->derive_kdf(cl->hs_result.shared_secret.data(), cl->hs_result.shared_secret.size(),
old_peer.size() ? old_peer.data() : NULL, old_peer.size(),
!cl->is_incoming ? "server key" : "client key", cl->peer_key.data(), len);
#ifdef WITH_ISAL_CRYPTO
if (ok)
isal_aes_gcm_pre_256(cl->peer_key.data(), &cl->peer_key_isal);
#endif
cl->peer_iv_ctr = 0;
}
return ok;
}
void osd_messenger_t::init_tls()
{
if (!tls_cert.empty() || !tls_key.empty() || !osd_tls_ca.empty() || !client_tls_ca.empty())
{
if (tls_cert.empty() || tls_key.empty() || osd_tls_ca.empty() || osd_num && client_tls_ca.empty())
{
if (osd_num)
fprintf(stderr, "Vitastor transport encryption requires osd_cert, osd_pkey, osd_ca, client_ca options for OSDs\n");
else
fprintf(stderr, "Vitastor transport encryption requires cert, pkey and osd_ca options\n");
exit(1);
}
else
{
#ifndef __MOCK__
gcm_enabled = true;
hs_ctx = msgr_handshake_ctx_i::create_ctx();
if (!hs_ctx->init(tls_cert, tls_key, osd_tls_ca, client_tls_ca))
{
fprintf(stderr, "Error: %s\n", hs_ctx->get_error().c_str());
exit(1);
}
#endif
}
}
}
void osd_messenger_t::init_tls_client(osd_client_t *cl)
{
if (gcm_enabled)
{
cl->gcm_enabled = true;
cl->hs = hs_ctx->create();
cl->hs->init(cl->is_incoming);
if (cl->hs->get_out().size())
{
if (cl->write_state == 0)
{
cl->write_state = CL_WRITE_READY;
write_ready_clients.push_back(cl->client_id);
}
}
}
}
void osd_messenger_t::destroy_tls()
{
#ifdef WITH_ISAL_CRYPTO
for (isal_gcm_context_data *ctx: encrypt_gcm_pool)
{
free(ctx);
}
for (isal_gcm_context_data *ctx: decrypt_gcm_pool)
{
free(ctx);
}
#else
for (EVP_CIPHER_CTX *ctx: encrypt_gcm_pool)
{
EVP_CIPHER_CTX_free(ctx);
}
for (EVP_CIPHER_CTX *ctx: decrypt_gcm_pool)
{
EVP_CIPHER_CTX_free(ctx);
}
#endif
if (hs_ctx)
{
delete hs_ctx;
hs_ctx = NULL;
}
}
+10 -9
View File
@@ -3,16 +3,18 @@
#include <stdint.h>
// WITH_OPENSSL is left to possibly support other crypto libraries
#ifdef WITH_OPENSSL
#ifdef WITH_ISAL_CRYPTO
#include <isa-l_crypto/aes_xts.h>
#endif
#include "../util/xxh_x86dispatch.h"
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#endif
class op_aes_xts_encrypt_t
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX *ctx = NULL;
#endif
uint64_t start_offset = 0;
@@ -30,7 +32,6 @@ 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);
};
@@ -39,13 +40,14 @@ void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
class op_aes_xts_decrypt_t
{
#ifdef WITH_OPENSSL
#ifndef WITH_ISAL_CRYPTO
EVP_CIPHER_CTX *ctx = NULL;
#endif
uint64_t start_offset = 0;
uint8_t **key_chain = NULL;
size_t chain_size = 0;
uint8_t *key_indexes = NULL;
void *key_indexes = NULL;
int key_index_bytes = 0;
size_t offset = 0;
size_t block_size = 0;
uint8_t *tmp = NULL;
@@ -59,8 +61,7 @@ public:
op_aes_xts_decrypt_t();
~op_aes_xts_decrypt_t();
inline bool has_buffered() { return decrypted; };
void start(uint8_t **key_chain, size_t chain_size, uint8_t *key_indexes, uint64_t start_offset, size_t block_size);
void 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);
};
+765
View File
@@ -0,0 +1,765 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdint.h>
#include <assert.h>
#include <string>
#include <vector>
#include <memory>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
#include <openssl/ec.h>
#include <openssl/kdf.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include "msgr_handshake.h"
#include "openssl_util.h"
#include "str_util.h"
#define AES_256_GCM_KEY_SIZE 32
#define AES_256_GCM_IV_SIZE 12
// TLS 1.3-like handshake
// 1. Client->server: EC public key
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
// 3. Client->server: encrypted certificate and digital signature of the handshake
// "vitaECDH" interleaved
#define MSGR_HS_MAGIC 0x4861447443694576l
#define MSGR_HS_MAX_LEN 131072
#define MSGR_HS_SERVER_INIT 0
#define MSGR_HS_CLIENT_INIT 1
#define MSGR_HS_SERVER_REPLY 2
#define MSGR_HS_CLIENT_REPLY 3
#define MSGR_HS_DONE 100
#define MSGR_HS_ERROR 101
struct __attribute__((__packed__)) msgr_handshake_hdr_t
{
uint32_t msg_len;
uint64_t magic;
uint32_t type;
};
class msgr_handshake_ctx_t: public msgr_handshake_ctx_i
{
friend class msgr_handshake_t;
EVP_PKEY_CTX *pctx = NULL;
EVP_PKEY *params = NULL;
X509_STORE *ca = NULL;
X509 *osd_ca = NULL;
X509 *client_ca = NULL;
std::string my_cert_pem;
X509 *my_cert = NULL;
EVP_PKEY *my_pubkey = NULL;
const EVP_MD *md = NULL;
EVP_PKEY *my_privkey = NULL;
EVP_KDF_CTX* kdf_ctx = NULL;
std::string error;
bool on_error(const std::string & prefix);
public:
~msgr_handshake_ctx_t();
msgr_handshake_i* create() override;
bool init(const std::string & pem_cert, const std::string & pem_key,
const std::string & pem_osd_ca, const std::string & pem_client_ca) override;
std::string get_error() override;
bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) override;
};
class msgr_handshake_t: public msgr_handshake_i
{
msgr_handshake_ctx_t *ctx = NULL;
bool is_server = false;
std::vector<uint8_t> full_handshake;
std::vector<uint8_t> in_buf;
std::vector<uint8_t> out_buf;
std::string error;
int state = 0;
EVP_PKEY *ec_key = NULL;
X509 *peer_cert = NULL;
bool peer_is_osd = false;
std::vector<uint8_t> shared_secret;
std::vector<uint8_t> hs_key, peer_hs_key;
msgr_handshake_hdr_t *cur_hdr = NULL;
uint8_t *cur_buf = NULL;
size_t cur_left = 0;
bool on_error(const std::string & prefix);
bool derive_shared_secret(EVP_PKEY *peer_ec_key);
bool derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len);
bool sign(std::vector<uint8_t> & out);
bool verify(const uint8_t *signature, size_t signature_len);
bool encrypt(const uint8_t* src, size_t len, uint8_t* dest);
bool decrypt(const uint8_t* src, size_t & len, uint8_t* dest);
bool make_client_init();
bool make_server_reply();
bool make_client_reply();
bool verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len);
ssize_t start_msg(uint8_t* src, size_t len, uint32_t expected_type);
bool read_with_len(const uint8_t* & dst, uint32_t & dst_len);
bool handle_client_init();
bool handle_server_reply();
bool handle_client_reply();
bool handle_peer_cert(const uint8_t *key, uint32_t key_len);
void complete();
public:
// Workflow: create -> init -> handle -> get_result/get_error -> destruct
msgr_handshake_t(msgr_handshake_ctx_t *ctx): ctx(ctx) {}
~msgr_handshake_t();
bool init(bool server_mode) override;
ssize_t handle(uint8_t* in_buf, size_t in_size) override;
bool done() override;
std::vector<uint8_t>& get_out() override;
msgr_handshake_result_t get_result() override;
std::string get_error() override;
};
msgr_handshake_ctx_i* msgr_handshake_ctx_i::create_ctx()
{
return new msgr_handshake_ctx_t();
}
msgr_handshake_i* msgr_handshake_ctx_t::create()
{
return new msgr_handshake_t(this);
}
bool msgr_handshake_ctx_t::init(const std::string & pem_cert, const std::string & pem_key,
const std::string & pem_osd_ca, const std::string & pem_client_ca)
{
if (pem_cert.substr(0, 5) == "-----")
my_cert_pem = pem_cert;
else
{
my_cert_pem = read_file(pem_cert);
if (my_cert_pem.empty())
{
error = "Failed to load certificate file";
return false;
}
}
{
BIO *bio = BIO_new_mem_buf(my_cert_pem.data(), my_cert_pem.size());
if (!bio)
return on_error("BIO_new_mem_buf: ");
my_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
BIO_free(bio);
if (!my_cert)
return on_error("Failed to load certificate: ");
}
if (!(my_pubkey = X509_get0_pubkey(my_cert)))
return on_error("X509_get0_pubkey: ");
if (!(md = EVP_get_digestbynid(NID_sha384)))
return on_error("EVP_get_digestbynid SHA384: ");
if (!(my_privkey = openssl_load_key(pem_key)))
return on_error("Failed to load private key: ");
if (!(ca = X509_STORE_new()))
return on_error("X509_STORE_CTX_new: ");
if (!(osd_ca = openssl_load_cert(pem_osd_ca)))
return on_error("Failed to load OSD CA certificate: ");
if (X509_STORE_add_cert(ca, osd_ca) <= 0)
return on_error("X509_STORE_add_cert OSD CA: ");
if (!pem_client_ca.empty() && !(client_ca = openssl_load_cert(pem_client_ca)))
return on_error("Failed to load client CA certificate: ");
if (client_ca && X509_STORE_add_cert(ca, client_ca) <= 0)
return on_error("X509_STORE_add_cert client CA: ");
if (!(pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)))
return on_error("EVP_PKEY_CTX_new_id: ");
if (EVP_PKEY_paramgen_init(pctx) <= 0)
return on_error("EVP_PKEY_paramgen_init: ");
if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, /*NID_X9_62_prime256v1*/NID_secp384r1) <= 0)
return on_error("EVP_PKEY_CTX_set_ec_paramgen_curve_nid: ");
if (EVP_PKEY_paramgen(pctx, &params) <= 0)
return on_error("EVP_PKEY_paramgen: ");
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
if (!kdf)
return on_error("EVP_KDF_fetch: ");
kdf_ctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);
return true;
}
std::string msgr_handshake_ctx_t::get_error()
{
return error;
}
msgr_handshake_ctx_t::~msgr_handshake_ctx_t()
{
if (pctx)
EVP_PKEY_CTX_free(pctx);
if (params)
EVP_PKEY_free(params);
if (ca)
X509_STORE_free(ca);
if (osd_ca)
X509_free(osd_ca);
if (client_ca)
X509_free(client_ca);
my_pubkey = NULL;
if (my_cert)
X509_free(my_cert);
if (my_privkey)
EVP_PKEY_free(my_privkey);
if (kdf_ctx)
EVP_KDF_CTX_free(kdf_ctx);
}
bool msgr_handshake_ctx_t::on_error(const std::string & prefix)
{
error = prefix+ERR_error_string(ERR_get_error(), NULL);
return false;
}
bool msgr_handshake_ctx_t::derive_kdf(const uint8_t* insecret, size_t insecret_len,
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize)
{
OSSL_PARAM params[5];
int n = 0;
params[n++] = OSSL_PARAM_construct_utf8_string("digest", (char*)"sha384", (size_t)7);
params[n++] = OSSL_PARAM_construct_octet_string("key", (void*)insecret, insecret_len);
params[n++] = OSSL_PARAM_construct_octet_string("info", (void*)label, strlen(label)+1);
params[n++] = OSSL_PARAM_construct_octet_string("salt", (salt ? (void*)salt : (void*)""), salt_len);
params[n++] = OSSL_PARAM_construct_end();
assert(n <= sizeof(params)/sizeof(OSSL_PARAM));
if (EVP_KDF_CTX_set_params(kdf_ctx, params) <= 0)
return false;
if (EVP_KDF_derive(kdf_ctx, outsecret, outsize, NULL) <= 0)
return false;
return true;
}
msgr_handshake_t::~msgr_handshake_t()
{
if (peer_cert)
X509_free(peer_cert);
if (ec_key)
EVP_PKEY_free(ec_key);
}
bool msgr_handshake_t::on_error(const std::string & prefix)
{
error = prefix+ERR_error_string(ERR_get_error(), NULL);
state = MSGR_HS_ERROR;
return false;
}
bool msgr_handshake_t::init(bool server_mode)
{
this->ctx = ctx;
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> kctx(EVP_PKEY_CTX_new(ctx->params, NULL), EVP_PKEY_CTX_free);
if (!kctx)
return on_error("EVP_PKEY_CTX_new with EC params: ");
if (EVP_PKEY_keygen_init(kctx.get()) <= 0)
return on_error("EVP_PKEY_keygen_init: ");
if (EVP_PKEY_keygen(kctx.get(), &ec_key) <= 0)
return on_error("EVP_PKEY_keygen: ");
if (!server_mode)
{
// Send initial message - only the EC public key
if (!make_client_init())
return false;
}
this->is_server = server_mode;
this->state = server_mode ? MSGR_HS_SERVER_INIT : MSGR_HS_CLIENT_INIT;
return true;
}
static void copy_to(std::vector<uint8_t> & buf, const void* src, uint32_t len)
{
size_t old_size = buf.size();
buf.resize(buf.size() + len);
memcpy(buf.data() + old_size, src, len);
}
static void copy_to_with_len(std::vector<uint8_t> & buf, const void* src, uint32_t len)
{
copy_to(buf, &len, sizeof(len));
copy_to(buf, src, len);
}
bool msgr_handshake_t::derive_shared_secret(EVP_PKEY *peer_ec_key)
{
EVP_PKEY_CTX *dh_ctx = NULL;
if (!(dh_ctx = EVP_PKEY_CTX_new(ec_key, NULL)))
return on_error("EVP_PKEY_CTX_new for ECDH: ");
if (!EVP_PKEY_derive_init(dh_ctx))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive_init: ");
}
if (!EVP_PKEY_derive_set_peer(dh_ctx, peer_ec_key))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive_set_peer: ");
}
size_t len = 0;
if (!EVP_PKEY_derive(dh_ctx, NULL, &len))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive get length: ");
}
shared_secret.resize(len);
assert(len == 48);
if (!EVP_PKEY_derive(dh_ctx, shared_secret.data(), &len))
{
EVP_PKEY_CTX_free(dh_ctx);
return on_error("EVP_PKEY_derive: ");
}
assert(len == shared_secret.size());
shared_secret.resize(len);
EVP_PKEY_CTX_free(dh_ctx);
return true;
}
bool msgr_handshake_t::derive_hs_keys(const uint8_t *encoded_peer_key, size_t encoded_key_len)
{
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> peer_ec_key(EVP_PKEY_new(), EVP_PKEY_free);
if (!peer_ec_key)
return on_error("EVP_PKEY_new: ");
if (EVP_PKEY_copy_parameters(peer_ec_key.get(), ec_key) <= 0)
return on_error("EVP_PKEY_copy_parameters: ");
if (EVP_PKEY_set1_encoded_public_key(peer_ec_key.get(), encoded_peer_key, encoded_key_len) <= 0)
return on_error("Invalid handshake peer key: ");
if (!derive_shared_secret(peer_ec_key.get()))
return false;
hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
peer_hs_key.resize(AES_256_GCM_KEY_SIZE + AES_256_GCM_IV_SIZE);
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
NULL, 0, (state == MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
hs_key.data(), hs_key.size()))
return on_error("derive_kdf: ");
if (!ctx->derive_kdf(shared_secret.data(), shared_secret.size(),
NULL, 0, (state != MSGR_HS_SERVER_INIT ? "server hs key" : "client hs key"),
peer_hs_key.data(), peer_hs_key.size()))
return on_error("derive_kdf: ");
return true;
}
bool msgr_handshake_t::sign(std::vector<uint8_t> & out)
{
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
if (!md_ctx)
return on_error("EVP_MD_CTX_create: ");
if (EVP_DigestSignInit(md_ctx.get(), NULL, ctx->md, NULL, ctx->my_privkey) <= 0)
return on_error("EVP_DigestSignInit: ");
if (EVP_DigestSignUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
return on_error("EVP_DigestSignUpdate: ");
size_t siglen = 0;
if (EVP_DigestSignFinal(md_ctx.get(), NULL, &siglen) <= 0)
return on_error("EVP_DigestSignFinal get length: ");
size_t oldsize = out.size();
out.resize(oldsize + siglen);
if (EVP_DigestSignFinal(md_ctx.get(), out.data() + oldsize, &siglen) <= 0)
return on_error("EVP_DigestSignFinal: ");
out.resize(oldsize + siglen);
return true;
}
bool msgr_handshake_t::verify(const uint8_t *signature, size_t signature_len)
{
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> md_ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
if (!md_ctx)
return on_error("EVP_MD_CTX_create: ");
if (EVP_DigestVerifyInit(md_ctx.get(), NULL, ctx->md, NULL, X509_get0_pubkey(peer_cert)) <= 0)
return on_error("EVP_DigestVerifyInit: ");
if (EVP_DigestVerifyUpdate(md_ctx.get(), full_handshake.data(), full_handshake.size()) <= 0)
return on_error("EVP_DigestVerifyUpdate: ");
if (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) <= 0)
return false;
return true;
}
bool msgr_handshake_t::encrypt(const uint8_t* src, size_t len, uint8_t* dest)
{
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> enc_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
if (!ctx)
return on_error("EVP_CIPHER_CTX_new: ");
if (EVP_EncryptInit_ex(enc_ctx.get(), EVP_aes_256_gcm(), NULL, hs_key.data(), hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
return on_error("EVP_EncryptInit AES-256-GCM: ");
int actual_out;
if (EVP_EncryptUpdate(enc_ctx.get(), dest, &actual_out, src, len) <= 0)
return on_error("EVP_EncryptUpdate: ");
assert(actual_out == len);
if (EVP_EncryptFinal_ex(enc_ctx.get(), NULL, &actual_out) <= 0)
return on_error("EVP_EncryptFinal: ");
if (EVP_CIPHER_CTX_ctrl(enc_ctx.get(), EVP_CTRL_GCM_GET_TAG, 16, dest+len) <= 0)
return on_error("EVP_CTRL_GCM_GET_TAG: ");
(*(uint64_t*)(hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
return true;
}
bool msgr_handshake_t::decrypt(const uint8_t *src, size_t & len, uint8_t* dest)
{
if (len <= 16) // only tag?!
{
len = 0;
error = "Handshake decryption failed";
state = MSGR_HS_ERROR;
return false;
}
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> dec_ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
if (!ctx)
return on_error("EVP_CIPHER_CTX_new: ");
if (EVP_DecryptInit_ex(dec_ctx.get(), EVP_aes_256_gcm(), NULL, peer_hs_key.data(), peer_hs_key.data() + AES_256_GCM_KEY_SIZE) <= 0)
return on_error("EVP_DecryptInit AES-256-GCM: ");
int actual_out;
len -= 16;
if (EVP_DecryptUpdate(dec_ctx.get(), dest, &actual_out, src, len) <= 0)
return on_error("EVP_DecryptUpdate: ");
assert(actual_out == len);
if (EVP_CIPHER_CTX_ctrl(dec_ctx.get(), EVP_CTRL_GCM_SET_TAG, 16, (void*)(src+len)) <= 0)
return on_error("EVP_CTRL_GCM_SET_TAG: ");
if (EVP_DecryptFinal_ex(dec_ctx.get(), NULL, &actual_out) <= 0)
{
error = "Handshake decryption failed";
state = MSGR_HS_ERROR;
return false;
}
(*(uint64_t*)(peer_hs_key.data() + AES_256_GCM_KEY_SIZE))++; // change IV
return true;
}
bool msgr_handshake_t::make_client_init()
{
uint8_t *key = NULL;
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
if (!key_len)
return on_error("EVP_PKEY_get1_encoded_public_key: ");
const size_t old_len = out_buf.size();
out_buf.resize(out_buf.size() + key_len + sizeof(msgr_handshake_hdr_t));
uint8_t *buf = out_buf.data() + old_len;
msgr_handshake_hdr_t *hdr = (msgr_handshake_hdr_t *)buf;
hdr->msg_len = key_len + sizeof(msgr_handshake_hdr_t);
hdr->magic = MSGR_HS_MAGIC;
hdr->type = MSGR_HS_CLIENT_INIT;
memcpy(buf + sizeof(msgr_handshake_hdr_t), key, key_len);
copy_to(full_handshake, &hdr->type, sizeof(hdr->type));
copy_to_with_len(full_handshake, key, key_len);
OPENSSL_free(key);
return true;
}
bool msgr_handshake_t::make_server_reply()
{
uint8_t *key = NULL;
size_t key_len = EVP_PKEY_get1_encoded_public_key(ec_key, &key);
if (!key_len)
return on_error("EVP_PKEY_get1_encoded_public_key: ");
// Append type, key and raw certificate to signed data and sign it
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_SERVER_REPLY };
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
copy_to_with_len(full_handshake, key, key_len);
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
std::vector<uint8_t> signature;
if (!sign(signature))
{
OPENSSL_free(key);
return false;
}
// Encrypt certificate and signature
std::vector<uint8_t> encrypt_data;
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
copy_to_with_len(encrypt_data, signature.data(), signature.size());
encrypt_data.resize(encrypt_data.size()+16);
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
{
OPENSSL_free(key);
return false;
}
// Construct message
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + 4 + key_len + encrypt_data.size();
copy_to(out_buf, &hdr, sizeof(hdr));
copy_to_with_len(out_buf, key, key_len);
copy_to(out_buf, encrypt_data.data(), encrypt_data.size());
OPENSSL_free(key);
return true;
}
bool msgr_handshake_t::make_client_reply()
{
// Append type and raw certificate to signed data and sign it
msgr_handshake_hdr_t hdr = { .magic = MSGR_HS_MAGIC, .type = MSGR_HS_CLIENT_REPLY };
copy_to(full_handshake, &hdr.type, sizeof(hdr.type));
copy_to_with_len(full_handshake, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
std::vector<uint8_t> signature;
if (!sign(signature))
return false;
// Encrypt certificate and signature
std::vector<uint8_t> encrypt_data;
copy_to_with_len(encrypt_data, ctx->my_cert_pem.data(), ctx->my_cert_pem.size());
copy_to_with_len(encrypt_data, signature.data(), signature.size());
encrypt_data.resize(encrypt_data.size()+16);
if (!encrypt(encrypt_data.data(), encrypt_data.size()-16, encrypt_data.data()))
return false;
// Construct message
hdr.msg_len = sizeof(msgr_handshake_hdr_t) + encrypt_data.size();
copy_to(out_buf, &hdr, sizeof(hdr));
copy_to(out_buf, encrypt_data.data(), encrypt_data.size());
return true;
}
bool msgr_handshake_t::verify_peer(const uint8_t *peer_cert_pem, size_t peer_cert_len)
{
BIO *bio = BIO_new_mem_buf(peer_cert_pem, peer_cert_len);
if (!bio)
return on_error("BIO_new_mem_buf: ");
peer_cert = PEM_read_bio_X509(bio, NULL, 0, NULL);
BIO_free(bio);
if (!peer_cert)
{
error = "Invalid peer certificate";
state = MSGR_HS_ERROR;
return false;
}
std::unique_ptr<X509_STORE_CTX, decltype(&X509_STORE_CTX_free)> ca_ctx(X509_STORE_CTX_new(), X509_STORE_CTX_free);
if (!ca_ctx)
return on_error("X509_STORE_CTX_new: ");
if (X509_STORE_CTX_init(ca_ctx.get(), ctx->ca, peer_cert, NULL) <= 0)
return on_error("X509_STORE_CTX_init: ");
// Maybe use X509_VERIFY_PARAM_set_auth_level(X509_STORE_CTX_get0_param(ca_ctx.get()), 2) ?
X509_STORE_CTX_set_default(ca_ctx.get(), is_server ? "ssl_client" : "ssl_server");
if (X509_verify_cert(ca_ctx.get()) <= 0)
{
error = "Peer certificate verification failed: ";
error += X509_verify_cert_error_string(X509_STORE_CTX_get_error(ca_ctx.get()));
state = MSGR_HS_ERROR;
return false;
}
peer_is_osd = (X509_verify(peer_cert, X509_get0_pubkey(ctx->osd_ca)) > 0);
if (!is_server && !peer_is_osd)
{
error = "Peer is not an OSD";
state = MSGR_HS_ERROR;
return false;
}
return true;
}
ssize_t msgr_handshake_t::start_msg(uint8_t* src, size_t len, uint32_t expected_type)
{
size_t orig_len = len;
size_t to_buffer = (len < sizeof(msgr_handshake_hdr_t)-in_buf.size()
? len : sizeof(msgr_handshake_hdr_t)-in_buf.size());
in_buf.insert(in_buf.end(), src, src+to_buffer);
len -= to_buffer;
src += to_buffer;
if (in_buf.size() < sizeof(msgr_handshake_hdr_t))
return 0;
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
if (cur_hdr->magic != MSGR_HS_MAGIC ||
cur_hdr->type != expected_type ||
cur_hdr->msg_len <= sizeof(msgr_handshake_hdr_t) ||
cur_hdr->msg_len >= MSGR_HS_MAX_LEN)
{
error = "Invalid handshake packet magic, type or size";
state = MSGR_HS_ERROR;
return -1;
}
to_buffer = (len < cur_hdr->msg_len-in_buf.size()
? len : cur_hdr->msg_len-in_buf.size());
in_buf.insert(in_buf.end(), src, src+to_buffer);
cur_hdr = (msgr_handshake_hdr_t *)in_buf.data();
len -= to_buffer;
src += to_buffer;
if (in_buf.size() < cur_hdr->msg_len)
return 0;
cur_left = cur_hdr->msg_len - sizeof(msgr_handshake_hdr_t);
cur_buf = in_buf.data() + sizeof(msgr_handshake_hdr_t);
return orig_len - len;
}
bool msgr_handshake_t::read_with_len(const uint8_t* & dst, uint32_t & dst_len)
{
if (cur_left < 4)
{
error = "Handshake packet too short";
state = MSGR_HS_ERROR;
return false;
}
dst_len = *(uint32_t*)cur_buf;
cur_buf += 4;
cur_left -= 4;
if (cur_left < dst_len)
{
error = "Handshake packet too short";
state = MSGR_HS_ERROR;
return false;
}
dst = cur_buf;
cur_buf += dst_len;
cur_left -= dst_len;
return true;
}
bool msgr_handshake_t::handle_client_init()
{
// Derive shared secret and handshake keys
if (!derive_hs_keys(cur_buf, cur_left))
return false;
// Add type and key to full_handshake
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
copy_to_with_len(full_handshake, cur_buf, cur_left);
in_buf.clear();
return true;
}
// Decrypt and check peer certificate
bool msgr_handshake_t::handle_peer_cert(const uint8_t *key, uint32_t key_len)
{
if (!decrypt(cur_buf, cur_left, cur_buf))
return false;
const uint8_t *peer_cert_pem = NULL;
uint32_t peer_cert_len = 0;
if (!read_with_len(peer_cert_pem, peer_cert_len))
return false;
// Parse and verify certificate
if (!verify_peer(peer_cert_pem, peer_cert_len))
return false;
// Verify signature
copy_to(full_handshake, &cur_hdr->type, sizeof(cur_hdr->type));
if (key)
copy_to_with_len(full_handshake, key, key_len);
copy_to_with_len(full_handshake, peer_cert_pem, peer_cert_len);
const uint8_t *signature = NULL;
uint32_t signature_len = 0;
if (!read_with_len(signature, signature_len))
return false;
if (!verify(signature, signature_len))
return false;
return true;
}
bool msgr_handshake_t::handle_server_reply()
{
// Derive shared secret and handshake keys
const uint8_t *key = NULL;
uint32_t key_len = 0;
if (!read_with_len(key, key_len))
return false;
if (!derive_hs_keys(key, key_len))
return false;
// Decrypt and check peer certificate
if (!handle_peer_cert(key, key_len))
return false;
in_buf.clear();
return true;
}
bool msgr_handshake_t::handle_client_reply()
{
// Decrypt and check peer certificate
if (!handle_peer_cert(NULL, 0))
return false;
in_buf.clear();
return true;
}
void msgr_handshake_t::complete()
{
state = MSGR_HS_DONE;
hs_key.clear();
peer_hs_key.clear();
full_handshake.clear();
}
ssize_t msgr_handshake_t::handle(uint8_t* in_buf, size_t in_size)
{
if (state == MSGR_HS_SERVER_INIT)
{
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_INIT);
if (r < 0)
return r;
if (r == 0)
return in_size;
if (!handle_client_init())
return -1;
// Send encrypted & signed response
if (!make_server_reply())
return -1;
state = MSGR_HS_SERVER_REPLY;
return r;
}
else if (state == MSGR_HS_CLIENT_INIT)
{
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_SERVER_REPLY);
if (r < 0)
return r;
if (r == 0)
return in_size;
if (!handle_server_reply())
return -1;
// Verification passed, send certificate to the server
if (!make_client_reply())
return -1;
// Finished!
complete();
return r;
}
else if (state == MSGR_HS_SERVER_REPLY)
{
ssize_t r = start_msg(in_buf, in_size, MSGR_HS_CLIENT_REPLY);
if (r < 0)
return r;
if (r == 0)
return in_size;
if (!handle_client_reply())
return -1;
// Verification passed
// Finished!
complete();
return r;
}
else if (state == MSGR_HS_DONE)
{
return 0;
}
else if (state != MSGR_HS_ERROR)
{
error = "Unexpected handshake state: "+std::to_string(state);
}
return -1;
}
bool msgr_handshake_t::done()
{
return (state == MSGR_HS_DONE);
}
std::vector<uint8_t>& msgr_handshake_t::get_out()
{
return out_buf;
}
msgr_handshake_result_t msgr_handshake_t::get_result()
{
if (state != MSGR_HS_DONE)
return msgr_handshake_result_t{};
X509_up_ref(peer_cert);
return msgr_handshake_result_t{
.peer_cert = peer_cert,
.peer_is_osd = peer_is_osd,
.shared_secret = shared_secret,
};
}
std::string msgr_handshake_t::get_error()
{
return error;
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#pragma once
#include <stdint.h>
#include <string>
#include <vector>
#include <openssl/types.h>
#define AES_256_GCM_KEY_SIZE 32
#define AES_256_GCM_IV_SIZE 12
#define AES_256_GCM_MAX_IV_CTR ((uint64_t)1 << 32)
// TLS 1.3-like handshake
// 1. Client->server: EC public key
// 2. Server->client: EC public key, encrypted certificate and digital signature of the handshake
// 3. Client->server: encrypted certificate and digital signature of the handshake
struct msgr_handshake_result_t
{
X509 *peer_cert = NULL;
bool peer_is_osd = false;
std::vector<uint8_t> shared_secret;
};
class msgr_handshake_i
{
public:
// Workflow: create -> init -> handle_msg -> get_result/get_error -> destruct
virtual ~msgr_handshake_i() = default;
virtual bool init(bool server_mode) = 0;
virtual ssize_t handle(uint8_t* in_buf, size_t in_size) = 0;
virtual bool done() = 0;
virtual std::vector<uint8_t> & get_out() = 0;
virtual msgr_handshake_result_t get_result() = 0;
virtual std::string get_error() = 0;
};
class msgr_handshake_ctx_i
{
public:
static msgr_handshake_ctx_i* create_ctx();
virtual ~msgr_handshake_ctx_i() = default;
virtual msgr_handshake_i* create() = 0;
virtual bool init(const std::string & pem_cert, const std::string & pem_key,
const std::string & pem_osd_ca, const std::string & pem_client_ca) = 0;
virtual std::string get_error() = 0;
virtual bool derive_kdf(const uint8_t* insecret, size_t insecret_len,
const uint8_t* salt, size_t salt_len, const char *label, uint8_t *outsecret, size_t outsize) = 0;
};
+5 -3
View File
@@ -173,7 +173,7 @@ struct __attribute__((visibility("default"))) osd_op_t
{
timespec tv_begin = { 0 }, tv_end = { 0 };
uint64_t op_type = OSD_OP_IN;
int peer_fd;
uint64_t client_id = 0;
osd_any_op_t req;
osd_any_reply_t reply;
blockstore_op_t *bs_op = NULL;
@@ -181,11 +181,12 @@ struct __attribute__((visibility("default"))) osd_op_t
// bitmap, bitmap_len, bmp_data are only meaningful for reads
void *bitmap = NULL;
unsigned bitmap_len = 0;
unsigned bmp_data = 0;
void *bitmap_buf = NULL;
size_t bmp_data = 0;
uint8_t *bitmap_buf = NULL;
void *rmw_buf = NULL;
std::shared_ptr<osd_op_enc_t> enc;
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;
@@ -195,4 +196,5 @@ struct __attribute__((visibility("default"))) osd_op_t
void cancel();
bool is_recovery_related();
uint64_t calc_data_checksum();
};
+64 -43
View File
@@ -187,6 +187,8 @@ std::vector<msgr_rdma_context_t*> msgr_rdma_context_t::create_all(const std::vec
ibv_device **raw_dev_list = NULL;
ibv_device **dev_list = NULL;
ibv_device *single_list[2] = {};
int up_ports = 0;
int single_port_num = 0;
raw_dev_list = dev_list = ibv_get_device_list(NULL);
if (!dev_list || !*dev_list)
@@ -221,6 +223,7 @@ std::vector<msgr_rdma_context_t*> msgr_rdma_context_t::create_all(const std::vec
dev_list = single_list;
}
retry:
for (int i = 0; dev_list[i]; ++i)
{
auto dev = dev_list[i];
@@ -258,6 +261,9 @@ std::vector<msgr_rdma_context_t*> msgr_rdma_context_t::create_all(const std::vec
fprintf(stderr, "RDMA device %s port %d GID %d does not exist\n", ibv_get_device_name(dev), port_num, sel_gid_index);
continue;
}
up_ports++;
single_port_num = port_num;
single_list[0] = dev;
uint32_t port_mtu = sel_mtu ? sel_mtu : ibv_mtu_to_bytes(portinfo.active_mtu);
#ifdef IBV_ADVISE_MR_ADVICE_PREFETCH_NO_FAULT
if (sel_gid_index < 0)
@@ -298,6 +304,14 @@ cleanup_dev:
ibv_close_device(context);
}
if (!ret.size() && up_ports == 1 && dev_list != single_list)
{
// Auto-select the only available device/port if there is only one
dev_list = single_list;
sel_port_num = single_port_num;
goto retry;
}
cleanup:
if (raw_dev_list)
ibv_free_device_list(raw_dev_list);
@@ -493,7 +507,7 @@ int msgr_rdma_connection_t::connect(msgr_rdma_address_t *dest)
return 0;
}
bool osd_messenger_t::connect_rdma(int peer_fd, std::string rdma_address, uint64_t client_max_msg)
bool osd_messenger_t::connect_rdma(uint64_t client_id, std::string rdma_address, uint64_t client_max_msg)
{
// Try to connect to the peer using RDMA
msgr_rdma_address_t addr;
@@ -503,12 +517,12 @@ bool osd_messenger_t::connect_rdma(int peer_fd, std::string rdma_address, uint64
{
client_max_msg = rdma_max_msg;
}
auto cl = clients.at(peer_fd);
auto cl = clients.at(client_id);
msgr_rdma_context_t *selected_ctx = choose_rdma_context(cl);
if (!selected_ctx)
{
if (log_level > 0)
fprintf(stderr, "No RDMA context for peer %d, using only TCP\n", cl->peer_fd);
fprintf(stderr, "No RDMA context for peer %ju, using only TCP\n", client_id);
return false;
}
msgr_rdma_connection_t *rdma_conn = msgr_rdma_connection_t::create(selected_ctx, rdma_max_send, rdma_max_recv, rdma_max_sge, client_max_msg);
@@ -519,16 +533,15 @@ bool osd_messenger_t::connect_rdma(int peer_fd, std::string rdma_address, uint64
{
delete rdma_conn;
fprintf(
stderr, "Failed to connect RDMA queue pair to %s (client %d)\n",
addr.to_string().c_str(), peer_fd
stderr, "Failed to connect RDMA queue pair to %s (client %ju)\n",
addr.to_string().c_str(), client_id
);
}
else
{
// Remember connection, but switch to RDMA only after sending the configuration response
auto cl = clients.at(peer_fd);
cl->rdma_conn = rdma_conn;
cl->peer_state = PEER_RDMA_CONNECTING;
cl->peer_state = PEER_RDMA_CONNECTING_IN;
return true;
}
}
@@ -540,7 +553,7 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
{
ibv_send_wr *bad_wr = NULL;
ibv_send_wr wr = {
.wr_id = (uint64_t)(cl->peer_fd*2+1),
.wr_id = cl->client_id,
.sg_list = sge,
.num_sge = op_sge,
.opcode = IBV_WR_SEND,
@@ -555,33 +568,6 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
cl->rdma_conn->cur_send++;
}
int osd_messenger_t::try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len)
{
int total_dst_len = dst_len;
while (dst_len > 0 && (cl->write_op || cl->write_ops.size()))
{
if (!cl->write_op)
{
cl->write_op = cl->write_ops.front();
cl->write_ops.pop_front();
}
osd_op_t *op = cl->write_op;
size_t copied = op_copy_to(cl, dst, dst_len);
if (!copied)
{
break;
}
dst += copied;
dst_len -= copied;
if (!cl->write_op && op->op_type == OSD_OP_IN)
{
// this is a reply, free the op after sending it
cl->send_free_ops.push_back(op);
}
}
return total_dst_len-dst_len;
}
void osd_messenger_t::try_send_rdma(osd_client_t *cl)
{
auto rc = cl->rdma_conn;
@@ -607,7 +593,12 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
dst_len = (rc->send_out_pos < rc->send_out_size ? rc->send_out_size-rc->send_out_pos : rc->send_done_pos-rc->send_out_pos);
if (dst_len > rc->max_msg)
dst_len = rc->max_msg;
copied = 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;
@@ -637,7 +628,7 @@ static void try_recv_rdma_wr(osd_client_t *cl, void *buf)
};
ibv_recv_wr *bad_wr = NULL;
ibv_recv_wr wr = {
.wr_id = (uint64_t)(cl->peer_fd*2),
.wr_id = cl->client_id,
.sg_list = &sge,
.num_sge = 1,
};
@@ -652,6 +643,7 @@ static void try_recv_rdma_wr(osd_client_t *cl, void *buf)
bool osd_messenger_t::init_recv_rdma(osd_client_t *cl)
{
return true;
auto rc = cl->rdma_conn;
assert(!rc->recv_buf.buf);
rc->recv_buf.buf = (uint8_t*)malloc_or_die(rc->max_msg * rc->max_recv);
@@ -694,8 +686,8 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
event_count = ibv_poll_cq(rdma_context->cq, RDMA_EVENTS_AT_ONCE, wc);
for (int i = 0; i < event_count; i++)
{
int client_id = wc[i].wr_id >> 1;
bool is_send = wc[i].wr_id & 1;
uint64_t client_id = wc[i].wr_id;
bool is_send = wc[i].opcode == IBV_WC_SEND;
auto cl_it = clients.find(client_id);
if (cl_it == clients.end())
{
@@ -707,25 +699,49 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
continue;
}
auto rc = cl->rdma_conn;
if (!rc)
{
// Connection destroyed (fallback to TCP)
continue;
}
if (wc[i].status != IBV_WC_SUCCESS)
{
fprintf(stderr, "RDMA work request failed for client %d", client_id);
fprintf(stderr, "RDMA work request (%s) failed for client %ju", is_send ? "send" : "recv", client_id);
if (cl->osd_num)
{
fprintf(stderr, " (OSD %ju)", cl->osd_num);
}
fprintf(stderr, " with status: %s, stopping client\n", ibv_wc_status_str(wc[i].status));
stop_client(client_id);
clear_immediate_ops(client_id);
continue;
}
if (!is_send)
{
if (cl->peer_state == PEER_RDMA_CONNECTING_OUT)
{
// First message received over RDMA - TCP socket can be closed now
if (log_level > 0)
{
fprintf(stderr, "Successfully connected with client %ju using RDMA\n", cl->client_id);
}
cl->peer_state = PEER_RDMA;
if (cl->peer_fd >= 0 && cl->rdma_close_tcp)
{
// TCP socket is not needed anymore
tfd->set_fd_handler(cl->peer_fd, false, NULL);
clients_by_fd.erase(cl->peer_fd);
close(cl->peer_fd);
cl->peer_fd = -1;
}
if (cl->osd_num)
{
osd_peers[cl->osd_num] = cl;
on_connect_peer(cl->osd_num, 0, cl->client_id);
}
}
rc->cur_recv--;
if (!handle_read_buffer(cl, rc->recv_buffers[rc->next_recv_buf], wc[i].byte_len))
{
// handle_read_buffer may stop the client
clear_immediate_ops(client_id);
continue;
}
try_recv_rdma_wr(cl, rc->recv_buffers[rc->next_recv_buf]);
@@ -748,6 +764,11 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
cl->send_free_ops.pop_front();
}
cl->send_free_ops.pop_front();
if ((cl->proto_csum_status & MSGR_CSUM_NEG) && !cl->write_op && !cl->write_ops.size())
{
// Checksums negotiated, enable
cl->proto_csum_status = cl->proto_csum_status & (~MSGR_CSUM_NEG);
}
try_send_rdma(cl);
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ struct msgr_rdma_connection_t
msgr_rdma_buf_t recv_buf;
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();
+16 -32
View File
@@ -11,7 +11,7 @@
struct rdmacm_connecting_t
{
rdma_cm_id *cmid = NULL;
int peer_fd = -1;
uint64_t client_id = 0;
osd_num_t peer_osd = 0;
std::string addr;
sockaddr_storage parsed_addr = {};
@@ -19,6 +19,7 @@ struct rdmacm_connecting_t
int tcp_port = 0;
int timeout_ms = 0;
int timeout_id = -1;
bool is_incoming = false;
msgr_rdma_context_t *rdma_context = NULL;
};
@@ -117,9 +118,9 @@ void osd_messenger_t::handle_rdmacm_events()
auto cli_it = rdmacm_connections.find(ev->id);
if (cli_it != rdmacm_connections.end())
{
fprintf(stderr, "Received %s event for peer %d, closing connection\n",
event_type_name, cli_it->second->peer_fd);
stop_client(cli_it->second->peer_fd);
fprintf(stderr, "Received %s event for client %ju, closing connection\n",
event_type_name, cli_it->second->client_id);
stop_client(cli_it->second->client_id);
}
else if (rdmacm_connecting.find(ev->id) != rdmacm_connecting.end())
{
@@ -265,14 +266,6 @@ msgr_rdma_context_t* osd_messenger_t::rdmacm_create_qp(rdma_cm_id *cmid)
void osd_messenger_t::rdmacm_accept(rdma_cm_event *ev)
{
// Make a fake FD (FIXME: do not use FDs for identifying clients!)
int fake_fd = socket(AF_INET, SOCK_STREAM, 0);
if (fake_fd < 0)
{
fprintf(stderr, "Failed to allocate a fake socket for RDMA-CM client: %s (code %d)\n", strerror(errno), errno);
rdma_destroy_id(ev->id);
return;
}
auto rdma_context = rdmacm_create_qp(ev->id);
if (!rdma_context)
{
@@ -297,12 +290,13 @@ void osd_messenger_t::rdmacm_accept(rdma_cm_event *ev)
// Wait for RDMA_CM_ESTABLISHED, and enable the connection only after it
auto conn = new rdmacm_connecting_t;
conn->cmid = ev->id;
conn->peer_fd = fake_fd;
conn->client_id = next_client_id++;
conn->parsed_addr = *(sockaddr_storage*)rdma_get_peer_addr(ev->id);
conn->rdma_context = rdma_context;
conn->is_incoming = true;
rdmacm_set_conn_timeout(conn);
rdmacm_connecting[ev->id] = conn;
fprintf(stderr, "[OSD %ju] new client %d: connection from %s via RDMA-CM\n", this->osd_num, conn->peer_fd,
fprintf(stderr, "[OSD %ju] new client %ju: connection from %s via RDMA-CM\n", this->osd_num, conn->client_id,
addr_to_string(conn->parsed_addr).c_str());
}
@@ -332,8 +326,6 @@ void osd_messenger_t::rdmacm_on_connect_peer_error(rdma_cm_id *cmid, int res)
auto peer_osd = conn->peer_osd;
if (conn->timeout_id >= 0)
tfd->clear_timer(conn->timeout_id);
if (conn->peer_fd >= 0)
close(conn->peer_fd);
if (conn->rdma_context)
conn->rdma_context->reserve_cqe(-rdma_max_send-rdma_max_recv);
if (conn->cmid)
@@ -354,7 +346,7 @@ void osd_messenger_t::rdmacm_on_connect_peer_error(rdma_cm_id *cmid, int res)
else
{
// TCP is disabled
on_connect_peer(peer_osd, res == 0 ? -EINVAL : (res > 0 ? -res : res));
on_connect_peer(peer_osd, res == 0 ? -EINVAL : (res > 0 ? -res : res), 0);
}
}
}
@@ -365,7 +357,7 @@ void osd_messenger_t::rdmacm_try_connect_peer(uint64_t peer_osd, const std::stri
if (!string_to_addr(addr, false, rdmacm_port, &sa))
{
fprintf(stderr, "Address %s is invalid\n", addr.c_str());
on_connect_peer(peer_osd, -EINVAL);
on_connect_peer(peer_osd, -EINVAL, 0);
return;
}
rdma_cm_id *cmid = NULL;
@@ -376,17 +368,7 @@ void osd_messenger_t::rdmacm_try_connect_peer(uint64_t peer_osd, const std::stri
if (!disable_tcp)
try_connect_peer_tcp(peer_osd, addr.c_str(), fallback_tcp_port);
else
on_connect_peer(peer_osd, res);
return;
}
// Make a fake FD (FIXME: do not use FDs for identifying clients!)
int fake_fd = socket(AF_INET, SOCK_STREAM, 0);
if (fake_fd < 0)
{
int res = -errno;
rdma_destroy_id(cmid);
// Can't create socket, pointless to try TCP
on_connect_peer(peer_osd, res);
on_connect_peer(peer_osd, res, 0);
return;
}
if (log_level > 0)
@@ -394,7 +376,7 @@ void osd_messenger_t::rdmacm_try_connect_peer(uint64_t peer_osd, const std::stri
auto conn = new rdmacm_connecting_t;
rdmacm_connecting[cmid] = conn;
conn->cmid = cmid;
conn->peer_fd = fake_fd;
conn->client_id = next_client_id++;
conn->peer_osd = peer_osd;
conn->addr = addr;
conn->parsed_addr = sa;
@@ -511,13 +493,15 @@ void osd_messenger_t::rdmacm_established(rdma_cm_event *ev)
auto cl = new osd_client_t();
cl->peer_addr = conn->parsed_addr;
cl->peer_port = conn->rdmacm_port;
cl->peer_fd = conn->peer_fd;
cl->client_id = conn->client_id;
cl->is_incoming = conn->is_incoming;
cl->peer_state = PEER_RDMA;
cl->connect_timeout_id = -1;
cl->osd_num = peer_osd;
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
cl->rdma_conn = rc;
clients[conn->peer_fd] = cl;
init_tls_client(cl);
clients[conn->client_id] = cl;
if (conn->timeout_id >= 0)
{
tfd->clear_timer(conn->timeout_id);
File diff suppressed because it is too large Load Diff
+686 -180
View File
File diff suppressed because it is too large Load Diff
+116 -47
View File
@@ -5,9 +5,12 @@
#include <assert.h>
#include "messenger.h"
#include "../util/xxh_x86dispatch.h"
#ifdef WITH_RDMA
#include "msgr_rdma.h"
#endif
#include <openssl/evp.h>
#include <openssl/pem.h>
void osd_client_t::cancel_ops()
{
@@ -43,51 +46,57 @@ void osd_op_t::cancel()
}
}
void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete)
// force_delete means stop the client anyway, even if there are refs to it in the event loop.
// the flag should be used in the destructor.
// why? - because yes, we could close the FD first and let it fail all requests in the event loop,
// but in that case it can be quickly reopened and we can get old failed responses for the new FD.
void osd_messenger_t::stop_client(uint64_t client_id, bool force_delete)
{
assert(peer_fd != 0);
auto it = clients.find(peer_fd);
if (it == clients.end())
auto it = clients.find(client_id);
if (!client_id || it == clients.end())
{
return;
}
osd_client_t *cl = it->second;
// FIXME: This 'force' flag is probably an ugly reenterability hack - check its logic and maybe remove it
if (cl->peer_state == PEER_CONNECTING && !force || cl->peer_state == PEER_STOPPED)
if (cl->peer_state == PEER_STOPPED)
{
if (force_delete)
{
destroy_client(cl);
}
return;
}
clear_immediate_ops(peer_fd);
cl->received_ops.clear();
if (log_level > 0)
{
if (cl->osd_num)
{
fprintf(stderr, "[OSD %ju] Stopping client %d (OSD peer %ju)\n", osd_num, peer_fd, cl->osd_num);
fprintf(stderr, "[OSD %ju] Stopping client %ju (OSD peer %ju)\n", osd_num, client_id, cl->osd_num);
}
else if (cl->in_osd_num)
{
fprintf(stderr, "[OSD %ju] Stopping client %d (incoming OSD peer %ju)\n", osd_num, peer_fd, cl->in_osd_num);
fprintf(stderr, "[OSD %ju] Stopping client %ju (incoming OSD peer %ju)\n", osd_num, client_id, cl->in_osd_num);
}
else
{
fprintf(stderr, "[OSD %ju] Stopping client %d (regular client)\n", osd_num, peer_fd);
fprintf(stderr, "[OSD %ju] Stopping client %ju (regular client)\n", osd_num, client_id);
}
}
if (cl->encrypt_ctx)
if (cl->xts_enc_ctx)
{
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
destroy_aes_xts_encrypt(cl->encrypt_ctx);
if (encrypt_xts_pool.size() > max_cipher_pool_size)
destroy_aes_xts_encrypt(cl->xts_enc_ctx);
else
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
cl->encrypt_ctx = NULL;
encrypt_xts_pool.push_back(cl->xts_enc_ctx);
cl->xts_enc_ctx = NULL;
}
if (cl->decrypt_ctx)
if (cl->xts_dec_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
destroy_aes_xts_decrypt(cl->decrypt_ctx);
if (decrypt_xts_pool.size() > max_cipher_pool_size)
destroy_aes_xts_decrypt(cl->xts_dec_ctx);
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
decrypt_xts_pool.push_back(cl->xts_dec_ctx);
cl->xts_dec_ctx = NULL;
}
// First set state to STOPPED so another stop_client() call doesn't try to free it again
cl->refs++;
@@ -95,11 +104,11 @@ void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete)
cl->peer_state = PEER_STOPPED;
if (cl->osd_num)
{
auto osd_it = osd_peer_fds.find(cl->osd_num);
if (osd_it != osd_peer_fds.end() && osd_it->second == cl->peer_fd)
auto osd_it = osd_peers.find(cl->osd_num);
if (osd_it != osd_peers.end() && osd_it->second == cl)
{
// ...and forget OSD peer
osd_peer_fds.erase(osd_it);
osd_peers.erase(osd_it);
}
}
#ifdef WITH_RDMA
@@ -113,29 +122,11 @@ void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete)
}
#endif
#ifndef __MOCK__
// Then remove FD from the eventloop so we don't accidentally read something
tfd->set_fd_handler(peer_fd, false, NULL);
if (cl->connect_timeout_id >= 0)
{
tfd->clear_timer(cl->connect_timeout_id);
cl->connect_timeout_id = -1;
}
for (auto rit = read_ready_clients.begin(); rit != read_ready_clients.end(); rit++)
{
if (*rit == peer_fd)
{
read_ready_clients.erase(rit);
break;
}
}
for (auto wit = write_ready_clients.begin(); wit != write_ready_clients.end(); wit++)
{
if (*wit == peer_fd)
{
write_ready_clients.erase(wit);
break;
}
}
#endif
if (cl->in_osd_num && break_pg_locks)
{
@@ -150,19 +141,51 @@ void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete)
// so do not repeer on it.
repeer_pgs(cl->osd_num);
}
// Find the item again because it can be invalidated at this point
it = clients.find(peer_fd);
if (it != clients.end())
if (cl->peer_fd >= 0)
{
clients.erase(it);
int r = shutdown(cl->peer_fd, SHUT_RDWR);
if (r != 0 && errno != ENOTCONN)
{
fprintf(stderr, "[OSD %ju] failed to shutdown a socket: %s (code %d)\n", osd_num, strerror(errno), errno);
}
}
cl->refs--;
if (cl->refs <= 0 || force_delete)
{
delete cl;
destroy_client(cl);
}
}
void osd_messenger_t::destroy_client(osd_client_t *cl)
{
// Find the item again because it can be invalidated at this point
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)
{
read_ready_clients.erase(rit);
break;
}
}
for (auto wit = write_ready_clients.begin(); wit != write_ready_clients.end(); wit++)
{
if (*wit == cl->client_id)
{
write_ready_clients.erase(wit);
break;
}
}
clients_by_fd.erase(cl->peer_fd);
}
delete cl;
}
osd_client_t::~osd_client_t()
{
free(in_buf);
@@ -196,7 +219,10 @@ osd_client_t::~osd_client_t()
{
if (op)
{
delete op;
if (!((size_t)op & 7))
delete op;
else
free((void*)((size_t)op & ~(size_t)7));
}
}
#ifndef __MOCK__
@@ -208,4 +234,47 @@ osd_client_t::~osd_client_t()
}
#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;
}
if (enc_ctx)
{
#ifdef WITH_ISAL_CRYPTO
free(enc_ctx);
#else
EVP_CIPHER_CTX_free(enc_ctx);
#endif
enc_ctx = NULL;
}
if (dec_ctx)
{
#ifdef WITH_ISAL_CRYPTO
free(dec_ctx);
#else
EVP_CIPHER_CTX_free(dec_ctx);
#endif
dec_ctx = NULL;
}
if (ssl_out_buf)
{
free(ssl_out_buf);
ssl_out_buf = NULL;
}
if (hs)
{
delete hs;
hs = NULL;
}
if (hs_result.peer_cert)
{
X509_free(hs_result.peer_cert);
hs_result.peer_cert = NULL;
}
}
+13 -9
View File
@@ -231,12 +231,25 @@ struct __attribute__((__packed__)) osd_op_rw_t
uint32_t len;
// 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 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
@@ -247,15 +260,6 @@ struct __attribute__((__packed__)) osd_reply_rw_t
uint32_t pad0;
// for reads and writes: object version
uint64_t version;
// for reads: chain info size for OSD_OP_RETURN_CHAIN
// (parent number is returned as a variable 2^N number of bits)
// i.e. 0 bits = everything is read from the inode itself
// 1 bit = inode(0) or its parent(1)
// 2 bits = inode(0) or its 1-3 parents
// 4 bits = inode(0) or its 1-7 parents
// and etc
// chain size in bits is (op->req.rw.len / bitmap_granularity * chain_bits_per_block + 7) / 8
uint32_t chain_bits_per_block;
};
struct __attribute__((__packed__)) osd_reply_del_t
+1 -1
View File
@@ -6,7 +6,7 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: Vitastor
Description: Vitastor client library
Version: 3.0.5
Version: 3.0.10
Libs: -L${libdir} -lvitastor_client
Cflags: -I${includedir}
+4 -3
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
@@ -15,15 +15,14 @@ add_custom_command(
add_library(vitastor_cli STATIC
cli_common.cpp
cli_alloc_osd.cpp
cli_status.cpp
cli_describe.cpp
cli_fix.cpp
cli_ls.cpp
cli_create.cpp
cli_create_user.cpp
cli_dd.cpp
cli_modify.cpp
cli_modify_osd.cpp
cli_modify_user.cpp
cli_osd_tree.cpp
cli_pg_ls.cpp
cli_flatten.cpp
@@ -38,6 +37,8 @@ add_library(vitastor_cli STATIC
cli_pool_modify.cpp
cli_pool_rm.cpp
cli_serve.cpp
cli_status.cpp
cli_user_ls.cpp
${OPENAPI_JSON_H}
)
target_compile_options(vitastor_cli PUBLIC -fPIC)
+51 -12
View File
@@ -49,6 +49,9 @@ static const char* help_text =
" --enc-key random Generate a new random AES-256-XTS encryption key for the new image.\n"
" --enc-key HEX Set a specified AES-256-XTS key (64 bytes in hex) for the new image.\n"
" --enc-key vault:ID Use an encryption key from an external Vault secret with specified ID.\n"
" --owner username Set owner (default is the current user from TLS certificate).\n"
" --owner_group name Set owner group name.\n"
" --reader_group rdr Set reader group name.\n"
"\n"
"vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>\n"
"vitastor-cli snap-create [OPTIONS] <image>@<snapshot>\n"
@@ -63,10 +66,13 @@ static const char* help_text =
" Rename, resize image or change its readonly status. Images with children can't be made read-write.\n"
" If the new size is smaller than the old size, extra data will be purged.\n"
" You should resize file system in the image, if present, before shrinking it.\n"
" --deleted 1|0 Set/clear 'deleted image' flag (set automatically during unfinished deletes).\n"
" -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n"
" --down-ok Proceed with shrinking even if some data will be left on unavailable OSDs.\n"
" --enc-key HEX Change image encryption key (allowed only with --force).\n"
" --deleted 1|0 Set/clear 'deleted image' flag (set automatically during unfinished deletes).\n"
" -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n"
" --down-ok Proceed with shrinking even if some data will be left on unavailable OSDs.\n"
" --enc-key HEX Change image encryption key (allowed only with --force).\n"
" --owner username Change image owner.\n"
" --owner_group name Change image owner group name.\n"
" --reader_group rdr Change image reader group name.\n"
"\n"
"vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M]\n"
" [count=N] [seek/oseek=N] [skip/iseek=M] [iodepth=N] [status=progress]\n"
@@ -205,6 +211,7 @@ static const char* help_text =
" --used_for_app s3:<name> Mark pool as used for S3 location with name <name>\n"
" --pg_stripe_size <number> Increase object grouping stripe\n"
" --max_osd_combinations 10000 Maximum number of random combinations for LP solver input\n"
" --creator_group <group> User group allowed to create images in this pool.\n"
" --wait Wait for the new pool to come online\n"
" -f|--force Do not check that cluster has enough OSDs to create the pool\n"
" Examples:\n"
@@ -216,7 +223,7 @@ static const char* help_text =
" [-s|--pg_size <number>] [--pg_minsize <number>] [-n|--pg_count <count>]\n"
" [--failure_domain <level>] [--root_node <node>] [--osd_tags <tags>] [--used_for_app <type>:<name>]\n"
" [--max_osd_combinations <number>] [--primary_affinity_tags <tags>] [--scrub_interval <time>]\n"
" [--level_placement <rules>] [--raw_placement <rules>]\n"
" [--level_placement <rules>] [--raw_placement <rules>] [--creator_group <group>]\n"
" Non-modifiable parameters (changing them WILL lead to data loss):\n"
" [--block_size <size>] [--bitmap_granularity <size>]\n"
" [--immediate_commit <all|small|none>] [--pg_stripe_size <size>]\n"
@@ -237,16 +244,24 @@ static const char* help_text =
" -r|--reverse Sort in descending order\n"
" -n|--count N Only list first N items\n"
"\n"
"vitastor-cli create-user --type <type> --name <name> --grants <grants>\n"
" FIXME\n"
"vitastor-cli ls-users|user-ls|ls-user|list-users [<name> ...]\n"
" List users (only with specified names if passed).\n"
"\n"
"vitastor-cli modify-user --type <type> --groups group1,group2,... <username>\n"
" Create or update user permissions. User names match CN of their certificates.\n"
" --type TYPE Set user type: client, admin, mon or osd. Default is client.\n"
" --groups GROUPS Set user's groups.\n"
"\n"
"vitastor-cli rm-user|remove-user|delete-user <username>\n"
" Remove a user.\n"
"\n"
"vitastor-cli serve\n"
" Start HTTP server able to handle CLI commands over a REST API. Options:\n"
" --bind_address ADDR Specify server IP address or addresses, separated by space. Default is 127.0.0.1.\n"
" --port 8080 Specify server port.\n"
" --ssl_cert FILE Path to server SSL certificate file (PEM format).\n"
" --ssl_key FILE Path to server SSL private key file.\n"
" --ssl_ca FILE Path to file with SSL CA certificates used to validate client connections.\n"
" --server_cert FILE Path to server TLS certificate file (PEM format).\n"
" --server_key FILE Path to server TLS private key file.\n"
" --client_ca FILE Path to file with TLS CA certificates used to validate client connections.\n"
"\n"
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
"\n"
@@ -570,10 +585,34 @@ std::function<bool(cli_result_t &)> cli_tool_t::start(json11::Json::object cfg,
}
action_cb = start_pool_ls(cfg);
}
else if (cmd[0] == "create-user")
else if (cmd[0] == "user-ls" || cmd[0] == "ls-user" || cmd[0] == "ls-users" || cmd[0] == "list-users")
{
// List users
if (cmd.size() > 1)
{
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["names"] = cmd;
}
action_cb = start_user_ls(cfg);
}
else if (cmd[0] == "modify-user" || cmd[0] == "user-modify")
{
// Create/update user
action_cb = start_create_user(cfg);
if (cmd.size() > 1)
{
cfg["name"] = cmd[1];
}
action_cb = start_modify_user(cfg);
}
else if (cmd[0] == "rm-user" || cmd[0] == "remove-user" || cmd[0] == "delete-user")
{
// Remove user
if (cmd.size() > 1)
{
cfg["name"] = cmd[1];
}
cfg["remove"] = true;
action_cb = start_modify_user(cfg);
}
else if (cmd[0] == "serve")
{
+9 -4
View File
@@ -9,6 +9,7 @@
#include "object_id.h"
#include "ringloop.h"
#include <functional>
#include <set>
struct rm_inode_t;
struct snap_merger_t;
@@ -26,6 +27,8 @@ struct cli_result_t
json11::Json data;
};
struct user_info_t;
class cli_tool_t
{
public:
@@ -37,6 +40,8 @@ public:
bool is_command_line = false;
bool color = false;
std::shared_ptr<user_info_t> user; // for http mode
ring_loop_t *ringloop = NULL;
epoll_manager_t *epmgr = NULL;
cluster_client_t *cli = NULL;
@@ -53,6 +58,8 @@ public:
void change_parent(inode_t cur, inode_t new_parent, cli_result_t *result);
inode_config_t* get_inode_cfg(const std::string & name);
bool check_image_perm(const inode_config_t & cfg, bool write);
friend struct rm_inode_t;
friend struct snap_merger_t;
friend struct snap_flattener_t;
@@ -61,7 +68,6 @@ public:
std::function<bool(cli_result_t &)> start(json11::Json::object cfg, cli_result_t & result);
std::function<bool(cli_result_t &)> start_alloc_osd(json11::Json);
std::function<bool(cli_result_t &)> start_create(json11::Json);
std::function<bool(cli_result_t &)> start_create_user(json11::Json);
std::function<bool(cli_result_t &)> start_dd(json11::Json);
std::function<bool(cli_result_t &)> start_describe(json11::Json);
std::function<bool(cli_result_t &)> start_fix(json11::Json);
@@ -70,6 +76,7 @@ public:
std::function<bool(cli_result_t &)> start_merge(json11::Json);
std::function<bool(cli_result_t &)> start_modify(json11::Json);
std::function<bool(cli_result_t &)> start_modify_osd(json11::Json);
std::function<bool(cli_result_t &)> start_modify_user(json11::Json);
std::function<bool(cli_result_t &)> start_osd_tree(json11::Json);
std::function<bool(cli_result_t &)> start_pg_list(json11::Json);
std::function<bool(cli_result_t &)> start_pool_create(json11::Json);
@@ -82,11 +89,11 @@ public:
std::function<bool(cli_result_t &)> start_rm_wildcard(json11::Json);
std::function<bool(cli_result_t &)> start_serve(json11::Json);
std::function<bool(cli_result_t &)> start_status(json11::Json);
std::function<bool(cli_result_t &)> start_user_ls(json11::Json);
// Should be called like loop_and_wait(start_status(), <completion callback>)
void loop_and_wait(std::function<bool(cli_result_t &)> loop_cb, std::function<void(const cli_result_t &)> complete_cb);
void etcd_call(const std::string & api, json11::Json body);
void etcd_txn(json11::Json txn);
void iterate_kvs_1(json11::Json kvs, const std::string & prefix, std::function<void(uint64_t num, json11::Json)> cb);
@@ -103,5 +110,3 @@ std::string format_lat(uint64_t lat);
std::string format_q(double depth);
bool stupid_glob(const std::string str, const std::string glob);
json11::Json::object merge_json_objects(json11::Json::object obj1, const json11::Json::object & obj2);
+23 -14
View File
@@ -6,6 +6,15 @@
#include "cluster_client.h"
#include "cli.h"
bool cli_tool_t::check_image_perm(const inode_config_t & cfg, bool write)
{
return !user ||
user->type == user_type_t::ADMIN ||
user->name == cfg.owner ||
cfg.owner_group != "" && user->groups.find(cfg.owner_group) != user->groups.end() ||
!write && cfg.reader_group != "" && user->groups.find(cfg.reader_group) != user->groups.end();
}
json11::Json::object cli_tool_t::format_image(const inode_config_t & cfg)
{
auto pool_it = cli->st_cli.pool_config.find(INODE_POOL(cfg.num));
@@ -20,6 +29,18 @@ json11::Json::object cli_tool_t::format_image(const inode_config_t & cfg)
{ "readonly", cfg.readonly },
{ "deleted", cfg.deleted },
};
if (cfg.owner != "")
{
img["owner"] = cfg.owner;
}
if (cfg.owner_group != "")
{
img["owner_group"] = cfg.owner_group;
}
if (cfg.reader_group != "")
{
img["reader_group"] = cfg.reader_group;
}
if (!cfg.enc_key.empty())
{
img["encrypted"] = true;
@@ -109,10 +130,10 @@ void cli_tool_t::change_parent(inode_t cur, inode_t new_parent, cli_result_t *re
});
}
void cli_tool_t::etcd_call(const std::string & api, json11::Json body)
void cli_tool_t::etcd_txn(json11::Json txn)
{
waiting++;
cli->st_cli.etcd_call(api, body, cli->st_cli.etcd_slow_timeout, cli->st_cli.max_etcd_attempts, 0, [this](std::string err, json11::Json res)
cli->st_cli.etcd_txn_slow(txn, [this](std::string err, json11::Json res)
{
waiting--;
if (err != "")
@@ -124,11 +145,6 @@ void cli_tool_t::etcd_call(const std::string & api, json11::Json body)
});
}
void cli_tool_t::etcd_txn(json11::Json txn)
{
etcd_call("/kv/txn", txn);
}
inode_config_t* cli_tool_t::get_inode_cfg(const std::string & name)
{
for (auto & ic: cli->st_cli.inode_config)
@@ -249,10 +265,3 @@ void cli_tool_t::iterate_kvs_2(json11::Json kvs, const std::string & prefix, std
cb(pool_id, num, kv.value);
}
}
json11::Json::object merge_json_objects(json11::Json::object obj1, const json11::Json::object & obj2)
{
for (auto & kv: obj2)
obj1[kv.first] = kv.second;
return obj1;
}
+58 -18
View File
@@ -1,12 +1,11 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#ifdef WITH_OPENSSL
#include <openssl/rand.h>
#endif
#include <ctype.h>
#include "cli.h"
#include "http_client.h"
#include "cluster_client.h"
#include "str_util.h"
@@ -35,6 +34,7 @@ struct image_creator_t
bool force_size = false;
std::string enc_key;
bool set_key = false;
std::string new_owner, new_owner_group, new_reader_group;
pool_id_t old_pool_id = 0;
inode_t new_parent_id = 0;
@@ -119,6 +119,23 @@ struct image_creator_t
create_snapshot();
}
bool check_pool_permission()
{
if (!parent->user || parent->user->type == user_type_t::ADMIN)
{
return true;
}
auto pool_it = parent->cli->st_cli.pool_config.find(new_pool_id);
if (pool_it == parent->cli->st_cli.pool_config.end() ||
(pool_it->second.creator_group == "" || parent->user->groups.find(pool_it->second.creator_group) == parent->user->groups.end()))
{
result = (cli_result_t){ .err = EACCES, .text = "Pool image create permission denied" };
state = 100;
return false;
}
return true;
}
void create_image()
{
if (state == 2)
@@ -158,6 +175,10 @@ struct image_creator_t
state = 100;
return;
}
if (!check_pool_permission())
{
return;
}
if (!size && !force_size)
{
result = (cli_result_t){ .err = EINVAL, .text = "Image size is missing" };
@@ -249,11 +270,22 @@ resume_3:
state = 100;
return;
}
if (!parent->check_image_perm(cur_cfg, true))
{
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
state = 100;
return;
}
if (!new_pool_id)
{
// Create snapshot in the same pool by default
new_pool_id = old_pool_id;
}
// Verify pool permissions if the pool is different from the original
if (new_pool_id != old_pool_id && !check_pool_permission())
{
return;
}
attempt_create();
state = 4;
resume_4:
@@ -442,6 +474,19 @@ resume_3:
{
new_cfg.enc_key = cur_cfg.enc_key;
}
new_cfg.owner = http_context_get_ssl_cn(parent->cli->st_cli.get_http_ctx());
if (!new_owner.empty())
{
new_cfg.owner = new_owner;
}
if (!new_owner_group.empty())
{
new_cfg.owner_group = new_owner_group;
}
if (!new_reader_group.empty())
{
new_cfg.reader_group = new_reader_group;
}
json11::Json::array checks = json11::Json::array {
json11::Json::object {
{ "target", "VERSION" },
@@ -480,13 +525,10 @@ resume_3:
json11::Json::object {
{ "request_put", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name) },
{ "value", base64_encode(json11::Json(merge_json_objects(
parent->cli->st_cli.serialize_inode_cfg(&new_cfg),
json11::Json::object{
{ "id", new_id },
{ "pool_id", (uint64_t)new_pool_id },
}
)).dump()) },
{ "value", base64_encode(json11::Json(json11::Json::object{
{ "id", new_id },
{ "pool_id", (uint64_t)new_pool_id },
}).dump()) },
} },
},
json11::Json::object {
@@ -541,13 +583,10 @@ resume_3:
success.push_back(json11::Json::object {
{ "request_put", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name+"@"+new_snap) },
{ "value", base64_encode(json11::Json(merge_json_objects(
parent->cli->st_cli.serialize_inode_cfg(&snap_cfg),
json11::Json::object{
{ "id", old_id },
{ "pool_id", (uint64_t)old_pool_id },
}
)).dump()) },
{ "value", base64_encode(json11::Json(json11::Json::object{
{ "id", old_id },
{ "pool_id", (uint64_t)old_pool_id },
}).dump()) },
} },
});
};
@@ -587,14 +626,12 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_create(json11::Json cfg)
if (!cfg["enc_key"].is_null())
{
image_creator->set_key = true;
#ifdef WITH_OPENSSL
if (image_creator->enc_key == "random")
{
uint8_t newkey[64];
RAND_bytes(newkey, 64);
image_creator->enc_key = tohexstr(newkey, 64);
}
#endif
else
{
image_creator->enc_key = cfg["enc_key"].string_value();
@@ -610,6 +647,9 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_create(json11::Json cfg)
}
}
}
image_creator->new_owner = cfg["owner"].string_value();
image_creator->new_owner_group = cfg["owner_group"].string_value();
image_creator->new_reader_group = cfg["reader_group"].string_value();
image_creator->new_parent = cfg["parent"].string_value();
if (!cfg["size"].is_null())
{
-393
View File
@@ -1,393 +0,0 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include "cli.h"
#include "cluster_client.h"
#include "str_util.h"
#include "json_util.h"
struct etcd_perm_t
{
std::string key;
std::string range_end;
std::string perm_type;
};
class etcd_user_sync_t
{
protected:
int state = 0;
std::vector<etcd_perm_t> new_perms;
int i = 0;
std::map<std::pair<std::string, std::string>, std::string> old_perms;
std::map<std::pair<std::string, std::string>, std::string>::iterator old_perm_it;
public:
cli_tool_t *parent = NULL;
std::string role_name;
std::vector<etcd_perm_t> perms;
cli_result_t result;
// returns boolean true when done
bool run()
{
if (state == 1)
goto resume_1;
else if (state == 2)
goto resume_2;
else if (state == 3)
goto resume_3;
else if (state == 4)
goto resume_4;
else if (state == 5)
goto resume_5;
else if (state == 6)
goto resume_6;
else if (state == 100)
return true;
parent->etcd_call("/auth/user/get", json11::Json::object{
{ "name", role_name },
});
resume_1:
state = 1;
if (parent->waiting > 0)
return false;
// etcd returns HTTP/1.1 400 Bad Request + {"error":"etcdserver: user name not found","code":9}
// FIXME Check if it works
if (parent->etcd_err.text.find("user name not found") != std::string::npos)
{
parent->etcd_call("/auth/user/add", json11::Json::object{
{ "name", role_name },
{ "options", json11::Json::object{
{ "no_password", true }
} },
});
resume_2:
state = 2;
if (parent->waiting > 0)
return false;
// etcd returns HTTP/1.1 400 Bad Request + {"error":"etcdserver: role name already exists","code":9}
if (parent->etcd_err.err && parent->etcd_err.text.find("role name already exists") == std::string::npos)
{
result = parent->etcd_err;
return true;
}
}
parent->etcd_call("/auth/role/get", json11::Json::object{
{ "role", role_name },
});
resume_3:
state = 3;
if (parent->waiting > 0)
return false;
// etcd returns HTTP/1.1 400 Bad Request + {"error":"etcdserver: role name not found","code":9}
if (parent->etcd_err.text.find("role name not found") != std::string::npos)
{
parent->etcd_call("/auth/role/add", json11::Json::object{
{ "name", role_name },
});
resume_4:
state = 4;
if (parent->waiting > 0)
return false;
// etcd returns HTTP/1.1 400 Bad Request + {"error":"etcdserver: role name already exists","code":9}
if (parent->etcd_err.err && parent->etcd_err.text.find("role name already exists") == std::string::npos)
{
state = 100;
result = parent->etcd_err;
return true;
}
}
else if (parent->etcd_err.err)
{
state = 100;
result = parent->etcd_err;
return true;
}
for (auto & old_perm: parent->etcd_result["perm"].array_items())
{
old_perms.emplace(std::make_pair(old_perm["key"].string_value(), old_perm["range_end"].string_value()), old_perm["perm_type"].string_value());
}
for (auto & perm: perms)
{
auto perm_key = std::make_pair(
base64_encode(parent->cli->st_cli.etcd_prefix+perm.key),
perm.range_end.empty() ? "" : base64_encode(parent->cli->st_cli.etcd_prefix+perm.range_end)
);
auto perm_it = old_perms.find(perm_key);
if (perm_it == old_perms.end() || perm_it->second != perm.perm_type)
new_perms.push_back(perm);
else
old_perms.erase(perm_it);
}
for (old_perm_it = old_perms.begin(); old_perm_it != old_perms.end(); old_perm_it++)
{
parent->etcd_call("/auth/role/revoke", json11::Json::object{
{ "role", role_name },
{ "key", old_perm_it->first.first },
{ "range_end", old_perm_it->first.second },
});
resume_5:
state = 5;
if (parent->waiting > 0)
return false;
if (parent->etcd_err.err)
{
state = 100;
result = parent->etcd_err;
return true;
}
}
for (i = 0; i < new_perms.size(); i++)
{
{
auto & perm = new_perms[i];
json11::Json::object obj;
obj["key"] = base64_encode(parent->cli->st_cli.etcd_prefix+perm.key);
if (!perm.range_end.empty())
obj["range_end"] = base64_encode(parent->cli->st_cli.etcd_prefix+perm.range_end);
obj["perm_type"] = perm.perm_type;
parent->etcd_call("/auth/role/grant", json11::Json::object{
{ "name", role_name },
{ "perm", obj },
});
}
resume_6:
state = 6;
if (parent->waiting > 0)
return false;
if (parent->etcd_err.err)
{
state = 100;
result = parent->etcd_err;
return true;
}
}
state = 100;
result = {};
return true;
}
};
// Create/update a user
struct cli_create_user_t
{
cli_tool_t *parent;
std::string user_name;
std::string user_type;
json11::Json grants;
etcd_user_sync_t *user_sync = NULL;
int state = 0;
cli_result_t result;
bool is_done()
{
return state == 100;
}
void loop()
{
if (state == 1)
goto resume_1;
else if (state == 2)
goto resume_2;
user_sync = new etcd_user_sync_t();
user_sync->parent = parent;
user_sync->role_name = user_name;
if (user_type == "osd")
{
user_sync->perms = {
{ "/config/", "/config0", "READ" },
{ "/osd/", "/osd0", "READWRITE" },
{ "/pg/config", "", "READ" },
{ "/pg/state/", "/pg/state0", "READWRITE" },
{ "/pg/history/", "/pg/history0", "READWRITE" },
{ "/pgstats/", "/pgstats0", "READWRITE" },
};
}
else if (user_type == "mon")
{
user_sync->perms = {
{ "/config/", "/config0", "READ" },
{ "/osd/", "/osd0", "READ" },
{ "/mon/", "/mon0", "READWRITE" },
{ "/pg/config", "", "READWRITE" },
{ "/pg/history/", "/pg/history0", "READWRITE" },
{ "/pgstats/", "/pgstats0", "READ" },
{ "/inode/stats/", "/inode/stats0", "READWRITE" },
{ "/pool/stats/", "/pool/stats0", "READWRITE" },
{ "/stats/", "/stats0", "READWRITE" },
{ "/history/last_clean_pgs", "", "READWRITE" },
{ "/index/", "/index0", "READ" },
};
}
else if (user_type == "admin")
{
// CLI admin + global I/O client
user_sync->perms = {
{ "/config/", "/config0", "READWRITE" },
{ "/osd", "/osd0", "READWRITE" },
{ "/mon/", "/mon0", "READ" },
{ "/pg/", "/pg0", "READ" },
{ "/pg/history/", "/pg/history0", "READWRITE" },
{ "/pgstats/", "/pgstats0", "READ" },
{ "/inode/stats/", "/inode/stats0", "READ" },
{ "/pool/stats/", "/pool/stats0", "READ" },
{ "/stats/", "/stats0", "READ" },
{ "/index/", "/index0", "READWRITE" },
};
}
else if (user_type == "client")
{
// I/O client without etcd write permissions
user_sync->perms = {
{ "/config/global", "", "READ" },
{ "/config/node_placement", "", "READ" },
{ "/config/pools", "", "READ" },
{ "/osd/state", "/osd/state0", "READ" },
{ "/pg/config", "", "READ" },
{ "/pg/state/", "/pg/state0", "READ" },
//{ "/index/image/<name>", "", "READ" },
};
{
std::string error;
grants = parse_grants(grants, error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
for (auto & grant: grants.array_items())
{
user_sync->perms.push_back({
"/index/image/"+grant["name"].string_value()+"/",
grant["prefix"].bool_value() ? "/index/image/"+grant["name"].string_value()+"0" : "",
"READ"
});
}
// Also write client's image permissions to etcd, for future vitastor-cli API support
parent->etcd_txn(json11::Json::object {
{ "success", json11::Json::array { json11::Json::object {
{ "request_put", json11::Json::object {
{ "key", base64_encode(
parent->cli->st_cli.etcd_prefix+"/config/user/"+user_name
) },
{ "value", base64_encode(
json11::Json(json11::Json::object{
{ "type", user_type },
{ "grants", grants },
}).dump()
) },
} },
} } }
});
state = 2;
resume_2:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
}
else
{
result = (cli_result_t){ .err = EINVAL, .text = "Invalid user type: "+user_type };
state = 100;
return;
}
resume_1:
state = 1;
while (!user_sync->run())
{
return;
}
if (user_sync->result.err)
{
result = user_sync->result;
state = 100;
return;
}
delete user_sync;
user_sync = NULL;
state = 100;
}
json11::Json::array parse_grants(json11::Json grants, std::string & error)
{
json11::Json::array parsed;
if (grants.is_string())
{
// [rwx]:[^ ]+*( +[rwx]:name*)
grants = explode(" ", grants.string_value(), true);
}
for (auto & grant: grants.array_items())
{
if (grant.is_string())
{
const auto & str = grant.string_value();
if (str.size() < 3 || str[0] != 'r' && str[0] != 'w' && str[0] != 'x' || str[1] != ':')
{
error = "Invalid image permission: "+str;
return json11::Json::array();
}
else
{
parsed.push_back(json11::Json::object{
{ "type", str[0] == 'r' ? "read" : (str[0] == 'w' ? "write" : "admin") },
{ "name", str.substr(2, str.size() - (str[str.size()-1] == '*' ? 3 : 2)) },
{ "prefix", str[str.size()-1] == '*' },
});
}
}
else if (!grant.is_object())
{
error = "Image permissions should be strings or objects";
return json11::Json::array();
}
else
{
const auto & obj = grant.object_items();
for (auto & kv: obj)
{
if (!(kv.first == "type" && (kv.second == "read" || kv.second == "write" || kv.second == "admin") ||
kv.first == "name" && kv.second.is_string() ||
kv.first == "prefix" && kv.second.is_bool()))
{
error = "Invalid image permission: "+grant.dump();
return json11::Json::array();
}
}
parsed.push_back(grant);
}
}
return parsed;
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_create_user(json11::Json cfg)
{
auto creator = new cli_create_user_t();
creator->parent = this;
creator->user_type = cfg["type"].string_value();
creator->user_name = cfg["name"].string_value();
creator->grants = cfg["grants"];
return [creator](cli_result_t & result)
{
creator->loop();
if (creator->is_done())
{
result = creator->result;
delete creator;
return true;
}
return false;
};
}
+6
View File
@@ -35,6 +35,12 @@ struct snap_flattener_t
state = 100;
return;
}
if (!parent->check_image_perm(*target_cfg, true))
{
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
state = 100;
return;
}
target_id = target_cfg->num;
std::vector<inode_t> chain_list;
inode_config_t *cur = target_cfg;
+15 -4
View File
@@ -53,13 +53,19 @@ struct image_lister_t
return;
}
}
for (auto & ic: parent->cli->st_cli.inode_config)
auto begin_it = list_pool_id
? parent->cli->st_cli.inode_config.lower_bound(INODE_WITH_POOL(list_pool_id, 0))
: parent->cli->st_cli.inode_config.begin();
auto end_it = list_pool_id
? parent->cli->st_cli.inode_config.lower_bound(INODE_WITH_POOL(list_pool_id+1, 0))
: parent->cli->st_cli.inode_config.end();
for (auto it = begin_it; it != end_it; it++)
{
if (list_pool_id && INODE_POOL(ic.second.num) != list_pool_id)
if (!parent->check_image_perm(it->second, false))
{
continue;
}
stats[ic.second.num] = parent->format_image(ic.second);
stats[it->second.num] = parent->format_image(it->second);
}
}
@@ -106,6 +112,7 @@ resume_1:
state = 100;
return;
}
// FIXME: Do not always read everything
space_info = parent->etcd_result;
std::map<pool_id_t, uint64_t> pool_pg_real_size;
for (auto & kv_item: space_info["responses"][0]["response_range"]["kvs"].array_items())
@@ -139,6 +146,11 @@ resume_1:
}
inode_t inode_num = INODE_WITH_POOL(pool_id, only_inode_num);
uint64_t used_size = kv.value["raw_used"].uint64_value();
auto stat_it = stats.find(inode_num);
if (parent->user && parent->user->type != user_type_t::ADMIN && stat_it == stats.end())
{
continue;
}
// save stats
auto pool_it = parent->cli->st_cli.pool_config.find(pool_id);
if (pool_it != parent->cli->st_cli.pool_config.end())
@@ -147,7 +159,6 @@ resume_1:
used_size = used_size / (pool_pg_real_size[pool_id] ? pool_pg_real_size[pool_id] : 1)
* (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
}
auto stat_it = stats.find(inode_num);
if (stat_it == stats.end())
{
stats[inode_num] = json11::Json::object {
+38 -21
View File
@@ -19,6 +19,7 @@ struct image_changer_t
bool set_deleted = false, new_deleted = false;
bool set_key = false;
std::string enc_key;
json11::Json new_owner, new_owner_group, new_reader_group;
bool down_ok = false;
// interval between fsyncs
int fsync_interval = 128;
@@ -76,6 +77,12 @@ struct image_changer_t
state = 100;
return;
}
if (!parent->check_image_perm(cfg, true))
{
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
state = 100;
return;
}
for (auto & ic: parent->cli->st_cli.inode_config)
{
if (ic.second.parent_id == inode_num)
@@ -151,6 +158,18 @@ resume_1:
{
cfg.name = new_name;
}
if (new_owner.is_string())
{
cfg.owner = new_owner.string_value();
}
if (new_owner_group.is_string())
{
cfg.owner_group = new_owner_group.string_value();
}
if (new_reader_group.is_string())
{
cfg.reader_group = new_reader_group.string_value();
}
if (set_key)
{
if (!force)
@@ -188,6 +207,7 @@ resume_1:
} }
});
}
if (new_name != "")
{
std::string old_idx_key = base64_encode(
parent->cli->st_cli.etcd_prefix+"/index/image/"+image_name
@@ -201,29 +221,23 @@ resume_1:
{ "result", "LESS" },
{ "mod_revision", cfg.mod_revision+1 },
});
if (new_name != "")
{
checks.push_back(json11::Json::object {
{ "target", "VERSION" },
{ "version", 0 },
{ "key", new_idx_key },
});
success.push_back(json11::Json::object {
{ "request_delete_range", json11::Json::object {
{ "key", old_idx_key },
} }
});
}
checks.push_back(json11::Json::object {
{ "target", "VERSION" },
{ "version", 0 },
{ "key", new_idx_key },
});
success.push_back(json11::Json::object {
{ "request_delete_range", json11::Json::object {
{ "key", old_idx_key },
} }
});
success.push_back(json11::Json::object {
{ "request_put", json11::Json::object {
{ "key", new_name.empty() ? old_idx_key : new_idx_key },
{ "value", base64_encode(json11::Json(merge_json_objects(
parent->cli->st_cli.serialize_inode_cfg(&cfg),
json11::Json::object{
{ "id", INODE_NO_POOL(inode_num) },
{ "pool_id", (uint64_t)INODE_POOL(inode_num) },
}
)).dump()) },
{ "key", new_idx_key },
{ "value", base64_encode(json11::Json(json11::Json::object{
{ "id", INODE_NO_POOL(inode_num) },
{ "pool_id", (uint64_t)INODE_POOL(inode_num) },
}).dump()) },
} }
});
}
@@ -283,6 +297,9 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_modify(json11::Json cfg)
if (!changer->fsync_interval)
changer->fsync_interval = 128;
changer->down_ok = cfg["down_ok"].bool_value();
changer->new_owner = cfg["owner"];
changer->new_owner_group = cfg["owner_group"];
changer->new_reader_group = cfg["reader_group"];
// FIXME Check that the image doesn't have children when shrinking
return [changer](cli_result_t & result)
{
+161
View File
@@ -0,0 +1,161 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include "cli.h"
#include "cluster_client.h"
#include "str_util.h"
// Create/update/delete a user
struct cli_modify_user_t
{
cli_tool_t *parent;
std::string user_name;
std::string user_type;
json11::Json groups;
bool del = false;
int state = 0;
cli_result_t result;
etcd_kv_t kv;
json11::Json::object new_cfg;
bool is_done()
{
return state == 100;
}
void loop()
{
if (state == 1)
goto resume_1;
else if (state == 2)
goto resume_2;
if (user_type != "client" && user_type != "admin" && user_type != "mon" && user_type != "osd")
{
result = (cli_result_t){ .err = EINVAL, .text = "Unknown user type: "+user_type };
state = 100;
return;
}
if (groups.is_string())
{
groups = groups == "" ? std::vector<std::string>() : explode(",", groups.string_value(), true);
}
else if (groups.is_array())
{
for (auto & gr: groups.array_items())
{
if (!gr.is_string())
{
result = (cli_result_t){ .err = EINVAL, .text = "Group names must be strings" };
state = 100;
return;
}
}
}
else if (!groups.is_null())
{
result = (cli_result_t){ .err = EINVAL, .text = "Group names must be strings" };
state = 100;
return;
}
if (user_name == "")
{
result = (cli_result_t){ .err = EINVAL, .text = "User name must not be empty" };
state = 100;
return;
}
parent->etcd_txn(json11::Json::object {
{ "success", json11::Json::array { json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/user/"+user_name) },
} },
} } }
});
state = 1;
resume_1:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
kv = parent->cli->st_cli.parse_etcd_kv(parent->etcd_result["responses"][0]["response_range"]["kvs"][0]);
while (true)
{
new_cfg = kv.value.object_items();
if (!kv.mod_revision && del)
{
result = (cli_result_t){ .err = ENOENT, .text = "User "+user_name+" does not exist" };
state = 100;
break;
}
if (!groups.is_null())
new_cfg["groups"] = groups;
if (user_type != "")
new_cfg["type"] = user_type;
if (!new_cfg["type"].is_string())
new_cfg["type"] = "client";
parent->etcd_txn(json11::Json::object {
{ "compare", json11::Json::array { json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/user/"+user_name) },
{ "target", kv.mod_revision ? "MOD" : "VERSION" },
{ kv.mod_revision ? "mod_revision" : "version", kv.mod_revision },
} } },
{ "success", json11::Json::array {
del ? json11::Json::object { { "request_delete_range", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/user/"+user_name) },
} } } : json11::Json::object { { "request_put", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/user/"+user_name) },
{ "value", base64_encode(json11::Json(new_cfg).dump()) }
} } },
} },
{ "failure", json11::Json::array { json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/user/"+user_name) },
} },
} } },
});
state = 2;
resume_2:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
if (parent->etcd_result["succeeded"].bool_value())
break;
kv = parent->cli->st_cli.parse_etcd_kv(parent->etcd_result["responses"][0]["response_range"]["kvs"][0]);
}
state = 100;
result.text = del ? "User "+user_name+" removed" : "User "+user_name+" modified";
new_cfg["name"] = user_name;
result.data = del ? json11::Json::object{ { "ok", true } } : new_cfg;
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_modify_user(json11::Json cfg)
{
auto creator = new cli_modify_user_t();
creator->parent = this;
creator->user_name = cfg["name"].string_value();
creator->user_type = cfg["type"].string_value();
creator->groups = cfg["groups"];
creator->del = cfg["remove"].bool_value();
return [creator](cli_result_t & result)
{
creator->loop();
if (creator->is_done())
{
result = creator->result;
delete creator;
return true;
}
return false;
};
}
+1 -1
View File
@@ -91,7 +91,7 @@ std::string validate_pool_config(json11::Json::object & new_cfg, json11::Json ol
}
else if (key == "name" || key == "scheme" || key == "immediate_commit" ||
key == "failure_domain" || key == "root_node" || key == "scrub_interval" || key == "used_for_app" ||
key == "used_for_fs" || key == "raw_placement" || key == "local_reads")
key == "used_for_fs" || key == "raw_placement" || key == "local_reads" || key == "creator_group")
{
if (!value.is_string())
{
+17 -7
View File
@@ -269,10 +269,17 @@ resume_100:
{
char buf[1024];
snprintf(buf, 1024, "Parent inode of layer %s (id 0x%jx) not found", cur->name.c_str(), cur->parent_id);
result = (cli_result_t){ .err = ENOENT, .text = buf };
state = 100;
return;
}
cur = &it->second;
if (!parent->check_image_perm(*cur, true))
{
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
state = 100;
return;
}
chain_list.push_back(cur->num);
}
if (cur->num != from_cfg->num)
@@ -297,6 +304,12 @@ resume_100:
auto it = sources.find(ic.second.parent_id);
if (it != sources.end() && sources.find(ic.second.num) == sources.end())
{
if (!parent->check_image_perm(ic.second, true))
{
result = (cli_result_t){ .err = EACCES, .text = "Image permission denied" };
state = 100;
return;
}
merge_children.push_back(ic.second.num);
if (ic.second.readonly || writers_stopped)
{
@@ -487,13 +500,10 @@ resume_100:
json11::Json::object {
{ "request_put", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/index/image/"+child_cfg->name) },
{ "value", base64_encode(json11::Json(merge_json_objects(
parent->cli->st_cli.serialize_inode_cfg(&new_cfg),
json11::Json::object{
{ "id", INODE_NO_POOL(inverse_parent) },
{ "pool_id", (uint64_t)INODE_POOL(inverse_parent) },
}
)).dump()) },
{ "value", base64_encode(json11::Json({
{ "id", INODE_NO_POOL(inverse_parent) },
{ "pool_id", (uint64_t)INODE_POOL(inverse_parent) },
}).dump()) },
} },
},
};
+2
View File
@@ -286,6 +286,8 @@ struct rm_inode_t
.data = data,
};
}
// Wakeup callers (otherwise they wake up only on next loop())
parent->ringloop->wakeup();
}
in_continue = false;
}
+90 -30
View File
@@ -35,31 +35,35 @@ struct cli_serve_path_t
{
std::string cmd;
bool allow_get;
bool allow_client;
};
// Serve vitastor-cli commands over HTTP in JSON format
struct cli_serve_t
{
std::map<std::string, cli_serve_path_t> cmd_paths = {
{"data/delete", {"rm-data", false}},
{"data/describe", {"describe", true}},
{"data/fix", {"fix", false}},
{"data/merge", {"merge-data", false}},
{"image/create", {"create", false}},
{"image/delete", {"rm", false}},
{"image/flatten", {"flatten", false}},
{"image/list", {"ls", true}},
{"image/modify", {"modify", false}},
{"osd/alloc", {"alloc-osd", false}},
{"osd/delete", {"rm-osd", false}},
{"osd/list", {"ls-osd", true}},
{"osd/modify", {"modify-osd", false}},
{"pg/list", {"ls-pgs", true}},
{"pool/create", {"create-pool", false}},
{"pool/delete", {"rm-pool", false}},
{"pool/list", {"pools", true}},
{"pool/modify", {"modify-pool", false}},
{"status", {"status", true}},
{"data/delete", {"rm-data", false, false}},
{"data/describe", {"describe", true, false}},
{"data/fix", {"fix", false, false}},
{"data/merge", {"merge-data", false, false}},
{"image/create", {"create", false, true}},
{"image/delete", {"rm", false, true}},
{"image/flatten", {"flatten", false, true}},
{"image/list", {"ls", true, true}},
{"image/modify", {"modify", false, true}},
{"osd/alloc", {"alloc-osd", false, false}},
{"osd/delete", {"rm-osd", false, false}},
{"osd/list", {"ls-osd", true, false}},
{"osd/modify", {"modify-osd", false, false}},
{"pg/list", {"ls-pgs", true, false}},
{"pool/create", {"create-pool", false, false}},
{"pool/delete", {"rm-pool", false, false}},
{"pool/list", {"pools", true, false}},
{"pool/modify", {"modify-pool", false, false}},
{"user/delete", {"remove-user", false, false}},
{"user/list", {"ls-user", false, false}},
{"user/modify", {"modify-user", false, false}},
{"status", {"status", true, false}},
};
cli_tool_t *parent = NULL;
@@ -72,6 +76,7 @@ struct cli_serve_t
int port = 0;
int listen_backlog = 0;
bool ssl = false;
bool use_auth = false;
std::vector<int> listen_fds;
http_context_t *http_ctx = NULL;
std::set<cli_serve_conn_t*> connections;
@@ -107,19 +112,34 @@ struct cli_serve_t
listen_backlog = options["listen_backlog"].uint64_value();
if (!listen_backlog)
listen_backlog = 128;
ssl = json_is_true(options["ssl"]);
if (ssl)
{
std::string ssl_cert = options["ssl_cert"].string_value();
std::string ssl_key = options["ssl_key"].string_value();
std::string ssl_ca = options["ssl_ca"].string_value();
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, ssl_cert, ssl_key, ssl_ca, ssl_ca != "", error);
if (error != "")
std::string tls_cert = (parent->cli->config.find("server_cert") != parent->cli->config.end()
? parent->cli->config["server_cert"].string_value() : "");
std::string tls_key = (parent->cli->config.find("server_key") != parent->cli->config.end()
? parent->cli->config["server_key"].string_value() : "");
std::string tls_ca = (parent->cli->config.find("client_ca") != parent->cli->config.end()
? parent->cli->config["client_ca"].string_value() : "");
if (tls_cert != "" || tls_key != "" || tls_ca != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
ssl = true;
if (tls_cert == "" || tls_key == "")
{
result = (cli_result_t){ .err = EINVAL, .text = "server_cert and server_key are required to serve HTTPS" };
state = 100;
return;
}
// use_auth is enabled by default when client_ca is set
use_auth = (parent->cli->config["use_auth"].is_null()
? (tls_ca != "")
: json_is_true(parent->cli->config["use_auth"]));
std::string error;
http_ctx = http_context_init(parent->epmgr->tfd, tls_cert, tls_key, tls_ca, tls_ca != "", error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
}
for (auto & bind_address: bind_addresses)
@@ -200,6 +220,12 @@ struct cli_serve_t
if (text)
*text = "Bad Request";
}
else if (err == EACCES)
{
code = 403;
if (text)
*text = "Forbidden";
}
else if (err == EOPNOTSUPP)
{
code = 404;
@@ -334,6 +360,10 @@ struct cli_serve_t
conn->request_path = std::move(req_line[1]);
conn->request_body = std::move(msg->body);
conn->response_type = "";
if (use_auth)
{
conn->p->user = parent->cli->st_cli.get_user(msg->headers["_tls_common_name"]);
}
auto ctype = msg->headers["content-type"];
if (conn->request_method != "GET" && conn->request_method != "POST")
{
@@ -361,6 +391,32 @@ struct cli_serve_t
{
conn->response_type = "application/json";
conn->result = { .text = openapi_description };
if (use_auth)
{
// Filter available paths by privileges
if (conn->p->user->type == user_type_t::CLIENT)
{
std::string error;
auto openapi = json11::Json::parse(openapi_description, error).object_items();
json11::Json::object paths;
for (auto & kv: openapi["paths"].object_items())
{
auto cmd_it = cmd_paths.find(kv.first.substr(1));
if (cmd_it != cmd_paths.end() && cmd_it->second.allow_client)
{
paths[kv.first] = kv.second;
}
}
openapi["paths"] = paths;
conn->response_type = "application/json";
conn->result = { .text = json11::Json(openapi).dump() };
}
else if (conn->p->user->type != user_type_t::ADMIN)
{
conn->response_type = "";
conn->result = { .err = EACCES, .text = "Access denied" };
}
}
}
else if (cmd_it == cmd_paths.end())
{
@@ -370,6 +426,10 @@ struct cli_serve_t
{
conn->result = { .err = ENOSYS, .text = "method /"+uri[0]+" only allows POST requests" };
}
else if (use_auth && conn->p->user->type == user_type_t::CLIENT && !cmd_it->second.allow_client)
{
conn->result = { .err = EACCES, .text = "Access denied" };
}
else
{
std::string error;
+128
View File
@@ -0,0 +1,128 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include <algorithm>
#include "cli.h"
#include "cluster_client.h"
#include "str_util.h"
#include "json_util.h"
// List users
struct user_lister_t
{
cli_tool_t *parent;
std::vector<std::string> only_names;
int state = 0;
cli_result_t result;
json11::Json::array users;
bool is_done()
{
return state == 100;
}
void loop()
{
if (state == 1)
goto resume_1;
if (state == 100)
return;
{
json11::Json::array select;
if (!only_names.size())
{
select.push_back(json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(
parent->cli->st_cli.etcd_prefix+"/config/user/"
) },
{ "range_end", base64_encode(
parent->cli->st_cli.etcd_prefix+"/config/user0"
) },
} },
});
}
else
{
for (auto & name: only_names)
{
select.push_back(json11::Json::object {
{ "request_range", json11::Json::object {
{ "key", base64_encode(parent->cli->st_cli.etcd_prefix+"/config/user/"+name) },
} }
});
}
}
parent->etcd_txn(json11::Json::object {
{ "success", select },
});
}
state = 1;
resume_1:
if (parent->waiting > 0)
return;
if (parent->etcd_err.err)
{
result = parent->etcd_err;
state = 100;
return;
}
for (auto & response: parent->etcd_result["responses"].array_items())
{
for (auto & kv_item: response["response_range"]["kvs"].array_items())
{
auto kv = parent->cli->st_cli.parse_etcd_kv(kv_item);
auto user = kv.value.object_items();
user["name"] = kv.key.substr(parent->cli->st_cli.etcd_prefix.size()+13);
if (!parent->json_output)
user["groups_fmt"] = implode(",", user["groups"].array_items());
users.push_back(std::move(user));
}
}
if (parent->json_output)
{
// JSON output
result.data = users;
state = 100;
return;
}
// Table output: name, type, groups
json11::Json::array cols;
cols.push_back(json11::Json::object{
{ "key", "name" },
{ "title", "NAME" },
});
cols.push_back(json11::Json::object{
{ "key", "type" },
{ "title", "TYPE" },
});
cols.push_back(json11::Json::object{
{ "key", "groups_fmt" },
{ "title", "GROUPS" },
});
result.text = print_table(users, cols, parent->color);
state = 100;
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_user_ls(json11::Json cfg)
{
auto lister = new user_lister_t();
lister->parent = this;
if (cfg["names"].is_string())
lister->only_names.push_back(cfg["names"].string_value());
for (auto & item: cfg["names"].array_items())
lister->only_names.push_back(item.string_value());
return [lister](cli_result_t & result)
{
lister->loop();
if (lister->is_done())
{
result = lister->result;
delete lister;
return true;
}
return false;
};
}
+100 -3
View File
@@ -108,12 +108,16 @@
{ "type": "string", "enum": [ "", "random" ] },
{ "type": "string", "pattern": "^[0-9a-fA-F]{128}$|^vault:" }
]
}
},
"owner": { "type": "string", "description": "Set image owner" },
"owner_group": { "type": "string", "description": "Set image owner group" },
"reader_group": { "type": "string", "description": "Set image reader group" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
@@ -146,6 +150,9 @@
{ "type": "string", "pattern": "^[0-9a-fA-F]{128}$|^vault:" }
]
},
"owner": { "type": "string", "description": "Set image owner" },
"owner_group": { "type": "string", "description": "Set image owner group" },
"reader_group": { "type": "string", "description": "Set image reader group" },
"force": { "type": "boolean", "description": "Proceed with shrinking or setting readwrite flag even if the image has children" },
"down_ok": { "type": "boolean", "description": "Proceed with shrinking even if some data will be left on unavailable OSDs" }
}
@@ -153,6 +160,7 @@
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
@@ -202,6 +210,7 @@
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
@@ -221,6 +230,7 @@
"responses": {
"200": { "content": { "text/plain": { "schema": { "type": "string", "description": "Empty response" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
@@ -413,7 +423,7 @@
}
} },
"/pool/delete": { "post": {
"summary": "Delete an pool",
"summary": "Delete a pool",
"operationId": "poolDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
@@ -435,6 +445,66 @@
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/user/list": { "get": {
"summary": "List users",
"operationId": "userListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
"schema": { "$ref": "#/components/schemas/UserListParams" },
"style": "form",
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }
}
}, "post": {
"summary": "List users",
"operationId": "userListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserList" } } } }
}
} },
"/user/modify": { "post": {
"summary": "Modify a user",
"operationId": "userModify",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"$ref": "#/components/schemas/User"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/user/delete": { "post": {
"summary": "Delete a user",
"operationId": "userDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "User name to delete" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": { "ok": { "type": "boolean" } }
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/pg/list": { "get": {
"summary": "List PGs",
"operationId": "pgListGet",
@@ -605,6 +675,14 @@
}
}
},
"Forbidden": {
"description": "Access Denied (EACCES)",
"content": {
"text/plain": {
"schema": { "type": "string", "description": "Error text" }
}
}
},
"PreconditionFailed": {
"description": "Precondition Failed (ENOTEMPTY, EEXIST or ENOENT)",
"content": {
@@ -812,7 +890,8 @@
"scrub_interval": { "type": "string", "pattern": "^\\d+[smhdMy]$", "description": "Automatic scrub interval" },
"level_placement": { "type": "string", "description": "Additional failure domain rules" },
"raw_placement": { "type": "string", "description": "Raw PG generation rules" },
"max_osd_combinations": { "type": "integer", "format": "uint64", "description": "Maximum number of random combinations during PG generation" }
"max_osd_combinations": { "type": "integer", "format": "uint64", "description": "Maximum number of random combinations during PG generation" },
"creator_group": { "type": "string", "description": "User group allowed to create images in this pool" }
}
},
"PoolList": {
@@ -853,6 +932,24 @@
} } ]
}
},
"UserListParams": {
"type": "object",
"properties": {
"names": { "type": "array", "items": { "type": "string" }, "description": "User names to list" }
}
},
"UserList": {
"type": "array",
"items": { "$ref": "#/components/schemas/User" }
},
"User": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "User name" },
"type": { "type": "string", "enum": [ "osd", "mon", "admin", "client" ], "description": "User type" },
"groups": { "type": "array", "items": { "type": "string" }, "description": "User group names" }
}
},
"PgListParams": {
"type": "object",
"properties": {
+2 -2
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
@@ -7,7 +7,7 @@ add_executable(vitastor-disk
disk_tool.cpp disk_simple_offsets.cpp
disk_tool_discard.cpp disk_tool_journal.cpp disk_tool_meta.cpp disk_tool_prepare.cpp disk_tool_resize.cpp
disk_tool_resize_auto.cpp disk_tool_udev.cpp disk_tool_utils.cpp disk_tool_upgrade.cpp
../util/crc32c.c ../util/xxhash.c ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ../util/rw_blocking.cpp ../util/allocator.cpp ../util/ringloop.cpp
../util/crc32c.c ../util/xxh_x86dispatch.c ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ../util/rw_blocking.cpp ../util/allocator.cpp ../util/ringloop.cpp
../blockstore/blockstore_disk.cpp ../blockstore/blockstore_heap.cpp ../blockstore/multilist.cpp
)
target_link_libraries(vitastor-disk
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
+2 -1
View File
@@ -121,6 +121,7 @@ static const char* help_text =
" --logfile <FILE> log to the specified file\n"
" --enforce 1 enforce permissions at the server side (default is disabled)\n"
" --foreground 1 stay in foreground, do not daemonize\n"
" --trace trace all NFS requests\n"
"\n"
"NFS proxy is stateless if you use immediate_commit=all in your cluster and if\n"
"you do not use client_enable_writeback=true, so you can freely use multiple\n"
@@ -158,7 +159,7 @@ json11::Json::object nfs_proxy_t::parse_args(int narg, const char *args[])
{
const char *opt = args[i]+2;
cfg[str_replace(opt, "-", "_")] = !strcmp(opt, "json") || !strcmp(opt, "block") ||
!strcmp(opt, "dry-run") || !strcmp(opt, "recalc-stats") ||
!strcmp(opt, "dry-run") || !strcmp(opt, "recalc-stats") || !strcmp(opt, "trace") ||
!strcmp(opt, "include-empty") || !strcmp(opt, "no-rm") || i == narg-1 ? "1" : args[++i];
}
else
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
+21 -3
View File
@@ -14,6 +14,7 @@
#include "http_client.h"
#include "str_util.h"
#include "json_util.h"
#include "openssl_util.h"
osd_t::osd_t(const json11::Json & config, ring_loop_t *ringloop)
{
@@ -67,6 +68,11 @@ osd_t::osd_t(const json11::Json & config, ring_loop_t *ringloop)
msgr.repeer_pgs = [this](osd_num_t peer_osd) { repeer_pgs(peer_osd); };
msgr.break_pg_locks = [this](osd_num_t peer_osd) { break_pg_locks(peer_osd); };
msgr.check_config_hook = [this](osd_client_t *cl, json11::Json conf) { return check_peer_config(cl, conf); };
msgr.handshake_hook = [this](osd_client_t *cl)
{
if (!cl->hs_result.peer_is_osd)
cl->user_info = st_cli.get_user(openssl_get_cn(cl->hs_result.peer_cert));
};
msgr.init();
init_cluster();
@@ -170,9 +176,13 @@ void osd_t::parse_config(bool init)
bs->parse_config(bs_cfg);
}
st_cli.parse_config(config);
msgr.parse_config(config);
msgr.parse_config(config, init);
if (init)
{
// use_auth is enabled by default when encryption is enabled
use_auth = (config["use_auth"].is_null()
? msgr.is_encryption_enabled()
: json_is_true(config["use_auth"]));
// Vital Blockstore parameters
bs_block_size = config["block_size"].uint64_value();
if (!bs_block_size)
@@ -181,6 +191,9 @@ void osd_t::parse_config(bool init)
if (!bs_bitmap_granularity)
bs_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
clean_entry_bitmap_size = bs_block_size / bs_bitmap_granularity / 8;
msgr.bs_block_size = bs_block_size;
msgr.clean_entry_bitmap_size = clean_entry_bitmap_size;
msgr.max_write_request_size = MAX_DATA_BLOCK_SIZE; // will be changed after pool config
// immediate_commit
if (config["immediate_commit"] == "all")
immediate_commit = IMMEDIATE_ALL;
@@ -530,6 +543,11 @@ void osd_t::exec_op(osd_op_t *cur_op)
void osd_t::print_stats()
{
if (bs && log_level > 1)
{
printf("[OSD %ju] Live entries: %ju (%ju bytes), garbage entries: %ju (%ju bytes)\n", osd_num,
bs->get_live_entries(), bs->get_live_memory(), bs->get_garbage_entries(), bs->get_garbage_memory());
}
for (int i = OSD_OP_MIN; i <= OSD_OP_MAX; i++)
{
if (msgr.stats.op_stat_count[i] != prev_stats.op_stat_count[i] && i != OSD_OP_PING)
@@ -617,11 +635,11 @@ void osd_t::print_slow()
bufprintf("[OSD %ju] Slow op %jx", osd_num, (uint64_t)op);
if (kv.second->osd_num)
{
bufprintf(" from peer OSD %ju (client %d)", kv.second->osd_num, kv.second->peer_fd);
bufprintf(" from peer OSD %ju (client %ju)", kv.second->osd_num, kv.second->client_id);
}
else
{
bufprintf(" from client %d", kv.second->peer_fd);
bufprintf(" from client %ju", kv.second->client_id);
}
bufprintf(": %s id=%ju", osd_op_names[op->req.hdr.opcode], op->req.hdr.id);
if (op->req.hdr.opcode == OSD_OP_SEC_READ || op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
+4 -1
View File
@@ -30,6 +30,8 @@
#define OSD_RECOVERING 0x10
#define OSD_SCRUBBING 0x20
#define SELF_CLIENT 0
#define MAX_AUTOSYNC_INTERVAL 3600
#define DEFAULT_AUTOSYNC_INTERVAL 5
#define DEFAULT_AUTOSYNC_WRITES 128
@@ -155,6 +157,7 @@ class osd_t
etcd_state_client_t st_cli;
osd_messenger_t msgr;
bool use_auth = false;
int etcd_failed_attempts = 0;
std::string etcd_lease_id;
json11::Json self_state;
@@ -347,7 +350,7 @@ class osd_t
bool check_write_queue(osd_op_t *cur_op, pg_t & pg);
pg_osd_set_state_t* add_object_to_set(pg_t & pg, const object_id oid, const pg_osd_set_t & osd_set,
uint64_t old_pg_state, int log_at_level);
void remove_object_from_state(object_id & oid, pg_osd_set_state_t **object_state, pg_t &pg, bool report = true);
bool remove_object_from_state(object_id & oid, pg_osd_set_state_t **object_state, pg_t &pg, bool report = true);
pg_osd_set_state_t *mark_object(pg_t & pg, object_id oid, pg_osd_set_state_t *prev_object_state, bool ref,
std::function<int(pg_osd_set_t & new_set)> calc_set);
pg_osd_set_state_t *mark_object_corrupted(pg_t & pg, object_id oid, pg_osd_set_state_t *prev_object_state,
+16 -1
View File
@@ -428,6 +428,21 @@ void osd_t::on_change_pool_config_hook()
{
apply_pg_locks_localize_only();
}
msgr.max_write_request_size = 0;
for (auto & pc: st_cli.pool_config)
{
auto & pool_cfg = pc.second;
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint32_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
if (msgr.max_write_request_size < pg_block_size)
{
msgr.max_write_request_size = pg_block_size;
}
}
if (!msgr.max_write_request_size)
{
msgr.max_write_request_size = MAX_DATA_BLOCK_SIZE;
}
}
void osd_t::apply_pg_locks_localize_only()
@@ -991,7 +1006,7 @@ void osd_t::apply_pg_config()
// Add peers
for (auto pg_osd: all_peers)
{
if (pg_osd != this->osd_num && msgr.osd_peer_fds.find(pg_osd) == msgr.osd_peer_fds.end())
if (pg_osd != this->osd_num && msgr.osd_peers.find(pg_osd) == msgr.osd_peers.end())
{
msgr.connect_peer(pg_osd, st_cli.peer_states[pg_osd]);
}

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