Compare commits

...
Author SHA1 Message Date
Vitaliy Filippov 8124ee9eec WIP vitastor-cli create-user 2026-03-20 21:00:32 +03:00
Vitaliy Filippov ccbde3ff91 Duplicate all data in /index/image/ and support reading it from there 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 3a0455877a Add security parameter documentation 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 6c2c7f9d0f Support inline (string PEM) certificates and pkeys 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 11618da189 Show encryption keys (only IDs) in the listing 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 9d2a859760 Support storing image encryption keys in Vault 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 76042d9ea8 Prefer local etcd addresses and correctly cycle over them even when they need resolving
Seems slightly overcomplicated...
2026-03-20 21:00:32 +03:00
Vitaliy Filippov 96473f15f9 Support DNS resolving via libc-ares 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 7207d0fa75 Batch handle_immediate_ops more 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 0fb58e3c43 Add vitastor-cli create & modify --enc-key parameter 2026-03-20 21:00:32 +03:00
Vitaliy Filippov de7edfd31d Support reading from snapshots encrypted with different keys 2026-03-20 21:00:32 +03:00
Vitaliy Filippov e6c3f4c6f4 Support decryption with multiple keys 2026-03-20 21:00:32 +03:00
Vitaliy Filippov b4d172be35 Allow to return chain_info in response to reads 2026-03-20 21:00:32 +03:00
Vitaliy Filippov a2debfca68 Add basic AES-XTS client-side encryption support 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 4c3c43f8ac Rework msgr send/receive to allow encryption support 2026-03-20 21:00:32 +03:00
Vitaliy Filippov 6638889126 Move fromhexstr() to str_util 2026-03-20 20:58:58 +03:00
Vitaliy Filippov 7fb5426ae9 Add openapi description 2026-03-20 20:58:58 +03:00
Vitaliy Filippov b4af7409af Slightly fix API return and input types 2026-03-20 20:58:58 +03:00
Vitaliy Filippov 645b1f3081 Implement vitastor-cli serve command to serve simple HTTP API 2026-03-20 20:58:58 +03:00
Vitaliy Filippov 9a21a0ca65 Implement HTTP server support O_o 2026-03-20 20:58:58 +03:00
Vitaliy Filippov 28e4610d25 Rename http_response_t to http_message_t 2026-03-20 20:58:58 +03:00
Vitaliy Filippov 2ddefa539f Extract common HTTP context 2026-03-20 20:58:57 +03:00
Vitaliy Filippov 3aa307f034 Support xxhash 32-bit checksums (data_csum_type=xxh3_32) 2026-03-20 20:58:57 +03:00
Vitaliy Filippov 3ed7a0bc5e Detect block checksums using csum_block_size, not data_csum_type 2026-03-20 20:58:57 +03:00
Vitaliy Filippov ab77f487e3 Add client certificate support 2026-03-20 20:58:57 +03:00
Vitaliy Filippov ba9be46acd Do not re-initialize TLS context every connection 2026-03-20 20:58:57 +03:00
Vitaliy Filippov 04c7d61ee9 Add https support to antietcd 2026-03-20 20:58:57 +03:00
Vitaliy Filippov d0ae63eb50 Implement etcd SSL support via OpenSSL
Maybe I should remove all of this and use libwebsockets :)
2026-03-20 20:58:57 +03:00
Vitaliy Filippov 4c34a47179 Fix str_replace 2026-03-19 01:19:29 +03:00
Vitaliy Filippov df16ab627a Fix another bug in old store checksums O_o
Checksum verification wasn't working correctly on reads of small initial offsetted writes
from the journal (until the write was flushed to "clean" metadata).
2026-03-17 22:26:38 +00:00
Vitaliy Filippov 44c895dc30 Fix clients possibly not destroyed after being switched to RDMA
It could in theory lead to hung operations on RDMA client disconnections.

Workflow (probable):
* OSD switches an outbound connection to PEER_RDMA and removes the FD from epoll
* But a receive request in io_uring is still active
* Receive response is never handled, refs remains > 0
* The connection is dropped, but osd_client_t is not destroyed because refs > 0
* Outbound ops are not canceled, suboperations hang, primary operations hang too
2026-03-17 01:04:17 +03:00
Vitaliy Filippov fdea595913 Use vitastor-cli in the Cinder driver for modifications instead of direct etcd interaction 2026-03-15 01:44:30 +03:00
Vitaliy Filippov ef4c91ecc8 Fix mon_https_ca parsing 2026-03-15 01:01:41 +03:00
Vitaliy Filippov 5192c6cf50 Fix Docker install.sh, add instructions for Podman 2026-03-13 20:08:18 +03:00
Vitaliy Filippov cb639a130d Release 3.0.5
A single urgent fix for the new store: metadata was written incorrectly when
filled metadata area exceeded 4 GB due to a simple & stupid 32-bit integer overflow.
This was leading to OSDs corrupting metadata and being unable to start on the next
run.

The bug affected only the new store (meta_format=3) and OSDs with metadata area size
larger than 4 GB. You can check your OSD metadata area size by checking `data_offset`
in `vitastor-disk read-sb /dev/vitastor/osdXX-data` for single-disk OSDs or
`blockdev --getsize64 /dev/vitastor/osdXX-meta` for hybrid OSDs.

If you're affected, it's highly recommended to:
- Install updated packages without restarting OSDs
- Restart OSDs **one by one**
- If a restart of an OSD triggers "has_degraded" rebalance - wait for it to finish
  before restarting another OSD
- If an OSD fails to start with "double-claimed" errors in the log - purge and recreate it,
  and again wait for "has_degraded" rebalance to finish before proceeding to the next OSD
2026-03-07 13:54:44 +03:00
Vitaliy Filippov 892e3a8b6d Add Ubuntu 26.04 build 2026-03-07 13:54:44 +03:00
Vitaliy Filippov 0bd9c26620 Make block_sizes uint64_t to prevent other integer overflow errors in the future 2026-03-07 12:02:57 +03:00
Vitaliy Filippov ae2b1f7802 Some useless code changes for beauty 2026-03-07 00:15:16 +03:00
Vitaliy Filippov 883b2e45da Fix uint32 overflow in the new store :-X 2026-03-06 17:21:22 +03:00
Vitaliy Filippov 09df74cfac Release 3.0.4
New store bug fixes:

- Fix very slow OSD startup (hours) possible in unlucky cases
- Fix incorrect garbage entry validation on start in some cases, preventing OSD startup
- Fix small_write_throttling not functioning and either hanging or crashing OSDs
- Fix "different fact_versions returned from subops" error possible with EC in some cases
- Add a recovery option for the new store: skip_corrupted_meta_entries
- Disable intent writes (even 4k) for hybrid OSDs by default for better write buffering

Other changes:

- Fix chmod/chown of a new empty VitastorFS root directory not working
- Fix vitastor-dd treating all < 32k reads as always empty
- Fix vitastor-disk always setting 32k csum_block_size for HDDs when checksums are enabled
- Fix OSD missing PG state updates and PGs hanging in "starting" in rare cases
- Fix a possible OSD crash with "map::at" message in rare cases when stopping a client
- Fix a possible OSD crash on start when RDMA is available but fails to initialize
- Fix removal and overwriting of totaled objects (objects with all corrupted copies)
- Do not allow data_block_size < 8*bitmap_granularity
- Remove RDMA ODP (On-Demand Paging) support
- Update antietcd to 1.2.4
- Update Docker build to Debian Trixie
- Add packages for AlmaLinux 10
2026-03-03 20:12:31 +03:00
Vitaliy Filippov 2ef3f012c9 Fix Docker builds 2026-03-03 20:09:23 +03:00
Vitaliy Filippov 7175d99c64 Add packages for Alma 10 to docs 2026-03-03 11:17:20 +03:00
Vitaliy Filippov 230a26772e Do not allow data_block_size < 8*bitmap_granularity 2026-03-03 02:09:16 +03:00
Vitaliy Filippov 637684c579 Fix chmod/chown of empty NFS root directory 2026-03-03 02:05:37 +03:00
Vitaliy Filippov c541dd422f Add skip_corrupted_meta_entries docs 2026-03-03 01:52:22 +03:00
Vitaliy Filippov 22c39561cf Add a recovery option for the new store: skip_corrupted_meta_entries 2026-03-02 02:51:09 +03:00
Vitaliy Filippov d4c67b4879 Ignore array-bounds warning in bs_heap 2026-03-02 02:49:56 +03:00
Vitaliy Filippov d1b7167861 Fix vitastor-disk always overwriting csum_block_size with 32k, add default atomic_write_size 0 for hybrid OSDs 2026-03-01 23:47:35 +00:00
Vitaliy Filippov 33b561b73a Fix throttling with the new store 2026-03-01 23:46:30 +00:00
Vitaliy Filippov a95a60c600 Add sudo -E to preserve asan suppressions 2026-03-02 01:48:50 +03:00
Vitaliy Filippov b3bc815354 Sorry, udevadm trigger --settle sometimes just fails in docker :-( 2026-03-02 01:48:50 +03:00
Vitaliy Filippov 42fc45d6da Use iterate_with_stable in list_objects too
Fixes "different fact_versions returned from subops" with EC in some cases
2026-03-02 01:48:33 +03:00
Vitaliy Filippov 3fbe2e7f8a Add more details for "different fact_versions" 2026-03-02 01:48:33 +03:00
Vitaliy Filippov 310c512b43 Correctly delete handle_immediate_ops when stopping a client
Fixes map::at crash in some cases
2026-03-02 01:48:19 +03:00
Vitaliy Filippov fd3e3b4ef0 Update buildenv to bookworm 2026-03-02 01:47:05 +03:00
Vitaliy Filippov 93cf69f89f Merge osd_id.h into object_id.h 2026-03-02 01:46:17 +03:00
Vitaliy Filippov a8dd8bf06c Update antietcd to 1.2.4 2026-03-02 01:45:56 +03:00
Vitaliy Filippov 7e23b57014 Do not save watch revision from initial created: true messages 2026-02-27 01:11:18 +03:00
Vitaliy Filippov b2427836a1 Fix dd treating all < 32k reads as always empty :-X 2026-02-22 14:19:13 +03:00
Vitaliy Filippov 2ed6760447 Remove ODP support 2026-02-17 02:35:46 +03:00
Vitaliy Filippov a1d215ea2f Fix garbage validation, skip garbage entries on load 2026-02-17 01:12:26 +03:00
Vitaliy Filippov 4fa442a5de Sort items on load to fix O(N^2) startup 2026-02-17 01:12:26 +03:00
Vitaliy Filippov 3a057ed5af Update docker to trixie 2026-02-13 01:09:34 +03:00
Vitaliy Filippov 0ad042b28b Add packages for Alma 10 2026-02-12 02:11:06 +03:00
Vitaliy Filippov 4d75c6c8f9 Fix removal and overwriting of totaled objects (objects with all corrupted copies) 2026-02-11 01:24:58 +03:00
Vitaliy Filippov 9ec18f0aa1 Fix double ibv_close_device on RDMA init failure 2026-02-11 00:32:12 +03:00
Vitaliy Filippov facaa2cc6a Release 3.0.3
- Fix csum_block_size > 0 unusable with atomic writes in the new store
  (almost all atomic write requests generated invalid checksums with csum_block_size > 0)
- Fix monitor sometimes randomly failing to optimize PGs with the "problem is
  infeasible or unbounded" message due to not waiting to read full stdout of lp_solve
- Remove reshard_abort optimisation to fix chunked resharding introduced in 3.0.2 possibly
  corrupting in-memory OSD state when handling multiple rapid PG count change requests
- Fix removed inodes not disappearing from OSD statistics in the new store, leading to
  bloating of the statistics with old inodes
- Increase test coverage for the new store and fix several minor bugs:
  - Enabling/disabling of used_for_app was recalculating inode space statistics incorrectly
  - Fix object metadata validation on OSD startup rejecting some correct sequences of entries,
    possibly leading to OSD being unable to start
  - Fix PG activation with EC failing in rare cases with EBUSY when requesting to commit an
    already committed write
  - Fix a theoretically possible metadata writeback issue on ENOSPC during commit
- Fix monitor failing to optimize PGs in presence of a host with name convertible to
  js Number (like 04e278988710) :D
- Fix vitastor-cli dd sometimes (rarely) truncating the image when writing to stdout
- Fix a theoretically possible client connection object leak when io_uring is full
- Fix a leak of RDMA-CM connection objects
- Fix crashes with data_block_size < 32KB (useless setup, but anyway) (#113)
2026-02-08 01:39:57 +03:00
Vitaliy Filippov d2ac8e3827 Fix block writeback on enospc during commit 2026-02-07 16:27:26 +03:00
Vitaliy Filippov d6dacc67db Do not return EBUSY when already stable in the new store 2026-02-07 16:10:39 +03:00
Vitaliy Filippov cbe51595cb Remove reshard_abort 2026-02-07 16:05:40 +03:00
Vitaliy Filippov 6bd0830ab8 Add a test for chunked resharding 2026-02-07 16:02:17 +03:00
Vitaliy Filippov 796e82f34d Check get_compacted_count and get_compact_queue_size 2026-02-07 15:44:54 +03:00
Vitaliy Filippov db39283970 Add a test for complete_lsn_write 2026-02-07 15:36:46 +03:00
Vitaliy Filippov 9f8c686321 Add a test for redirect_intent 2026-02-07 15:09:58 +03:00
Vitaliy Filippov 2618e559d1 Add rollback tests, simplify rollback logic 2026-02-07 15:09:58 +03:00
Vitaliy Filippov 037d2bc162 Add a test for recalc_inode_stats in the new store and fix a bug in it 2026-02-07 14:00:10 +03:00
Vitaliy Filippov f0b64adb32 Fix removed inodes not disappearing from statistics in the new store 2026-02-07 02:48:49 +03:00
Vitaliy Filippov f06d64d879 Fix validate_object commit validation bug 2026-02-06 18:52:37 +03:00
Vitaliy Filippov d164499a1c Add more iterate_compaction test cases 2026-02-06 18:52:19 +03:00
Vitaliy Filippov 725e9aa8ae Remove unused read_locked_entry 2026-02-06 17:38:59 +03:00
Vitaliy Filippov 0715feffa1 Wait for finished read, not for process exit in lp_optimizer 2026-02-04 02:33:08 +03:00
Vitaliy Filippov e9f37e8dc3 Fix NaN PG optimisation error when a hostname has a name convertible to js Number... some shit like 04e278988710 2026-02-04 15:08:03 +03:00
Vitaliy Filippov 4fbe4b5654 Fix BIG_INTENT rechecks with enabled checksums 2026-02-04 02:33:50 +03:00
Vitaliy Filippov 9fb645693b Fix checksums with BIG_INTENT writes in the new store 2026-02-04 02:33:50 +03:00
Vitaliy Filippov 9e47828383 Add a test for big_intent with checksums 2026-02-04 02:33:50 +03:00
176 changed files with 15932 additions and 1971 deletions
+8 -7
View File
@@ -1,28 +1,29 @@
FROM node:16-bullseye
FROM node:16-bookworm
WORKDIR /root
ADD ./docker/vitastor.gpg /etc/apt/trusted.gpg.d
ADD ./docker/etc/apt/trusted.gpg.d /etc/apt/trusted.gpg.d
RUN echo 'deb http://deb.debian.org/debian bullseye-backports main' >> /etc/apt/sources.list; \
echo 'deb http://vitastor.io/debian bullseye main' >> /etc/apt/sources.list; \
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list; \
echo 'deb http://vitastor.io/debian bookworm main' >> /etc/apt/sources.list; \
echo >> /etc/apt/preferences; \
echo 'Package: *' >> /etc/apt/preferences; \
echo 'Pin: release a=bullseye-backports' >> /etc/apt/preferences; \
echo 'Pin: release n=bookworm-backports' >> /etc/apt/preferences; \
echo 'Pin-Priority: 500' >> /etc/apt/preferences; \
echo >> /etc/apt/preferences; \
echo 'Package: *' >> /etc/apt/preferences; \
echo 'Pin: origin "vitastor.io"' >> /etc/apt/preferences; \
echo 'Pin-Priority: 1000' >> /etc/apt/preferences; \
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/debian.sources; \
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
RUN apt-get update
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan5 \
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan8 \
libgoogle-perftools-dev devscripts libjerasure-dev cmake libibverbs-dev libisal-dev
RUN apt-get -y build-dep fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted libc-ares-dev udev
RUN apt-get --download-only source fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
RUN set -ex; \
+198
View File
@@ -234,6 +234,60 @@ jobs:
echo ""
done
test_etcd_fail_https:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_etcd_fail_https_antietcd:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_snapshot_https:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_interrupted_rebalance:
runs-on: ubuntu-latest
needs: build
@@ -648,6 +702,24 @@ jobs:
echo ""
done
test_snapshot_chain_encrypted:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_snapshot_chain:
runs-on: ubuntu-latest
needs: build
@@ -1206,6 +1278,96 @@ jobs:
echo ""
done
test_checksum:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_checksum.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_checksum_xxhash:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_checksum:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: OLD=1 /root/vitastor/tests/test_checksum.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_corrupt_all:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_corrupt_all.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_corrupt_all:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: OLD=1 /root/vitastor/tests/test_corrupt_all.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_reweight_half:
runs-on: ubuntu-latest
needs: build
@@ -1980,3 +2142,39 @@ jobs:
echo ""
done
test_write_encrypted:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_write_encrypted.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_write_encrypted_ec:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
+8
View File
@@ -38,6 +38,14 @@ for my $line (<>)
{
$test_name .= '_antietcd';
}
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
{
$test_name .= '_https';
}
elsif ($1 eq 'ENCRYPTED')
{
$test_name .= '_encrypted';
}
elsif ($1 eq 'OLD')
{
$test_name =~ s/^test_/test_old_/s;
+1
View File
@@ -3,3 +3,4 @@
package-lock.json
fio
qemu
node_modules
+1 -1
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.12)
project(vitastor)
set(VITASTOR_VERSION "3.0.2")
set(VITASTOR_VERSION "3.0.5")
include(CTest)
+1
View File
@@ -62,6 +62,7 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
- [Прочие параметры OSD](docs/config/osd.ru.md)
- [Параметры мониторов](docs/config/monitor.ru.md)
- [Безопасность](docs/config/security.ru.md)
- [Настройки пулов](docs/config/pool.ru.md)
- [Метаданные образов в etcd](docs/config/inode.ru.md)
- Использование
+1
View File
@@ -62,6 +62,7 @@ Read more details in the documentation. You can start from here: [Quick Start](d
- [OSD Disk Layout](docs/config/layout-osd.en.md)
- [OSD Runtime Parameters](docs/config/osd.en.md)
- [Monitor](docs/config/monitor.en.md)
- [Security](docs/config/security.en.md)
- [Pool configuration](docs/config/pool.en.md)
- [Image metadata in etcd](docs/config/inode.en.md)
- Usage
+7 -7
View File
@@ -1,5 +1,5 @@
# Compile stage
FROM golang:bookworm AS build
FROM golang:trixie AS build
ADD go.sum go.mod /app/
RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go mod download -x
@@ -9,7 +9,7 @@ RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)e
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi
# Final stage
FROM debian:bookworm
FROM debian:trixie
LABEL maintainers="Vitaliy Filippov <vitalif@yourcmc.ru>"
LABEL description="Vitastor CSI Driver"
@@ -25,20 +25,20 @@ RUN apt-get update && \
# NFS mount dependencies
nfs-common netbase \
# dependencies of qemu-storage-daemon
libnuma1 liburing2 libglib2.0-0 libfuse3-3 libaio1 libzstd1 libnettle8 \
libgmp10 libhogweed6 libp11-kit0 libidn2-0 libunistring2 libtasn1-6 libpcre2-8-0 libffi8 && \
libaio1t64 libc6 libfuse3-4 libglib2.0-0t64 libgmp10 libgnutls30t64 \
libhogweed6t64 libnettle8t64 libnuma1 libselinux1 liburing2 libzstd1 zlib1g && \
apt-get clean && \
(echo options nbd nbds_max=128 > /etc/modprobe.d/nbd.conf)
COPY --from=build /app/vitastor-csi /bin/
RUN (echo deb http://vitastor.io/debian bookworm main > /etc/apt/sources.list.d/vitastor.list) && \
RUN (echo deb http://vitastor.io/debian trixie main > /etc/apt/sources.list.d/vitastor.list) && \
((echo 'Package: *'; echo 'Pin: origin "vitastor.io"'; echo 'Pin-Priority: 1000') > /etc/apt/preferences.d/vitastor.pref) && \
wget -q -O /etc/apt/trusted.gpg.d/vitastor.gpg https://vitastor.io/debian/pubkey.gpg && \
apt-get update && \
apt-get install -y vitastor-client ibverbs-providers && \
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.2%2Bds-2%2Bvitastor1/qemu-utils_10.0.2%2Bds-2%2Bvitastor1_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.2%2Bds-2%2Bvitastor1/qemu-block-extra_10.0.2%2Bds-2%2Bvitastor1_amd64.deb && \
dpkg -x qemu-utils*.deb tmp1 && \
dpkg -x qemu-block-extra*.deb tmp1 && \
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \
+4 -4
View File
@@ -1,5 +1,5 @@
# Compile stage
FROM golang:bookworm AS build
FROM golang:trixie AS build
ADD go.sum go.mod /app/
RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go mod download -x
@@ -9,7 +9,7 @@ RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)e
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi
# Final stage
FROM debian:bookworm
FROM debian:trixie
LABEL maintainers="Vitaliy Filippov <vitalif@yourcmc.ru>"
LABEL description="Vitastor CSI Driver"
@@ -36,8 +36,8 @@ ADD deb /deb
RUN apt-get update && \
apt-get -y install /deb/vitastor-client_*.deb && \
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
dpkg -x qemu-utils*.deb tmp1 && \
dpkg -x qemu-block-extra*.deb tmp1 && \
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.2
VITASTOR_VERSION ?= v3.0.5
all: build push
+1 -1
View File
@@ -49,7 +49,7 @@ spec:
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: vitalif/vitastor-csi:v3.0.2
image: vitalif/vitastor-csi:v3.0.5
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.2
image: vitalif/vitastor-csi:v3.0.5
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.2"
vitastorCSIDriverVersion = "3.0.5"
)
// Config struct fills the parameters of request or user input
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# 26.04 Resolute Raccoon
docker build --build-arg DISTRO=ubuntu --build-arg REL=resolute -t vitastor-buildenv:resolute -f vitastor-buildenv.Dockerfile .
docker run -it --rm -e REL=resolute -v `dirname $0`/../:/root/vitastor vitastor-buildenv:resolute /root/vitastor/debian/vitastor-build.sh
+1 -1
View File
@@ -1,4 +1,4 @@
vitastor (3.0.2-1) unstable; urgency=medium
vitastor (3.0.5-1) unstable; urgency=medium
* Bugfixes
+1 -1
View File
@@ -3,7 +3,7 @@ Section: admin
Priority: optional
Maintainer: Vitaliy Filippov <vitalif@yourcmc.ru>
Build-Depends: debhelper, g++ (>= 8), libstdc++6 (>= 8),
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev, libc-ares-dev,
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
node-bindings <!nocheck>, node-gyp, node-nan
Standards-Version: 4.5.0
+1 -1
View File
@@ -25,7 +25,7 @@ RUN set -e -x; \
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
RUN apt-get update && \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev \
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
apt-get -y build-dep fio && \
apt-get --download-only source fio
+1 -1
View File
@@ -1,6 +1,6 @@
# Build Docker image with Vitastor packages
FROM debian:bookworm
FROM debian:trixie
ADD etc/apt /etc/apt/
RUN apt-get update && apt-get -y install vitastor ibverbs-providers udev systemd qemu-system-x86 qemu-system-common qemu-block-extra qemu-utils jq nfs-common && apt-get clean
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.2
VITASTOR_VERSION ?= v3.0.5
all: build push
+1 -1
View File
@@ -1,3 +1,3 @@
Package: *
Pin: release n=bookworm-backports
Pin: release n=trixie-backports
Pin-Priority: 500
+2 -2
View File
@@ -1,2 +1,2 @@
deb http://vitastor.io/debian bookworm main
deb http://http.debian.net/debian/ bookworm-backports main
deb http://vitastor.io/debian trixie main
#deb http://http.debian.net/debian/ trixie-backports main
@@ -7,7 +7,7 @@ PartOf=vitastor.target
[Service]
Restart=always
EnvironmentFile=/etc/vitastor/docker.conf
ExecStart=bash -c 'docker run --rm -i -v /etc/vitastor:/etc/vitastor -v /dev:/dev -v /run:/run \
ExecStart=bash -c 'docker run --rm -i -v /etc/vitastor:/etc/vitastor -v /dev:/dev -v /run:/run -e SYSTEMD_IN_CHROOT=0 \
--security-opt seccomp=unconfined --privileged --pid=host --log-driver none --network host --name vitastor vitastor:$VITASTOR_VERSION \
sleep.sh'
ExecStartPost=udevadm trigger
+1 -1
View File
@@ -4,7 +4,7 @@
#
# Desired Vitastor version
VITASTOR_VERSION=v3.0.2
VITASTOR_VERSION=v3.0.5
# Additional arguments for all containers
# For example, you may want to specify a custom logging driver here
+2 -3
View File
@@ -2,8 +2,7 @@
set -e
cp -urv /etc/default /host-etc/
cp -urv /etc/systemd /host-etc/
cp -urv /etc/udev /host-etc/
cp -urv /etc/systemd/system/vitastor* /host-etc/systemd/system/
cp -urv /etc/udev/rules.d /host-etc/udev/
cp -urnv /etc/vitastor /host-etc/
cp -urnv /opt/scripts/* /host-bin/
+1
View File
@@ -38,3 +38,4 @@ In the future, additional configuration methods may be added:
- [OSD Disk Layout](config/layout-osd.en.md)
- [OSD Runtime Parameters](config/osd.en.md)
- [Monitor](config/monitor.en.md)
- [Security Parameters](config/security.en.md)
+1
View File
@@ -41,3 +41,4 @@
- [Дисковые параметры OSD](config/layout-osd.ru.md)
- [Прочие параметры OSD](config/osd.ru.md)
- [Параметры мониторов](config/monitor.ru.md)
- [Параметры безопасности](config/security.ru.md)
+8 -2
View File
@@ -198,8 +198,14 @@ put a modified value into etcd key /vitastor/config/global.
- Type: string
- Default: none
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
enable data checksums.
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
Note that enabled data checksums either increase memory usage or reduce
performance. Check details in [csum_block_size](#csum_block_size) description.
## csum_block_size
+6 -2
View File
@@ -209,8 +209,12 @@ journal_block_size и meta_block_size. Однако на данный момен
- Тип: строка
- Значение по умолчанию: none
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
чтобы включить контрольные суммы данных.
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
Следует понимать, что контрольные суммы в зависимости от размера блока их
расчёта либо увеличивают потребление памяти, либо снижают производительность.
+17 -28
View File
@@ -22,7 +22,6 @@ between clients, OSDs and etcd.
- [rdma_max_msg](#rdma_max_msg)
- [rdma_max_recv](#rdma_max_recv)
- [rdma_max_send](#rdma_max_send)
- [rdma_odp](#rdma_odp)
- [peer_connect_interval](#peer_connect_interval)
- [peer_connect_timeout](#peer_connect_timeout)
- [osd_idle_timeout](#osd_idle_timeout)
@@ -102,11 +101,6 @@ found or if `osd_network` is not specified. Auto-selection is also
unsupported with old libibverbs < v32, like in Debian 10 Buster or
CentOS 7.
Vitastor supports all adapters, even ones without ODP support, like
Mellanox ConnectX-3 and non-Mellanox cards. Versions up to Vitastor
1.2.0 required ODP which is only present in Mellanox ConnectX >= 4.
See also [rdma_odp](#rdma_odp).
Run `ibv_devinfo -v` as root to list available RDMA devices and their
features.
@@ -116,6 +110,23 @@ the manual of your network vendor for details about setting up the switch
for RoCEv2 correctly. Usually it means setting up Lossless Ethernet with
PFC (Priority Flow Control) and ECN (Explicit Congestion Notification).
Vitastor supports all adapters, even ones without ODP (On-Demand Paging)
support, like Mellanox ConnectX-3 and non-Mellanox cards. ODP is only present
in Mellanox ConnectX >= 4 adapters and allows to skip memory registration
for RDMA and thus, in theory, avoid memory copying.
Versions up to Vitastor 1.2.0 required ODP, then it was disabled by default,
but it was still supported up to 3.0.3. Now ODP support is removed because it
actually only hurts performance: an example 3-node cluster with 8 NVMe in each
node and 2*25 GBit/s ConnectX-6 RDMA network pushed 3950000 read iops without
ODP, but only 239000 iops with ODP.
This happens because Mellanox ODP implementation seems to be based on
message retransmissions when the adapter doesn't know about the buffer yet -
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
which is generally slow in RDMA/RoCE networks. Here's a presentation about
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
## rdma_port_num
- Type: integer
@@ -187,28 +198,6 @@ less than `rdma_max_recv` so the receiving side doesn't run out of buffers.
Doesn't affect memory usage - additional memory isn't allocated for send
operations.
## rdma_odp
- Type: boolean
- Default: false
Use RDMA with On-Demand Paging. ODP is currently only available on Mellanox
ConnectX-4 and newer adapters. ODP allows to not register memory explicitly
for RDMA adapter to be able to use it. This, in turn, allows to skip memory
copying during sending. One would think this should improve performance, but
**in reality** RDMA performance with ODP is **drastically** worse. Example
3-node cluster with 8 NVMe in each node and 2*25 GBit/s ConnectX-6 RDMA network
without ODP pushes 3950000 read iops, but only 239000 iops with ODP...
This happens because Mellanox ODP implementation seems to be based on
message retransmissions when the adapter doesn't know about the buffer yet -
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
which is generally slow in RDMA/RoCE networks. Here's a presentation about
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
ODP support is retained in the code just in case a good ODP implementation
appears one day.
## peer_connect_interval
- Type: seconds
+18 -30
View File
@@ -22,7 +22,6 @@
- [rdma_max_msg](#rdma_max_msg)
- [rdma_max_recv](#rdma_max_recv)
- [rdma_max_send](#rdma_max_send)
- [rdma_odp](#rdma_odp)
- [peer_connect_interval](#peer_connect_interval)
- [peer_connect_timeout](#peer_connect_timeout)
- [osd_idle_timeout](#osd_idle_timeout)
@@ -101,12 +100,6 @@ RoCEv1/RoCEv2, и даже позволяет полностью отключи
не задана. Также автовыбор не поддерживается со старыми версиями библиотеки
libibverbs < v32, например в Debian 10 Buster или CentOS 7.
Vitastor поддерживает все модели адаптеров, включая те, у которых
нет поддержки ODP, то есть вы можете использовать RDMA с ConnectX-3 и
картами производства не Mellanox. Версии Vitastor до 1.2.0 включительно
требовали ODP, который есть только на Mellanox ConnectX 4 и более новых.
См. также [rdma_odp](#rdma_odp).
Запустите `ibv_devinfo -v` от имени суперпользователя, чтобы посмотреть
список доступных RDMA-устройств, их параметры и возможности.
@@ -117,6 +110,24 @@ Vitastor поддерживает все модели адаптеров, вкл
подразумевает настройку сети без потерь на основе PFC (Priority Flow
Control) и ECN (Explicit Congestion Notification).
Vitastor поддерживает все модели адаптеров, включая те, у которых нет
поддержки ODP (On-Demand Paging), например, ConnectX-3 и карты производства
не Mellanox. Функция ODP доступна только на адаптерах Mellanox ConnectX-4 и
более новых и позволяет не регистрировать память для её использования RDMA-картой,
благодаря чему в теории можно избежать лишних копирований памяти.
Версии Vitastor до 1.2.0 включительно требовали ODP, потом функция был отключена
по умолчанию, но поддерживалась вплоть до версии 3.0.3. Сейчас поддержка ODP
полностью удалена, так как на самом деле она только портит производительность:
например, на 3-узловом кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на
чтение с RDMA без ODP удаётся снять 3950000 iops, а с ODP - всего 239000 iops.
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
основана на повторной передаче сообщений, когда карте не известен буфер -
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
## rdma_port_num
- Тип: целое число
@@ -192,29 +203,6 @@ OSD в любом случае согласовывают реальное зн
Не влияет на потребление памяти - дополнительная память на операции отправки
не выделяется.
## rdma_odp
- Тип: булево (да/нет)
- Значение по умолчанию: false
Использовать RDMA с On-Demand Paging. ODP - функция, доступная пока что
исключительно на адаптерах Mellanox ConnectX-4 и более новых. ODP позволяет
не регистрировать память для её использования RDMA-картой. Благодаря этому
можно не копировать данные при отправке их в сеть и, казалось бы, это должно
улучшать производительность - но **по факту** получается так, что
производительность только ухудшается, причём сильно. Пример - на 3-узловом
кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на чтение с RDMA без ODP
удаётся снять 3950000 iops, а с ODP - всего 239000 iops...
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
основана на повторной передаче сообщений, когда карте не известен буфер -
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
Возможность использования ODP сохранена в коде на случай, если вдруг в один
прекрасный день появится хорошая реализация ODP.
## peer_connect_interval
- Тип: секунды
+27 -7
View File
@@ -38,6 +38,7 @@ with an OSD restart or, for some of them, even without restarting by updating co
- [journal_io](#journal_io)
- [journal_sector_buffer_count](#journal_sector_buffer_count)
- [journal_no_same_sector_overwrites](#journal_no_same_sector_overwrites)
- [skip_corrupted_meta_entries](#skip_corrupted_meta_entries)
- [throttle_small_writes](#throttle_small_writes)
- [throttle_target_iops](#throttle_target_iops)
- [throttle_target_mbs](#throttle_target_mbs)
@@ -279,13 +280,19 @@ Maximum number of journal flushers (see above min_flusher_count).
- Type: boolean
- Default: true
This parameter makes Vitastor always keep metadata area of the block device
in memory. It's required for good performance because it allows to avoid
additional read-modify-write cycles during metadata modifications. Metadata
area size is currently roughly 224 MB per 1 TB of data. You can turn it off
to reduce memory usage by this value, but it will hurt performance. This
restriction is likely to be removed in the future along with the upgrade
of the metadata storage scheme.
Only for the old store ([meta_format](layout-osd.en.md#meta_format) 2).
This parameter makes Vitastor keep a copy of metadata area in memory as it is
on disk, in addition to the metadata database. When the option is enabled, every
metadata entry is effectively stored in RAM twice. It's required for good performance
because it allows to avoid additional read-modify-write cycles during metadata
modifications. Metadata area size with the old store is roughly 224 MB per 1 TB
of data. You can turn the option off to reduce memory usage by this value, but
it will reduce performance.
For the new store ([meta_format](layout-osd.en.md#meta_format) 3), the option
may be changed in the future to support operation without loading full metadata
database in memory.
## inmemory_journal
@@ -364,6 +371,8 @@ blocks. The only situation when you should increase it to a larger value
is when you enable journal_no_same_sector_overwrites. In this case set
it to, for example, 1024.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
## journal_no_same_sector_overwrites
- Type: boolean
@@ -377,6 +386,17 @@ journal after writing it instead of possibly overwriting it the second time.
Most (99%) other SSDs don't need this option.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
## skip_corrupted_meta_entries
- Type: boolean
- Default: false
Only for the new store ([meta_format](layout-osd.en.md#meta_format) 3).
Allow OSD to start when some metadata entries or blocks are corrupted by
skipping them. Should be only used as an emergency measure.
## throttle_small_writes
- Type: boolean
+28 -7
View File
@@ -39,6 +39,7 @@
- [journal_io](#journal_io)
- [journal_sector_buffer_count](#journal_sector_buffer_count)
- [journal_no_same_sector_overwrites](#journal_no_same_sector_overwrites)
- [skip_corrupted_meta_entries](#skip_corrupted_meta_entries)
- [throttle_small_writes](#throttle_small_writes)
- [throttle_target_iops](#throttle_target_iops)
- [throttle_target_mbs](#throttle_target_mbs)
@@ -287,13 +288,19 @@ Flusher - это микро-поток (корутина), которая коп
- Тип: булево (да/нет)
- Значение по умолчанию: true
Данный параметр заставляет Vitastor всегда держать область метаданных диска
в памяти. Это нужно, чтобы избегать дополнительных операций чтения с диска
при записи. Размер области метаданных на данный момент составляет примерно
224 МБ на 1 ТБ данных. При включении потребление памяти снизится примерно
на эту величину, но при этом также снизится и производительность. В будущем,
после обновления схемы хранения метаданных, это ограничение, скорее всего,
будет ликвидировано.
Только для старого хранилища ([meta_format](layout-osd.en.md#meta_format) 2).
Данный параметр заставляет Vitastor всегда держать копию области метаданных
в памяти в том же виде, как она лежит на диске, в дополнение к БД метаданных.
То есть, с включённой опцией каждая запись метаданных хранится в памяти дважды.
Это нужно, чтобы избегать дополнительных операций чтения с диска при записи.
Размер области метаданных в старом хранилище составляет примерно 224 МБ на
1 ТБ данных. Вы можете отключить опцию, чтобы снизить потребление памяти
примерно на эту величину, но при этом также снизится и производительность.
Для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3) опция,
возможно, будет переработана в будущем для поддержки работы без полной
загрузки метаданных в памяти.
## inmemory_journal
@@ -376,6 +383,8 @@ fsync небезопасным даже с режимом "directsync".
нужно менять - это если вы включаете journal_no_same_sector_overwrites. В
этом случае установите данный параметр, например, в 1024.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
## journal_no_same_sector_overwrites
- Тип: булево (да/нет)
@@ -391,6 +400,18 @@ fsync небезопасным даже с режимом "directsync".
Почти все другие SSD (99% моделей) не требуют данной опции.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
## skip_corrupted_meta_entries
- Тип: булево (да/нет)
- Значение по умолчанию: false
Только для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3).
Разрешить OSD запускаться, даже если часть блоков или записей метаданных
повреждена, пропуская их. Опция предназначена для использования только в
целях аварийного восстановления.
## throttle_small_writes
- Тип: булево (да/нет)
+150
View File
@@ -0,0 +1,150 @@
[Documentation](../../README.md#documentation) → [Configuration](../config.en.md) → Security Parameters
-----
[Читать на русском](security.ru.md)
# Security Parameters
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
- [etcd_client_cert](#etcd_client_cert)
- [etcd_client_key](#etcd_client_key)
- [etcd_ca](#etcd_ca)
- [osd_etcd_client_cert](#osd_etcd_client_cert)
- [osd_etcd_client_key](#osd_etcd_client_key)
- [mon_etcd_client_cert](#mon_etcd_client_cert)
- [mon_etcd_client_key](#mon_etcd_client_key)
- [vault_url](#vault_url)
- [vault_secret_api_path](#vault_secret_api_path)
- [vault_client_cert](#vault_client_cert)
- [vault_client_key](#vault_client_key)
- [vault_ca](#vault_ca)
- [vault_timeout_ms](#vault_timeout_ms)
- [vault_error_timeout_sec](#vault_error_timeout_sec)
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
## etcd_client_cert
- Type: string
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
etcd https connections. May be path to a file or just a PEM string with certificate.
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
## etcd_client_key
- Type: string
Private key for etcd_client_cert (also a file or a PEM string).
## etcd_ca
- Type: string
Trusted TLS CA to verify etcd server certificate. May be path to a file,
directory or just a PEM string with certificate.
## osd_etcd_client_cert
- Type: string
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
OSDs, clients and monitors should have different permissions, so they should
use different certificates.
## osd_etcd_client_key
- Type: string
Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
## mon_etcd_client_cert
- Type: string
Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
## mon_etcd_client_key
- Type: string
Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
## vault_url
- Type: string
Vault base URL.
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
Encryption is performed by the client, OSDs don't have access to decrypted data.
Encryption keys may be stored in etcd or, for the increased security level, in an external
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
instance.
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
In that case, only key IDs are stored in etcd.
## vault_secret_api_path
- Type: string
- Default: /v1/secret/
Vault v1 secret API mount path to use.
## vault_client_cert
- Type: string
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
may be path to a file or just a certificate in PEM string.
## vault_client_key
- Type: string
Private key for vault_client_cert (also a file or a PEM string).
## vault_ca
- Type: string
Trusted TLS CA to verify Vault server certificate. May be path to a file,
directory or just a PEM string with certificate.
## vault_timeout_ms
- Type: integer
- Default: 5000
Timeout for Vault requests in milliseconds.
## vault_error_timeout_sec
- Type: integer
- Default: 60
Time (in seconds) to wait before retrying after receiving an error from Vault.
## vault_refresh_leeway_sec
- Type: integer
- Default: 60
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
in case of system clock drift.
## max_aes_xts_pool_size
- Type: integer
- Default: 256
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
doesn't require modification.
+154
View File
@@ -0,0 +1,154 @@
[Документация](../../README-ru.md#документация) → [Конфигурация](../config.ru.md) → Параметры безопасности
-----
[Read in English](security.en.md)
# Параметры безопасности
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
OSD, мониторами и клиентами.
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
поддерживает онлайн-изменение.
- [etcd_client_cert](#etcd_client_cert)
- [etcd_client_key](#etcd_client_key)
- [etcd_ca](#etcd_ca)
- [osd_etcd_client_cert](#osd_etcd_client_cert)
- [osd_etcd_client_key](#osd_etcd_client_key)
- [mon_etcd_client_cert](#mon_etcd_client_cert)
- [mon_etcd_client_key](#mon_etcd_client_key)
- [vault_url](#vault_url)
- [vault_secret_api_path](#vault_secret_api_path)
- [vault_client_cert](#vault_client_cert)
- [vault_client_key](#vault_client_key)
- [vault_ca](#vault_ca)
- [vault_timeout_ms](#vault_timeout_ms)
- [vault_error_timeout_sec](#vault_error_timeout_sec)
- [vault_refresh_leeway_sec](#vault_refresh_leeway_sec)
- [max_aes_xts_pool_size](#max_aes_xts_pool_size)
## etcd_client_cert
- Тип: строка
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
сертификатом в формате PEM. В последнем случае строка должна начинаться с
"-----BEGIN CERTIFICATE-----".
## etcd_client_key
- Тип: строка
Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
## etcd_ca
- Тип: строка
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
Может быть путём к файлу, директории или просто строкой с сертификатом в
формате PEM.
## osd_etcd_client_cert
- Тип: строка
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
использовать разные сертификаты.
## osd_etcd_client_key
- Тип: строка
Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
## mon_etcd_client_cert
- Тип: строка
Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
## mon_etcd_client_key
- Тип: строка
Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
## vault_url
- Тип: строка
Базовый адрес Vault.
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
В этом случае, только ID ключей хранятся в etcd.
## vault_secret_api_path
- Тип: строка
- Значение по умолчанию: /v1/secret/
Путь к API секретов v1 для использования клиентами.
## vault_client_cert
- Тип: строка
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
может быть путём к файлу или просто PEM-строкой с сертификатом.
## vault_client_key
- Тип: строка
Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
## vault_ca
- Тип: строка
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
Может быть путём к файлу, директории или просто строкой с сертификатом в
формате PEM.
## vault_timeout_ms
- Тип: целое число
- Значение по умолчанию: 5000
Максимально время выполнения Vault-запросов в миллисекундах.
## vault_error_timeout_sec
- Тип: целое число
- Значение по умолчанию: 60
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
## vault_refresh_leeway_sec
- Тип: целое число
- Значение по умолчанию: 60
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
## max_aes_xts_pool_size
- Тип: целое число
- Значение по умолчанию: 256
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
Вряд ли требует изменения.
+2
View File
@@ -44,6 +44,8 @@
{{../../config/monitor.en.md|indent=2}}
{{../../config/security.en.md|indent=2}}
{{../../config/pool.en.md|indent=2}}
{{../../config/inode.en.md|indent=2}}
+2
View File
@@ -44,6 +44,8 @@
{{../../config/monitor.ru.md|indent=2}}
{{../../config/security.ru.md|indent=2}}
{{../../config/pool.ru.md|indent=2}}
{{../../config/inode.ru.md|indent=2}}
+14 -4
View File
@@ -233,11 +233,21 @@
type: string
default: none
info: |
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
enable data checksums.
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
Note that enabled data checksums either increase memory usage or reduce
performance. Check details in [csum_block_size](#csum_block_size) description.
info_ru: |
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
чтобы включить контрольные суммы данных.
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
Следует понимать, что контрольные суммы в зависимости от размера блока их
расчёта либо увеличивают потребление памяти, либо снижают производительность.
+35 -50
View File
@@ -84,11 +84,6 @@
unsupported with old libibverbs < v32, like in Debian 10 Buster or
CentOS 7.
Vitastor supports all adapters, even ones without ODP support, like
Mellanox ConnectX-3 and non-Mellanox cards. Versions up to Vitastor
1.2.0 required ODP which is only present in Mellanox ConnectX >= 4.
See also [rdma_odp](#rdma_odp).
Run `ibv_devinfo -v` as root to list available RDMA devices and their
features.
@@ -97,6 +92,23 @@
the manual of your network vendor for details about setting up the switch
for RoCEv2 correctly. Usually it means setting up Lossless Ethernet with
PFC (Priority Flow Control) and ECN (Explicit Congestion Notification).
Vitastor supports all adapters, even ones without ODP (On-Demand Paging)
support, like Mellanox ConnectX-3 and non-Mellanox cards. ODP is only present
in Mellanox ConnectX >= 4 adapters and allows to skip memory registration
for RDMA and thus, in theory, avoid memory copying.
Versions up to Vitastor 1.2.0 required ODP, then it was disabled by default,
but it was still supported up to 3.0.3. Now ODP support is removed because it
actually only hurts performance: an example 3-node cluster with 8 NVMe in each
node and 2*25 GBit/s ConnectX-6 RDMA network pushed 3950000 read iops without
ODP, but only 239000 iops with ODP.
This happens because Mellanox ODP implementation seems to be based on
message retransmissions when the adapter doesn't know about the buffer yet -
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
which is generally slow in RDMA/RoCE networks. Here's a presentation about
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
info_ru: |
Название RDMA-устройства для связи с Vitastor OSD (например, "rocep5s0f0").
Если не указано, Vitastor попробует найти RoCE-устройство, соответствующее
@@ -105,12 +117,6 @@
не задана. Также автовыбор не поддерживается со старыми версиями библиотеки
libibverbs < v32, например в Debian 10 Buster или CentOS 7.
Vitastor поддерживает все модели адаптеров, включая те, у которых
нет поддержки ODP, то есть вы можете использовать RDMA с ConnectX-3 и
картами производства не Mellanox. Версии Vitastor до 1.2.0 включительно
требовали ODP, который есть только на Mellanox ConnectX 4 и более новых.
См. также [rdma_odp](#rdma_odp).
Запустите `ibv_devinfo -v` от имени суперпользователя, чтобы посмотреть
список доступных RDMA-устройств, их параметры и возможности.
@@ -120,6 +126,24 @@
коммутатора для RoCEv2 ищите в документации производителя. Обычно это
подразумевает настройку сети без потерь на основе PFC (Priority Flow
Control) и ECN (Explicit Congestion Notification).
Vitastor поддерживает все модели адаптеров, включая те, у которых нет
поддержки ODP (On-Demand Paging), например, ConnectX-3 и карты производства
не Mellanox. Функция ODP доступна только на адаптерах Mellanox ConnectX-4 и
более новых и позволяет не регистрировать память для её использования RDMA-картой,
благодаря чему в теории можно избежать лишних копирований памяти.
Версии Vitastor до 1.2.0 включительно требовали ODP, потом функция был отключена
по умолчанию, но поддерживалась вплоть до версии 3.0.3. Сейчас поддержка ODP
полностью удалена, так как на самом деле она только портит производительность:
например, на 3-узловом кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на
чтение с RDMA без ODP удаётся снять 3950000 iops, а с ODP - всего 239000 iops.
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
основана на повторной передаче сообщений, когда карте не известен буфер -
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
- name: rdma_port_num
type: int
info: |
@@ -218,45 +242,6 @@
у принимающей стороны в процессе работы не заканчивались буферы на приём.
Не влияет на потребление памяти - дополнительная память на операции отправки
не выделяется.
- name: rdma_odp
type: bool
default: false
online: false
info: |
Use RDMA with On-Demand Paging. ODP is currently only available on Mellanox
ConnectX-4 and newer adapters. ODP allows to not register memory explicitly
for RDMA adapter to be able to use it. This, in turn, allows to skip memory
copying during sending. One would think this should improve performance, but
**in reality** RDMA performance with ODP is **drastically** worse. Example
3-node cluster with 8 NVMe in each node and 2*25 GBit/s ConnectX-6 RDMA network
without ODP pushes 3950000 read iops, but only 239000 iops with ODP...
This happens because Mellanox ODP implementation seems to be based on
message retransmissions when the adapter doesn't know about the buffer yet -
it likely uses standard "RNR retransmissions" (RNR = receiver not ready)
which is generally slow in RDMA/RoCE networks. Here's a presentation about
it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
ODP support is retained in the code just in case a good ODP implementation
appears one day.
info_ru: |
Использовать RDMA с On-Demand Paging. ODP - функция, доступная пока что
исключительно на адаптерах Mellanox ConnectX-4 и более новых. ODP позволяет
не регистрировать память для её использования RDMA-картой. Благодаря этому
можно не копировать данные при отправке их в сеть и, казалось бы, это должно
улучшать производительность - но **по факту** получается так, что
производительность только ухудшается, причём сильно. Пример - на 3-узловом
кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на чтение с RDMA без ODP
удаётся снять 3950000 iops, а с ODP - всего 239000 iops...
Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и
основана на повторной передаче сообщений, когда карте не известен буфер -
вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready).
А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука.
Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf
Возможность использования ODP сохранена в коде на случай, если вдруг в один
прекрасный день появится хорошая реализация ODP.
- name: peer_connect_interval
type: sec
min: 1
+46 -14
View File
@@ -253,21 +253,33 @@
type: bool
default: true
info: |
This parameter makes Vitastor always keep metadata area of the block device
in memory. It's required for good performance because it allows to avoid
additional read-modify-write cycles during metadata modifications. Metadata
area size is currently roughly 224 MB per 1 TB of data. You can turn it off
to reduce memory usage by this value, but it will hurt performance. This
restriction is likely to be removed in the future along with the upgrade
of the metadata storage scheme.
Only for the old store ([meta_format](layout-osd.en.md#meta_format) 2).
This parameter makes Vitastor keep a copy of metadata area in memory as it is
on disk, in addition to the metadata database. When the option is enabled, every
metadata entry is effectively stored in RAM twice. It's required for good performance
because it allows to avoid additional read-modify-write cycles during metadata
modifications. Metadata area size with the old store is roughly 224 MB per 1 TB
of data. You can turn the option off to reduce memory usage by this value, but
it will reduce performance.
For the new store ([meta_format](layout-osd.en.md#meta_format) 3), the option
may be changed in the future to support operation without loading full metadata
database in memory.
info_ru: |
Данный параметр заставляет Vitastor всегда держать область метаданных диска
в памяти. Это нужно, чтобы избегать дополнительных операций чтения с диска
при записи. Размер области метаданных на данный момент составляет примерно
224 МБ на 1 ТБ данных. При включении потребление памяти снизится примерно
на эту величину, но при этом также снизится и производительность. В будущем,
после обновления схемы хранения метаданных, это ограничение, скорее всего,
будет ликвидировано.
Только для старого хранилища ([meta_format](layout-osd.en.md#meta_format) 2).
Данный параметр заставляет Vitastor всегда держать копию области метаданных
в памяти в том же виде, как она лежит на диске, в дополнение к БД метаданных.
То есть, с включённой опцией каждая запись метаданных хранится в памяти дважды.
Это нужно, чтобы избегать дополнительных операций чтения с диска при записи.
Размер области метаданных в старом хранилище составляет примерно 224 МБ на
1 ТБ данных. Вы можете отключить опцию, чтобы снизить потребление памяти
примерно на эту величину, но при этом также снизится и производительность.
Для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3) опция,
возможно, будет переработана в будущем для поддержки работы без полной
загрузки метаданных в памяти.
- name: inmemory_journal
type: bool
default: true
@@ -386,11 +398,15 @@
blocks. The only situation when you should increase it to a larger value
is when you enable journal_no_same_sector_overwrites. In this case set
it to, for example, 1024.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
info_ru: |
Максимальное число буферов, разрешённых для использования под записываемые
в журнал блоки метаданных. Единственная ситуация, в которой этот параметр
нужно менять - это если вы включаете journal_no_same_sector_overwrites. В
этом случае установите данный параметр, например, в 1024.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
- name: journal_no_same_sector_overwrites
type: bool
default: false
@@ -402,6 +418,8 @@
journal after writing it instead of possibly overwriting it the second time.
Most (99%) other SSDs don't need this option.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
info_ru: |
Включайте данную опцию для SSD вроде Intel D3-S4510 и D3-S4610, которые
ОЧЕНЬ не любят, когда ПО перезаписывает один и тот же сектор несколько раз
@@ -412,6 +430,20 @@
самого сектора.
Почти все другие SSD (99% моделей) не требуют данной опции.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
- name: skip_corrupted_meta_entries
type: bool
default: false
info: |
Only for the new store ([meta_format](layout-osd.en.md#meta_format) 3).
Allow OSD to start when some metadata entries or blocks are corrupted by
skipping them. Should be only used as an emergency measure.
info_ru: |
Только для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3).
Разрешить OSD запускаться, даже если часть блоков или записей метаданных
повреждена, пропуская их. Опция предназначена для использования только в
целях аварийного восстановления.
- name: throttle_small_writes
type: bool
default: false
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"yaml": "^2.8.2"
}
}
+5
View File
@@ -0,0 +1,5 @@
# Security Parameters
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
+7
View File
@@ -0,0 +1,7 @@
# Параметры безопасности
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
OSD, мониторами и клиентами.
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
поддерживает онлайн-изменение.
+131
View File
@@ -0,0 +1,131 @@
- name: etcd_client_cert
type: string
info: |
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
etcd https connections. May be path to a file or just a PEM string with certificate.
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
info_ru: |
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
сертификатом в формате PEM. В последнем случае строка должна начинаться с
"-----BEGIN CERTIFICATE-----".
- name: etcd_client_key
type: string
info: Private key for etcd_client_cert (also a file or a PEM string).
info_ru: Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
- name: etcd_ca
type: string
info: |
Trusted TLS CA to verify etcd server certificate. May be path to a file,
directory or just a PEM string with certificate.
info_ru: |
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
Может быть путём к файлу, директории или просто строкой с сертификатом в
формате PEM.
- name: osd_etcd_client_cert
type: string
info: |
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
OSDs, clients and monitors should have different permissions, so they should
use different certificates.
info_ru: |
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
использовать разные сертификаты.
- name: osd_etcd_client_key
type: string
info: Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
- name: mon_etcd_client_cert
type: string
info: Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
info_ru: Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
- name: mon_etcd_client_key
type: string
info: Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
- name: vault_url
type: string
info: |
Vault base URL.
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
Encryption is performed by the client, OSDs don't have access to decrypted data.
Encryption keys may be stored in etcd or, for the increased security level, in an external
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
instance.
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
In that case, only key IDs are stored in etcd.
info_ru: |
Базовый адрес Vault.
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
В этом случае, только ID ключей хранятся в etcd.
- name: vault_secret_api_path
type: string
default: /v1/secret/
info: Vault v1 secret API mount path to use.
info_ru: Путь к API секретов v1 для использования клиентами.
- name: vault_client_cert
type: string
info: |
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
may be path to a file or just a certificate in PEM string.
info_ru: |
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
может быть путём к файлу или просто PEM-строкой с сертификатом.
- name: vault_client_key
type: string
info: Private key for vault_client_cert (also a file or a PEM string).
info_ru: Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
- name: vault_ca
type: string
info: |
Trusted TLS CA to verify Vault server certificate. May be path to a file,
directory or just a PEM string with certificate.
info_ru: |
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
Может быть путём к файлу, директории или просто строкой с сертификатом в
формате PEM.
- name: vault_timeout_ms
type: int
default: 5000
info: Timeout for Vault requests in milliseconds.
info_ru: Максимально время выполнения Vault-запросов в миллисекундах.
- name: vault_error_timeout_sec
type: int
default: 60
info: |
Time (in seconds) to wait before retrying after receiving an error from Vault.
info_ru: |
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
- name: vault_refresh_leeway_sec
type: int
default: 60
info: |
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
in case of system clock drift.
info_ru: |
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
lease_timeout, на случай "ухода" системных часов.
- name: max_aes_xts_pool_size
type: int
default: 256
info: |
Maximum number of OpenSSL encryption contexts cached in OSD memory. Probably
doesn't require modification.
info_ru: |
Максимальное количество кэшируемых в памяти OSD контекстов шифрования OpenSSL.
Вряд ли требует изменения.
+27 -3
View File
@@ -26,13 +26,37 @@ 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.2`
`docker pull vitalif/vitastor:v3.0.5`
2. Install scripts to the host system: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.2 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.5 install.sh`
3. Reload udev rules: \
`udevadm control --reload-rules`
4. Enable the vitastor-host service: \
`systemctl enable --now vitastor-host`
And you can return to [Quick Start](../intro/quickstart.en.md).
After these steps, you can return to [Quick Start](../intro/quickstart.en.md).
## Podman
If you use Podman, run the following commands as root before installing Vitastor containers:
```
ln -s podman /usr/bin/docker
mkdir -p /etc/systemd/system/systemd-udevd.service.d
cat >/etc/systemd/system/systemd-udevd.service.d/override.conf <<EOF
[Service]
CapabilityBoundingSet=~
SystemCallFilter=@mount capset
EOF
systemctl daemon-reload
systemctl restart systemd-udevd
```
Without it, udev fails to do calls into a Podman container and Vitastor disk detection doesn't work.
## Upgrading Containers
+27 -2
View File
@@ -25,14 +25,39 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
Инструкция по установке максимально простая.
1. Скачайте Docker-образ желаемой версии: \
`docker pull vitalif/vitastor:v3.0.2`
`docker pull vitalif/vitastor:v3.0.5`
2. Установите скрипты в хост-систему командой: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.2 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.5 install.sh`
3. Перезагрузите правила udev: \
`udevadm control --reload-rules`
4. Включите сервис vitastor-host: \
`systemctl enable --now vitastor-host`
После этого вы можете возвращаться к разделу [Быстрый старт](../intro/quickstart.ru.md).
## Podman
Если вы используете Podman, перед установкой контейнеров Vitastor выполните следующие
команды от имени суперпользователя:
```
ln -s podman /usr/bin/docker
mkdir -p /etc/systemd/system/systemd-udevd.service.d
cat >/etc/systemd/system/systemd-udevd.service.d/override.conf <<EOF
[Service]
CapabilityBoundingSet=~
SystemCallFilter=@mount capset
EOF
systemctl daemon-reload
systemctl restart systemd-udevd
```
Без этих настроек udev не может делать вызовы внутрь Podman-контейнеров и определение дисков Vitastor не работает.
## Обновление контейнеров
Сначала обязательно проверьте раздел [Обновление Vitastor](../usage/admin.ru.md#обновление-vitastor),
+3 -1
View File
@@ -33,15 +33,17 @@
- CentOS 7: `yum install https://vitastor.io/rpms/centos/7/vitastor-release.rpm`
- CentOS 8: `dnf install https://vitastor.io/rpms/centos/8/vitastor-release.rpm`
- AlmaLinux 9 and other RHEL 9 clones (Rocky, Oracle...): `dnf install https://vitastor.io/rpms/centos/9/vitastor-release.rpm`
- AlmaLinux 10 and other RHEL 10 clones: `dnf install https://vitastor.io/rpms/centos/10/vitastor-release.rpm`
- Enable EPEL: `yum/dnf install epel-release`
- Enable additional CentOS repositories:
- CentOS 7: `yum install centos-release-scl`
- CentOS 8: `dnf install centos-release-advanced-virtualization`
- RHEL 9 clones: not required
- RHEL 9/10 clones: not required
- Enable elrepo-kernel:
- CentOS 7: `yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm`
- CentOS 8: `dnf install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm`
- RHEL 9 clones: `dnf install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm`
- RHEL 10 clones: not required
- Install packages: `yum/dnf install vitastor lpsolve etcd kernel-ml qemu-kvm`
## Installation requirements
+3 -1
View File
@@ -33,15 +33,17 @@
- CentOS 7: `yum install https://vitastor.io/rpms/centos/7/vitastor-release.rpm`
- CentOS 8: `dnf install https://vitastor.io/rpms/centos/8/vitastor-release.rpm`
- AlmaLinux 9 и другие клоны RHEL 9 (Rocky, Oracle...): `dnf install https://vitastor.io/rpms/centos/9/vitastor-release.rpm`
- AlmaLinux 10 и другие клоны RHEL 10: `dnf install https://vitastor.io/rpms/centos/10/vitastor-release.rpm`
- Включите EPEL: `yum/dnf install epel-release`
- Включите дополнительные репозитории CentOS:
- CentOS 7: `yum install centos-release-scl`
- CentOS 8: `dnf install centos-release-advanced-virtualization`
- Клоны RHEL 9: не нужно
- Клоны RHEL 9/10: не нужно
- Включите elrepo-kernel:
- CentOS 7: `yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm`
- CentOS 8: `dnf install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm`
- Клоны RHEL 9: `dnf install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm`
- Клоны RHEL 10: не нужно
- Установите пакеты: `yum/dnf install vitastor lpsolve etcd kernel-ml qemu-kvm`
## Установочные требования
+2 -2
View File
@@ -15,8 +15,8 @@
- gcc and g++ 8 or newer, clang 10 or newer, or other compiler with C++11 plus
designated initializers support from C++20
- CMake
- jerasure headers and libraries
- ISA-L, libibverbs and librdmacm headers and libraries (optional)
- jerasure, c-ares headers and libraries
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
- tcmalloc (google-perftools-dev)
## Basic instructions
+2 -2
View File
@@ -15,8 +15,8 @@
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
назначенных инициализаторов (designated initializers) из C++20
- CMake
- Заголовки и библиотеки jerasure
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm
- Заголовки и библиотеки jerasure, c-ares
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
- tcmalloc (google-perftools-dev)
## Базовая инструкция
+2
View File
@@ -41,6 +41,8 @@
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
- [S3](../installation/s3.en.md)
- [TLS support for etcd connections](../config/security.en.md)
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
## Plugins and tools
+2
View File
@@ -43,6 +43,8 @@
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
- [S3](../installation/s3.ru.md)
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
## Драйверы и инструменты
+21 -7
View File
@@ -125,18 +125,31 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
## create
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
Pool must be specified if there is more than one pool.
Create an image. Options:
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
If parent is not a snapshot, it must be a read-only image.
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
```
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
```
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
Options:
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
By default, the image retains its old encryption key when taking a snapshot.
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
@@ -151,6 +164,7 @@ You should resize file system in the image, if present, before shrinking it.
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
## dd
+22 -8
View File
@@ -127,19 +127,32 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
## create
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
Создать образ. Опции:
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
```
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
```
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
клиентов, если пишущий клиент максимум 1.
Создать снимок образа `<image>` (можно использовать любую форму команды).
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
Опции:
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
@@ -156,6 +169,7 @@ vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
## dd
+11 -7
View File
@@ -18,7 +18,7 @@ class AntiEtcdAdapter
cluster = cluster ? (''+(cluster||'')).split(/,+/) : [];
cluster = Object.keys(cluster.reduce((a, url) =>
{
a[url.toLowerCase().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '')] = true;
a[url.toLowerCase().replace(/^(https?:\/\/)?(.*?)(\/.*)?$/, (m, m1, m2) => (m1||'http://')+m2)] = true;
return a;
}, {}));
const cfg_port = config.antietcd_port;
@@ -26,7 +26,8 @@ class AntiEtcdAdapter
is_local['0.0.0.0'] = true;
is_local['::'] = true;
is_local[''] = true;
const selected = cluster.map(s => s.split(':', 2)).filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
// split :, 3 -> <schema>:<//ip>:<port>
const selected = cluster.map(s => s.split(':', 3)).filter(ip => is_local[ip[1].substr(2)] && (!cfg_port || ip[2] == cfg_port));
if (selected.length > 1)
{
console.error('More than 1 etcd_address matches local IPs, please specify port');
@@ -35,12 +36,15 @@ class AntiEtcdAdapter
else if (selected.length == 1)
{
const antietcd_config = {
ip: selected[0][0],
port: selected[0][1],
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
ip: selected[0][1].substr(2),
port: selected[0][2],
cert: config.antietcd_cert,
key: config.antietcd_key,
ca: config.etcd_ca,
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][2]+'.json.gz'),
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
node_id: selected[0][1].substr(2)+':'+selected[0][2], // node_id = ip:port
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
cluster_key: (config.etcd_prefix || '/vitastor'),
stale_read: 1,
log_level: 1,
+27 -6
View File
@@ -1,7 +1,9 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
const fs = require('fs');
const http = require('http');
const https = require('https');
const WebSocket = require('ws');
const { b64, local_ips } = require('./utils.js');
@@ -15,11 +17,30 @@ class EtcdAdapter
this.ws = null;
this.ws_alive = false;
this.ws_keepalive_timer = null;
this.opts = {};
}
parse_config(config)
{
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
if (config.mon_etcd_client_cert || config.etcd_client_cert)
{
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
if (this.opts.cert.substr(0, 5) != '-----')
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
}
if (config.mon_etcd_client_key || config.etcd_client_key)
{
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
if (this.opts.key.substr(0, 5) != '-----')
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
}
if (config.etcd_ca)
{
this.opts.ca = config.etcd_ca;
if (this.opts.ca.substr(0, 5) != '-----')
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
}
}
parse_etcd_addresses(addrs)
@@ -39,7 +60,7 @@ class EtcdAdapter
for (let url of addrs)
{
let scheme = 'http';
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
const slash = url.indexOf('/');
const colon = url.indexOf(':');
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
@@ -130,7 +151,7 @@ class EtcdAdapter
}
ok(false);
}, this.mon.config.etcd_mon_timeout);
this.ws = new WebSocket(base+'/watch');
this.ws = new WebSocket(base+'/watch', this.opts);
this.ws_used_url = cur_addr;
const fail = () =>
{
@@ -272,7 +293,7 @@ class EtcdAdapter
{
throw new Error(MON_STOPPED);
}
const res = await POST(base+path, body, timeout);
const res = await POST(base+path, body, timeout, this.opts);
if (this.mon.stopped)
{
throw new Error(MON_STOPPED);
@@ -298,7 +319,7 @@ class EtcdAdapter
}
}
function POST(url, body, timeout)
function POST(url, body, timeout, opts)
{
return new Promise(ok =>
{
@@ -310,10 +331,10 @@ function POST(url, body, timeout)
req = null;
ok({ error: 'timeout' });
}, timeout) : null;
let req = http.request(url, { method: 'POST', headers: {
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
'Content-Type': 'application/json',
'Content-Length': body_text.length,
} }, (res) =>
}, ...(opts||{}) }, (res) =>
{
if (!req)
{
+11 -1
View File
@@ -45,7 +45,14 @@ const etcd_tree = {
config_path: "/etc/vitastor/vitastor.conf",
etcd_prefix: "/vitastor",
// etcd connection - configurable online
etcd_address: "10.0.115.10:2379/v3",
etcd_address: "http://10.0.115.10:2379/v3",
etcd_client_cert: "",
etcd_client_key: "",
osd_etcd_client_cert: "",
osd_etcd_client_key: "",
mon_etcd_client_cert: "",
mon_etcd_client_key: "",
etcd_ca: "",
// mon
etcd_mon_ttl: 5, // min: 1
etcd_mon_timeout: 1000, // ms. min: 0
@@ -217,6 +224,8 @@ const etcd_tree = {
parent_id?: <inode_t>,
readonly?: boolean,
deleted?: boolean,
enc_key?: string,
meta?: any,
}
}
}, */
@@ -383,6 +392,7 @@ 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
@@ -16,7 +16,7 @@ async function create_http_server(cfg, handler)
};
if (cfg.mon_https_ca)
{
tls.mon_https_ca = await fsp.readFile(cfg.mon_https_ca);
tls.ca = await fsp.readFile(cfg.mon_https_ca);
}
if (cfg.mon_https_client_auth)
{
+8 -5
View File
@@ -10,16 +10,19 @@ const NO_OSD = 'Z';
async function lp_solve(text)
{
const cp = child_process.spawn('lp_solve');
let stdout = '', stderr = '', finish_cb;
let stdout = '', stderr = '', finish_cb, finished = 0;
cp.stdout.on('data', buf => stdout += buf.toString());
cp.stderr.on('data', buf => stderr += buf.toString());
cp.on('exit', () => finish_cb && finish_cb());
cp.stdout.on('end', () => finish_cb());
cp.stderr.on('end', () => finish_cb());
cp.stdin.write(text);
cp.stdin.end();
if (cp.exitCode == null)
await new Promise(ok => (finish_cb = () =>
{
await new Promise(ok => finish_cb = ok);
}
finished++;
if (finished == 2)
ok();
}));
if (!stdout.trim())
{
return null;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor-mon",
"version": "3.0.2",
"version": "3.0.5",
"description": "Vitastor SDS monitor service",
"main": "mon-main.js",
"scripts": {
@@ -9,7 +9,7 @@
"author": "Vitaliy Filippov",
"license": "UNLICENSED",
"dependencies": {
"antietcd": "^1.2.2",
"antietcd": "^1.2.4",
"sprintf-js": "^1.1.2",
"ws": "^7.2.5"
},
+1 -1
View File
@@ -195,7 +195,7 @@ async function generate_pool_pgs(state, global_config, pool_id, osd_tree, levels
const folded_tree = make_hier_tree(global_config, folded.nodes.reduce((a, c) => { a[c.id] = c; return a; }, {}));
const old_pg_count = prev_pgs.length;
const optimize_cfg = {
osd_weights: folded.nodes.reduce((a, c) => { if (Number(c.id)) { a[c.id] = c.size; } return a; }, {}),
osd_weights: folded.nodes.reduce((a, c) => { if (/^\d+$/.exec(c.id) && c.size != null) { a[c.id] = c.size||0; } return a; }, {}),
combinator: use_rules
// new algorithm:
? new RuleCombinator(folded_tree, rules, pool_cfg.max_osd_combinations)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor",
"version": "3.0.2",
"version": "3.0.5",
"description": "Low-level native bindings to Vitastor client library",
"main": "index.js",
"keywords": [
+30 -232
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.2'
VITASTOR_VERSION = '3.0.5'
LOG = logging.getLogger(__name__)
@@ -275,7 +275,7 @@ class VitastorDriver(driver.CloneableImageVD,
LOG.exception('error getting vitastor pool stats: '+str(e))
self._stats = stats
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, run update the stats first.
@@ -291,6 +291,14 @@ class VitastorDriver(driver.CloneableImageVD,
else:
return (1 + resp['kvs'][0]['value'], resp['kvs'][0]['mod_revision'])
def _cli(self, descr, *args):
args = [ 'vitastor-cli', *args, *(self._vitastor_args()) ]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to "+descr+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
def create_volume(self, volume):
"""Creates a logical volume."""
@@ -302,7 +310,7 @@ class VitastorDriver(driver.CloneableImageVD,
LOG.debug("creating volume '%s'", vol_name)
self._create_image(vol_name, { 'size': size })
self._cli('create volume', 'create', vol_name, '--size', size)
if volume.encryption_key_id:
self._create_encrypted_volume(volume, volume.obj_context)
@@ -346,7 +354,7 @@ class VitastorDriver(driver.CloneableImageVD,
snap_name = utils.convert_str(snapshot.name)
if snap_name.find('@') >= 0 or snap_name.find('/') >= 0:
raise exception.VolumeBackendAPIException(data = '@ and / are forbidden in volume and snapshot names')
self._create_snapshot(vol_name, vol_name+'@'+snap_name)
self._cli('create snapshot', 'snap-create', vol_name+'@'+snap_name)
def snapshot_revert_use_temp_snapshot(self):
"""Disable the use of a temporary snapshot on revert."""
@@ -359,21 +367,8 @@ class VitastorDriver(driver.CloneableImageVD,
snap_name = utils.convert_str(snapshot.name)
# Delete the image and recreate it from the snapshot
args = [ 'vitastor-cli', 'rm', vol_name, *(self._vitastor_args()) ]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to delete image "+vol_name+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
args = [
'vitastor-cli', 'create', '--parent', vol_name+'@'+snap_name,
vol_name, *(self._vitastor_args())
]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to recreate image "+vol_name+" from "+vol_name+"@"+snap_name+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
self._cli('delete image', 'rm', vol_name)
self._cli('recreate image', 'create', '--parent', vol_name+'@'+snap_name, vol_name)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
@@ -381,15 +376,7 @@ class VitastorDriver(driver.CloneableImageVD,
vol_name = utils.convert_str(snapshot.volume_name)
snap_name = utils.convert_str(snapshot.name)
args = [
'vitastor-cli', 'rm', vol_name+'@'+snap_name,
*(self._vitastor_args())
]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to remove snapshot "+vol_name+'@'+snap_name+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
self._cli('remove snapshot', 'rm', vol_name+'@'+snap_name)
def _child_count(self, parents):
children = 0
@@ -427,13 +414,7 @@ class VitastorDriver(driver.CloneableImageVD,
if src_vref.admin_metadata.get('readonly') == 'True':
# source volume is a volume-image cache entry or other readonly volume
# clone without intermediate snapshot
src = self._get_image(src_name)
LOG.debug("creating image '%s' from '%s'", dest_name, src_name)
new_cfg = self._create_image(dest_name, {
'size': size,
'parent_id': src['idx']['id'],
'parent_pool_id': src['idx']['pool_id'],
})
self._cli('create clone', 'create', '--parent', src_name, '--size', size, dest_name)
return {}
clone_snap = "%s@%s.clone_snap" % (src_name, dest_name)
@@ -446,15 +427,12 @@ class VitastorDriver(driver.CloneableImageVD,
clone_snap = dest_name
make_img = False
LOG.debug("creating layer '%s' under '%s'", clone_snap, src_name)
new_cfg = self._create_snapshot(src_name, clone_snap, True)
LOG.debug("creating snapshot '%s'", clone_snap)
self._cli('create base snapshot', 'snap-create', '--allow-existing', '1', clone_snap)
if make_img:
# Then create a clone from it
new_cfg = self._create_image(dest_name, {
'size': size,
'parent_id': new_cfg['parent_id'],
'parent_pool_id': new_cfg['parent_pool_id'],
})
self._cli('create clone', 'create', '--parent', clone_snap, '--size', size, dest_name)
return {}
@@ -464,7 +442,8 @@ class VitastorDriver(driver.CloneableImageVD,
vol_name = utils.convert_str(volume.name)
snap_name = utils.convert_str(snapshot.name)
snap = self._get_image('volume-'+snapshot.volume_id+'@'+snap_name)
src_snap = 'volume-'+snapshot.volume_id+'@'+snap_name
snap = self._get_image(src_snap)
if not snap:
raise exception.SnapshotNotFound(snapshot_id = snap_name)
snap_inode_id = int(resp['responses'][0]['kvs'][0]['value']['id'])
@@ -473,12 +452,8 @@ class VitastorDriver(driver.CloneableImageVD,
size = snap['cfg']['size']
if int(volume.size):
size = int(volume.size) * units.Gi
new_cfg = self._create_image(vol_name, {
'size': size,
'parent_id': snap['idx']['id'],
'parent_pool_id': snap['idx']['pool_id'],
})
self._cli('create clone', 'create', vol_name, '--size', size, '--parent', src_snap)
return {}
def _vitastor_args(self):
@@ -505,49 +480,7 @@ class VitastorDriver(driver.CloneableImageVD,
"""Deletes a logical volume."""
vol_name = utils.convert_str(volume.name)
# Find the volume and all its snapshots
range_end = b'index/image/' + vol_name.encode('utf-8')
range_end = range_end[0 : len(range_end)-1] + six.int2byte(range_end[len(range_end)-1] + 1)
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'index/image/'+vol_name, 'range_end': range_end } },
] })
if len(resp['responses'][0]['kvs']) == 0:
# already deleted
LOG.info("volume %s no longer exists in backend", vol_name)
return
layers = resp['responses'][0]['kvs']
layer_ids = {}
for kv in layers:
inode_id = int(kv['value']['id'])
pool_id = int(kv['value']['pool_id'])
inode_pool_id = (pool_id << 48) | (inode_id & 0xffffffffffff)
layer_ids[inode_pool_id] = True
# Check if the volume has clones and raise 'busy' if so
children = self._child_count(layer_ids)
if children > 0:
raise exception.VolumeIsBusy(volume_name = vol_name)
# Clear data
for kv in layers:
args = [
'vitastor-cli', 'rm-data', '--pool', str(kv['value']['pool_id']),
'--inode', str(kv['value']['id']), '--progress', '0',
*(self._vitastor_args())
]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to remove layer "+kv['key']+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
# Delete all layers from etcd
requests = []
for kv in layers:
requests.append({ 'request_delete_range': { 'key': kv['key'] } })
requests.append({ 'request_delete_range': { 'key': 'config/inode/'+str(kv['value']['pool_id'])+'/'+str(kv['value']['id']) } })
self._etcd_txn({ 'success': requests })
self._cli('delete volume', 'rm', '--matching', vol_name, vol_name+'@*', '--progress', '0')
def retype(self, context, volume, new_type, diff, host):
"""Change extra type specifications for a volume."""
@@ -567,98 +500,6 @@ class VitastorDriver(driver.CloneableImageVD,
"""Removes an export for a logical volume."""
pass
def _create_image(self, vol_name, cfg):
pool_s = str(self.cfg['pool_id'])
image_id = 0
while image_id == 0:
# check if the image already exists and find a free ID
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'index/image/'+vol_name } },
{ 'request_range': { 'key': 'index/maxid/'+pool_s } },
] })
if len(resp['responses'][0]['kvs']) > 0:
# already exists
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' already exists')
image_id, id_mod = self._next_id(resp['responses'][1])
# try to create the image
resp = self._etcd_txn({ 'compare': [
{ 'target': 'MOD', 'mod_revision': id_mod, 'key': 'index/maxid/'+pool_s },
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+vol_name },
{ 'target': 'VERSION', 'version': 0, 'key': 'config/inode/'+pool_s+'/'+str(image_id) },
], 'success': [
{ 'request_put': { 'key': 'index/maxid/'+pool_s, 'value': image_id } },
{ 'request_put': { 'key': 'index/image/'+vol_name, 'value': json.dumps({
'id': image_id, 'pool_id': self.cfg['pool_id']
}) } },
{ 'request_put': { 'key': 'config/inode/'+pool_s+'/'+str(image_id), 'value': json.dumps({
**cfg, 'name': vol_name,
}) } },
] })
if not resp.get('succeeded'):
# repeat
image_id = 0
def _create_snapshot(self, vol_name, snap_vol_name, allow_existing = False):
while True:
# check if the image already exists and snapshot doesn't
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'index/image/'+vol_name } },
{ 'request_range': { 'key': 'index/image/'+snap_vol_name } },
] })
if len(resp['responses'][0]['kvs']) == 0:
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
if len(resp['responses'][1]['kvs']) > 0:
if allow_existing:
snap_idx = resp['responses'][1]['kvs'][0]['value']
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'config/inode/'+str(snap_idx['pool_id'])+'/'+str(snap_idx['id']) } },
] })
if len(resp['responses'][0]['kvs']) == 0:
raise exception.VolumeBackendAPIException(data =
'Volume '+snap_vol_name+' is already indexed, but does not exist'
)
return resp['responses'][0]['kvs'][0]['value']
raise exception.VolumeBackendAPIException(
data = 'Volume '+snap_vol_name+' already exists'
)
vol_idx = resp['responses'][0]['kvs'][0]['value']
vol_idx_mod = resp['responses'][0]['kvs'][0]['mod_revision']
# get image inode config and find a new ID
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']) } },
{ 'request_range': { 'key': 'index/maxid/'+str(self.cfg['pool_id']) } },
] })
if len(resp['responses'][0]['kvs']) == 0:
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
vol_cfg = resp['responses'][0]['kvs'][0]['value']
vol_mod = resp['responses'][0]['kvs'][0]['mod_revision']
new_id, id_mod = self._next_id(resp['responses'][1])
# try to redirect image to the new inode
new_cfg = {
**vol_cfg, 'name': vol_name, 'parent_id': vol_idx['id'], 'parent_pool_id': vol_idx['pool_id']
}
resp = self._etcd_txn({ 'compare': [
{ 'target': 'MOD', 'mod_revision': vol_idx_mod, 'key': 'index/image/'+vol_name },
{ 'target': 'MOD', 'mod_revision': vol_mod, 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']) },
{ 'target': 'MOD', 'mod_revision': id_mod, 'key': 'index/maxid/'+str(self.cfg['pool_id']) },
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+snap_vol_name },
{ 'target': 'VERSION', 'version': 0, 'key': 'config/inode/'+str(self.cfg['pool_id'])+'/'+str(new_id) },
], 'success': [
{ 'request_put': { 'key': 'index/maxid/'+str(self.cfg['pool_id']), 'value': new_id } },
{ 'request_put': { 'key': 'index/image/'+vol_name, 'value': json.dumps({
'id': new_id, 'pool_id': self.cfg['pool_id']
}) } },
{ 'request_put': { 'key': 'config/inode/'+str(self.cfg['pool_id'])+'/'+str(new_id), 'value': json.dumps(new_cfg) } },
{ 'request_put': { 'key': 'index/image/'+snap_vol_name, 'value': json.dumps({
'id': vol_idx['id'], 'pool_id': vol_idx['pool_id']
}) } },
{ 'request_put': { 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']), 'value': json.dumps({
**vol_cfg, 'name': snap_vol_name, 'readonly': True
}) } }
] })
if resp.get('succeeded'):
return new_cfg
def initialize_connection(self, volume, connector):
data = {
'driver_volume_type': 'vitastor',
@@ -697,13 +538,9 @@ class VitastorDriver(driver.CloneableImageVD,
size = int(volume.size) * units.Gi
dest_name = utils.convert_str(volume.name)
# Find or create the base snapshot
snap_cfg = self._create_snapshot(base_vol.name, base_vol.name+'@.clone_snap', True)
self._cli('create base snapshot', 'create', '--allow-existing', '1', base_vol.name+'@.clone_snap')
# Then create a clone from it
new_cfg = self._create_image(dest_name, {
'size': size,
'parent_id': snap_cfg['parent_id'],
'parent_pool_id': snap_cfg['parent_pool_id'],
})
self._cli('create clone', 'create', dest_name, '--size', size, '--parent', base_vol.name+'@.clone_snap')
return ({}, True)
return ({}, False)
@@ -770,26 +607,8 @@ class VitastorDriver(driver.CloneableImageVD,
def extend_volume(self, volume, new_size):
"""Extend an existing volume."""
vol_name = utils.convert_str(volume.name)
while True:
vol = self._get_image(vol_name)
if not vol:
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
# change size
size = int(new_size) * units.Gi
if size == vol['cfg']['size']:
break
resp = self._etcd_txn({ 'compare': [ {
'target': 'MOD',
'mod_revision': vol['cfg_mod'],
'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
} ], 'success': [
{ 'request_put': {
'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
'value': json.dumps({ **vol['cfg'], 'size': size }),
} },
] })
if resp.get('succeeded'):
break
size = int(new_size) * units.Gi
self._cli('extend volume', 'modify', vol_name, '--resize', new_size)
LOG.debug(
"Extend volume from %(old_size)s GB to %(new_size)s GB.",
{'old_size': volume.size, 'new_size': new_size}
@@ -862,28 +681,7 @@ class VitastorDriver(driver.CloneableImageVD,
"""
from_name = self._get_existing_name(existing_ref)
to_name = utils.convert_str(volume.name)
self._rename(from_name, to_name)
def _rename(self, from_name, to_name):
while True:
vol = self._get_image(from_name)
if not vol:
raise exception.VolumeBackendAPIException(data = 'Volume '+from_name+' does not exist')
to = self._get_image(to_name)
if to:
raise exception.VolumeBackendAPIException(data = 'Volume '+to_name+' already exists')
resp = self._etcd_txn({ 'compare': [
{ 'target': 'MOD', 'mod_revision': vol['idx_mod'], 'key': 'index/image/'+vol['cfg']['name'] },
{ 'target': 'MOD', 'mod_revision': vol['cfg_mod'], 'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']) },
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+to_name },
], 'success': [
{ 'request_delete_range': { 'key': 'index/image/'+vol['cfg']['name'] } },
{ 'request_put': { 'key': 'index/image/'+to_name, 'value': json.dumps(vol['idx']) } },
{ 'request_put': { 'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
'value': json.dumps({ **vol['cfg'], 'name': to_name }) } },
] })
if resp.get('succeeded'):
break
self._cli('rename', 'modify', from_name, '--rename', to_name)
def unmanage(self, volume):
pass
@@ -956,7 +754,7 @@ class VitastorDriver(driver.CloneableImageVD,
snap_name = self._get_existing_name(existing_ref)
from_name = vol_name+'@'+snap_name
to_name = vol_name+'@'+utils.convert_str(snapshot.name)
self._rename(from_name, to_name)
self._cli('rename', 'modify', from_name, '--rename', to_name)
def unmanage_snapshot(self, snapshot):
"""Removes the specified snapshot from Cinder management."""
+1 -1
View File
@@ -21,7 +21,7 @@ rpmbuild -bp fio.spec
cd $VITASTOR
VER=$(grep ^Version: rpm/vitastor-$REL.spec | awk '{print $2}')
rm -rf fio
ln -s ~/rpmbuild/BUILD/fio*/ fio
ln -s $(ls -d ~/rpmbuild/BUILD/fio*/ | grep -v SPECPARTS) fio
sh copy-fio-includes.sh
rm fio
mv fio-copy fio
+17
View File
@@ -0,0 +1,17 @@
# Build packages for AlmaLinux 10 inside a container
# cd ..
# docker pull --platform=linux/amd64/v2 quay.io/almalinuxorg/almalinux:10
# docker build -t vitastor-buildenv:el10 -f rpm/vitastor-el10.Dockerfile .
# docker run -i --rm -v ./:/root/vitastor vitastor-buildenv:el10 /root/vitastor/rpm/vitastor-build.sh
FROM quay.io/almalinuxorg/almalinux:10
WORKDIR /root
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
RUN dnf -y install epel-release dnf-plugins-core
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
+199
View File
@@ -0,0 +1,199 @@
Name: vitastor
Version: 3.0.5
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
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
BuildRequires: nodejs >= 10
BuildRequires: jerasure-devel
BuildRequires: isa-l-devel
BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
Requires: vitastor-client-devel = %{version}-%{release}
Requires: vitastor-fio = %{version}-%{release}
%description
Vitastor is a small, simple and fast clustered block storage (storage for VM drives),
architecturally similar to Ceph which means strong consistency, primary-replication,
symmetric clustering and automatic data distribution over any number of drives of any
size with configurable redundancy (replication or erasure codes/XOR).
%package -n vitastor-osd
Summary: Vitastor - OSD
Requires: vitastor-client = %{version}-%{release}
Requires: util-linux
Requires: parted
%description -n vitastor-osd
Vitastor object storage daemon, i.e. server program that stores data.
%package -n vitastor-mon
Summary: Vitastor - monitor
Requires: nodejs >= 10
Requires: lpsolve
%description -n vitastor-mon
Vitastor monitor, i.e. server program responsible for watching cluster state and
scheduling cluster-level operations.
%package -n vitastor-client
Summary: Vitastor - client
%description -n vitastor-client
Vitastor client library and command-line interface.
%package -n vitastor-client-devel
Summary: Vitastor - development files
Group: Development/Libraries
Requires: vitastor-client = %{version}-%{release}
%description -n vitastor-client-devel
Vitastor library headers for development.
%package -n vitastor-fio
Summary: Vitastor - fio drivers
Group: Development/Libraries
Requires: vitastor-client = %{version}-%{release}
Requires: fio = 3.36-5.el10
%description -n vitastor-fio
Vitastor fio drivers for benchmarking.
%package -n vitastor-opennebula
Summary: Vitastor for OpenNebula
Group: Development/Libraries
Requires: vitastor-client
Requires: jq
Requires: python3-lxml
Requires: patch
Requires: qemu-kvm-block-vitastor
%description -n vitastor-opennebula
Vitastor storage plugin for OpenNebula.
%prep
%setup -q
%build
%cmake
%cmake_build
%install
rm -rf $RPM_BUILD_ROOT
%cmake_install
cd mon
npm install --production
cd ..
mkdir -p %buildroot/usr/lib/vitastor
cp -r mon %buildroot/usr/lib/vitastor
mv %buildroot/usr/lib/vitastor/mon/scripts/make-etcd %buildroot/usr/lib/vitastor/mon/
mkdir -p %buildroot/lib/systemd/system
cp mon/scripts/vitastor.target mon/scripts/vitastor-mon.service mon/scripts/vitastor-osd@.service %buildroot/lib/systemd/system
mkdir -p %buildroot/lib/udev/rules.d
cp mon/scripts/90-vitastor.rules %buildroot/lib/udev/rules.d
mkdir -p %buildroot/var/lib/one
cp -r opennebula/remotes %buildroot/var/lib/one
cp opennebula/install.sh %buildroot/var/lib/one/remotes/datastore/vitastor/
mkdir -p %buildroot/etc/
cp -r opennebula/sudoers.d %buildroot/etc/
%files
%doc GPL-2.0.txt VNPL-1.1.txt README.md README-ru.md
%files -n vitastor-osd
%_bindir/vitastor-osd
%_bindir/vitastor-disk
%_bindir/vitastor-dump-journal
/lib/systemd/system/vitastor-osd@.service
/lib/systemd/system/vitastor.target
/lib/udev/rules.d/90-vitastor.rules
%pre -n vitastor-osd
groupadd -r -f vitastor 2>/dev/null ||:
useradd -r -g vitastor -s /sbin/nologin -c "Vitastor daemons" -M -d /nonexistent vitastor 2>/dev/null ||:
install -o vitastor -g vitastor -d /var/log/vitastor
mkdir -p /etc/vitastor
%files -n vitastor-mon
/usr/lib/vitastor/mon
/lib/systemd/system/vitastor-mon.service
%pre -n vitastor-mon
groupadd -r -f vitastor 2>/dev/null ||:
useradd -r -g vitastor -s /sbin/nologin -c "Vitastor daemons" -M -d /nonexistent vitastor 2>/dev/null ||:
mkdir -p /etc/vitastor
mkdir -p /var/lib/vitastor
chown vitastor:vitastor /var/lib/vitastor
%files -n vitastor-client
%_bindir/vitastor-nbd
%_bindir/vitastor-ublk
%_bindir/vitastor-nfs
%_bindir/vitastor-cli
%_bindir/vitastor-rm
%_bindir/vitastor-kv
%_bindir/vitastor-kv-stress
%_bindir/vita
%_libdir/libvitastor_client.so*
%_libdir/libvitastor_kv.so*
%files -n vitastor-client-devel
%_includedir/vitastor_c.h
%_includedir/vitastor_kv.h
%_libdir/pkgconfig
%files -n vitastor-fio
%_libdir/libfio_vitastor.so
%_libdir/libfio_vitastor_blk.so
%_libdir/libfio_vitastor_sec.so
%files -n vitastor-opennebula
/var/lib/one
/etc/sudoers.d/opennebula-vitastor
%triggerin -n vitastor-opennebula -- opennebula
[ $2 = 0 ] || exit 0
/var/lib/one/remotes/datastore/vitastor/install.sh
# Turn off the brp-python-bytecompile script
%global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g')
%changelog
+1 -1
View File
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
+3 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.2
Version: 3.0.5
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.2.el7.tar.gz
Source0: vitastor-3.0.5.el7.tar.gz
BuildRequires: gperftools-devel
BuildRequires: devtoolset-9-gcc-c++
@@ -17,6 +17,7 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake3
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+1 -1
View File
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
+3 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.2
Version: 3.0.5
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.2.el8.tar.gz
Source0: vitastor-3.0.5.el8.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-toolset-9-gcc-c++
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+1 -1
View File
@@ -10,7 +10,7 @@ WORKDIR /root
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
RUN dnf -y install epel-release dnf-plugins-core
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
+3 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.2
Version: 3.0.5
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.2.el9.tar.gz
Source0: vitastor-3.0.5.el9.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
+9 -1
View File
@@ -21,7 +21,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
endif()
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
add_definitions(-DVITASTOR_VERSION="3.0.2")
add_definitions(-DVITASTOR_VERSION="3.0.5")
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})
@@ -75,6 +75,14 @@ if (RDMACM_LIBRARIES)
add_definitions(-DWITH_RDMACM)
endif (RDMACM_LIBRARIES)
find_package(OpenSSL REQUIRED)
if (OPENSSL_FOUND)
add_definitions(-DWITH_OPENSSL)
endif (OPENSSL_FOUND)
pkg_check_modules(CARES REQUIRED libcares)
include_directories(${CARES_INCLUDE_DIRS})
if (${WITH_SYSTEM_LIBURING})
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
include_directories(${LIBURING_INCLUDE_DIRS})
+1 -1
View File
@@ -4,7 +4,7 @@ project(vitastor)
# libvitastor_blk.a
add_library(vitastor_blk STATIC
../util/allocator.cpp ../util/crc32c.c ../util/ringloop.cpp
../util/allocator.cpp ../util/crc32c.c ../util/xxhash.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
-1
View File
@@ -187,7 +187,6 @@ public:
// MUST be called only when nobody makes any modifications to the DB for this pool
virtual void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit) = 0;
virtual bool reshard_continue(void *reshard_state, uint64_t chunk_limit) = 0;
virtual void reshard_abort(void *reshard_state) = 0;
// Event loop
virtual void loop() = 0;
+6 -4
View File
@@ -83,13 +83,17 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
{
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
}
else if (config["data_csum_type"] == "xxh3_32")
{
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
}
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
{
data_csum_type = BLOCKSTORE_CSUM_NONE;
}
else
{
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
}
csum_block_size = parse_size(config["csum_block_size"]);
discard_on_start = config.find("discard_on_start") != config.end() &&
@@ -173,9 +177,7 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
}
if (data_block_size / bitmap_granularity < 8)
{
fprintf(stderr, "Warning: block_size (%u) / bitmap_granularity (%u) = %u bits. "
"Consider using larger block_size or bitmap_granularity for better performance.\n",
data_block_size, bitmap_granularity, data_block_size / bitmap_granularity);
throw std::runtime_error("Data block size must be at least bitmap_granularity*8");
}
if (!data_csum_type)
{
+4 -3
View File
@@ -16,6 +16,7 @@
#define BLOCKSTORE_CSUM_NONE 0
// Lower byte of checksum type is its length
#define BLOCKSTORE_CSUM_CRC32C 0x104
#define BLOCKSTORE_CSUM_XXH3_32 0x204
#define MOCK_DATA_FD 1000
#define MOCK_META_FD 1001
@@ -26,14 +27,14 @@ class allocator_t;
struct blockstore_disk_t
{
std::string data_device, meta_device, journal_device;
uint32_t data_block_size;
uint64_t data_block_size;
uint64_t cfg_journal_size, cfg_data_size;
// Required write alignment and journal/metadata/data areas' location alignment
uint32_t disk_alignment = 4096;
// Journal block size - minimum_io_size of the journal device is the best choice
uint32_t journal_block_size = 4096;
uint64_t journal_block_size = 4096;
// Metadata block size - minimum_io_size of the metadata device is the best choice
uint32_t meta_block_size = 4096;
uint64_t meta_block_size = 4096;
// Atomic write size of the data block device
uint32_t atomic_write_size = 4096;
// Whether we should set RWF_ATOMIC on atomic writes
+232 -134
View File
@@ -12,6 +12,7 @@
#include "blockstore_heap.h"
#include "../util/allocator.h"
#include "../util/crc32c.h"
#include "../util/xxhash.h"
#include "../util/malloc_or_die.h"
#define BS_HEAP_FREE_MVCC 1
@@ -62,19 +63,19 @@ uint32_t blockstore_heap_t::get_simple_entry_size()
uint32_t blockstore_heap_t::get_big_entry_size()
{
return sizeof(heap_big_write_t) + dsk->clean_entry_bitmap_size*2 +
(!dsk->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
}
uint32_t blockstore_heap_t::get_big_intent_entry_size()
{
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
}
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
{
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
}
@@ -89,7 +90,7 @@ uint32_t blockstore_heap_t::get_csum_size(heap_entry_t *wr)
uint32_t blockstore_heap_t::get_csum_size(uint32_t entry_type, uint32_t offset, uint32_t len)
{
if (!dsk->data_csum_type)
if (!dsk->csum_block_size)
{
return 0;
}
@@ -212,15 +213,24 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
big().block_num = location / heap->dsk->data_block_size;
}
uint32_t heap_entry_t::calc_crc32c()
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
{
auto old_crc32c = crc32c;
crc32c = 0;
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
crc32c = old_crc32c;
auto old_checksum = checksum;
checksum = 0;
uint32_t res = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
res = (uint32_t)XXH3_64bits(this, size);
else
res = ::crc32c(0, (uint8_t*)this, size);
checksum = old_checksum;
return res;
}
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
{
return calc_checksum(heap->dsk);
}
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
{
uint64_t pg_num = 0;
@@ -301,12 +311,13 @@ int blockstore_heap_t::read_blocks(uint64_t disk_offset, uint64_t disk_size, uin
heap_entry_t *wr = (heap_entry_t*)data;
if (wr->size > dsk->meta_block_size-block_offset)
{
fprintf(stderr, "Error: entry is too large in metadata block %u at %u (%u > max %u bytes). ",
fprintf(stderr, "Error: entry is too large in metadata block %u at %u (%u > max %ju bytes). ",
block_num, block_offset, wr->size, dsk->meta_block_size-block_offset);
corrupted_block:
if (allow_corrupted)
{
fprintf(stderr, "Metadata block is corrupted, skipping\n");
recheck_modified_blocks.insert(block_num);
break;
}
else
@@ -331,7 +342,19 @@ corrupted_block:
block_num, block_offset, wr->size, sizeof(heap_entry_t));
goto corrupted_block;
}
wr->entry_type &= ~BS_HEAP_GARBAGE;
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;
}
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)) ||
@@ -345,6 +368,7 @@ corrupted_object:
if (allow_corrupted)
{
fprintf(stderr, "Entry is corrupted, skipping\n");
recheck_modified_blocks.insert(block_num);
block_offset += wr->size;
continue;
}
@@ -360,7 +384,7 @@ corrupted_object:
{
// Small writes require accessing offset & len to calculate correct length,
// so require at least sizeof(heap_small_write_t) for them
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u < min %zu bytes). Metadata is corrupted, aborting\n",
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u < min %zu bytes)\n",
wr->inode, wr->stripe, wr->version, block_num, block_offset, wr->size, sizeof(heap_small_write_t));
goto corrupted_object;
}
@@ -371,12 +395,12 @@ corrupted_object:
goto corrupted_object;
}
// Verify crc
uint32_t expected_crc32c = wr->calc_crc32c();
if (wr->crc32c != expected_crc32c)
uint32_t expected_checksum = wr->calc_checksum(this);
if (wr->checksum != expected_checksum)
{
fprintf(stderr, "Error: entry %jx:%jx v%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). Metadata is corrupted, aborting\n",
wr->inode, wr->stripe, wr->version,
block_num, block_offset, expected_crc32c, wr->crc32c);
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
wr->inode, wr->stripe, wr->version, wr->lsn,
block_num, block_offset, expected_checksum, wr->checksum);
goto corrupted_object;
}
// Verify offset & len
@@ -385,7 +409,7 @@ corrupted_object:
wr->small().offset % dsk->bitmap_granularity ||
wr->small().len % dsk->bitmap_granularity))
{
fprintf(stderr, "Error: %s entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters, aborting\n",
fprintf(stderr, "Error: %s entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters. ",
wr->type() == BS_HEAP_SMALL_WRITE ? "small_write" : "intent_write",
wr->inode, wr->stripe, wr->version, wr->small().offset, wr->small().len);
goto corrupted_object;
@@ -395,7 +419,7 @@ corrupted_object:
wr->big_intent().offset % dsk->bitmap_granularity ||
wr->big_intent().len % dsk->bitmap_granularity))
{
fprintf(stderr, "Error: big_intent entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters, aborting\n",
fprintf(stderr, "Error: big_intent entry %jx:%jx v%ju has invalid offset/length: %u/%u. Metadata is incompatible with current parameters. ",
wr->inode, wr->stripe, wr->version, wr->big_intent().offset, wr->big_intent().len);
goto corrupted_object;
}
@@ -422,7 +446,7 @@ int blockstore_heap_t::load_blocks(uint64_t disk_offset, uint64_t size, uint8_t
next_lsn = wr->lsn;
}
entries_loaded++;
insert_list_item(li);
loaded_list_items.push_back(li);
modify_alloc(block_num, [&](heap_block_info_t & inf)
{
if (!inf.entries.size())
@@ -458,19 +482,22 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
next_wr = wr;
if (wr->type() == BS_HEAP_ROLLBACK)
{
if (commit_wr && wr->version > commit_wr->version)
{
// rollback may not come before commit with a smaller version
fprintf(stderr, "Error: rollback entry %jx:%jx v%ju l%ju comes before a commit entry v%ju l%ju\n",
wr->inode, wr->stripe, wr->version, wr->lsn, commit_wr->version, commit_wr->lsn);
return false;
}
rollback_wr = wr;
continue;
}
if (wr->type() == BS_HEAP_COMMIT)
{
commit_wr = wr;
if (commit_wr && wr->version > commit_wr->version)
{
// commit may not come before commit with a smaller version
fprintf(stderr, "Error: commit entry %jx:%jx v%ju l%ju comes before a commit entry v%ju l%ju\n",
wr->inode, wr->stripe, wr->version, wr->lsn, commit_wr->version, commit_wr->lsn);
return false;
}
if (!commit_wr)
{
commit_wr = wr;
}
continue;
}
if (wr->entry_type & BS_HEAP_STABLE)
@@ -523,6 +550,23 @@ bool blockstore_heap_t::validate_object(heap_entry_t *obj)
return true;
}
void blockstore_heap_t::finish_load()
{
if (loaded_list_items.size())
{
// Sort everything and load in correct order
std::sort(loaded_list_items.begin(), loaded_list_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
{
return a->entry.lsn < b->entry.lsn;
});
for (auto & li: loaded_list_items)
{
insert_list_item(li);
}
loaded_list_items.clear();
}
}
void blockstore_heap_t::fill_recheck_queue()
{
for (auto & pgp: block_index)
@@ -709,6 +753,7 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
}
if (!recheck_queue_filled)
{
finish_load();
fill_recheck_queue();
recheck_queue_filled = true;
}
@@ -800,7 +845,7 @@ std::vector<uint32_t> blockstore_heap_t::get_recheck_modified_blocks()
return modified;
}
int blockstore_heap_t::finish_load(bool allow_corrupted)
int blockstore_heap_t::finish_recheck()
{
if (!marked_used_blocks)
{
@@ -837,14 +882,17 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
{
return true;
}
uint32_t len = 0;
if (wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE)
len = wr->small().len;
else if (wr->type() == BS_HEAP_BIG_INTENT)
len = wr->big_intent().len;
else
assert(0);
uint32_t real_csum = crc32c(0, data, len);
uint32_t real_csum = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
real_csum = (uint32_t)XXH3_64bits(data, len);
else
real_csum = crc32c(0, data, len);
if (set)
{
*wr_csum = real_csum;
@@ -854,13 +902,14 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
}
if (wr->type() == BS_HEAP_BIG_WRITE)
{
assert(offset != UINT32_MAX && len != UINT32_MAX);
return calc_block_checksums((uint32_t*)(wr->get_checksums(this) + offset/dsk->csum_block_size * (dsk->data_csum_type & 0xFF)),
data, wr->get_int_bitmap(this), offset, offset+len, set, NULL);
}
if (wr->type() == BS_HEAP_BIG_INTENT)
{
auto & bi = wr->big_intent();
return calc_block_checksums((uint32_t*)(wr->get_checksums(this) + offset/dsk->csum_block_size * (dsk->data_csum_type & 0xFF)),
return calc_block_checksums((uint32_t*)(wr->get_checksums(this) + bi.offset/dsk->csum_block_size * (dsk->data_csum_type & 0xFF)),
data, wr->get_int_bitmap(this), bi.offset, bi.offset+bi.len, set, NULL);
}
assert(wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE);
@@ -893,11 +942,26 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
return prev_crc;
}
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
{
uint32_t cur_len = 0;
while (size > 0)
{
uint8_t *data = next(pos, cur_len);
assert(data);
cur_len = (cur_len < size ? cur_len : size);
XXH3_64bits_update(xxh3_state, data, cur_len);
pos += cur_len;
size -= cur_len;
}
}
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
{
bool res = true;
XXH3_state_t* xxh3_state = NULL;
uint32_t pos = start;
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
uint32_t block_crc = 0;
@@ -914,42 +978,89 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
pos += dsk->bitmap_granularity;
// zero padding at the beginning or at the end of the block is not counted
if (pos > prev && prev > 0 && pos < block_end)
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
{
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
uint32_t zeropad = pos-prev;
while (zeropad > 0)
{
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
zeropad -= zerolen;
}
}
else
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
}
prev = pos;
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
pos += dsk->bitmap_granularity;
if (pos > prev)
{
isset = true;
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
xxh3_iter(xxh3_state, next, prev, pos-prev);
}
else
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
}
prev = pos;
}
}
else
{
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
}
else
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
pos = (end > block_end ? block_end : end);
isset = true;
}
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
{
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
XXH3_64bits_reset(xxh3_state);
}
if (set)
{
*block_csums = block_crc;
}
else if (isset && block_crc != *block_csums)
{
res = false;
if (bad_block_cb)
{
bad_block_cb(blk_start, *block_csums, block_crc);
res = false;
}
else
return false;
break;
}
block_end += dsk->csum_block_size;
block_crc = 0;
block_csums++;
}
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
{
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
XXH3_freeState(xxh3_state);
xxh3_state = NULL;
}
return res;
}
@@ -1077,16 +1188,6 @@ bool blockstore_heap_t::reshard_check(pool_id_t pool, uint32_t pg_count, uint32_
set_it->second.pg_stripe_size == pg_stripe_size);
}
void blockstore_heap_t::reshard_abort(void* reshard_state)
{
heap_reshard_state_t *st = (heap_reshard_state_t*)reshard_state;
for (auto sh_it = st->old_shards.begin(); sh_it != st->old_shards.end(); sh_it++)
{
block_index[sh_it->first] = std::move(sh_it->second);
}
delete st;
}
heap_entry_t *blockstore_heap_t::lock_and_read_entry(object_id oid)
{
auto obj = read_entry(oid);
@@ -1099,27 +1200,6 @@ heap_entry_t *blockstore_heap_t::lock_and_read_entry(object_id oid)
return obj;
}
heap_entry_t *blockstore_heap_t::read_locked_entry(object_id oid, uint64_t lsn)
{
auto obj = read_entry(oid);
assert(obj);
for (auto wr = obj; wr; wr = prev(wr))
{
if (wr->is_overwrite())
{
if (lsn == wr->lsn)
{
return obj;
}
else
{
obj = prev(wr);
}
}
}
return NULL;
}
bool blockstore_heap_t::unlock_entry(object_id oid)
{
auto mvcc_it = object_mvcc.find(oid);
@@ -1156,6 +1236,35 @@ heap_entry_t *blockstore_heap_t::read_entry(object_id oid)
return &li->entry;
}
void blockstore_heap_t::gc_block(heap_block_info_t & inf)
{
if (inf.has_garbage)
{
size_t i = 0, j = 0;
for (; i < inf.entries.size(); i++)
{
if (inf.entries[i]->entry.is_garbage())
{
// old entry invalidated by a newer one, mark it as freeable on block write
// assign a 'virtual' LSN to track GC completion
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn);
uint64_t gc_lsn = ++next_lsn;
inf.mod_lsn = inf.mod_lsn ? inf.mod_lsn : gc_lsn;
inf.mod_lsn_to = gc_lsn;
push_inflight_lsn(gc_lsn, &inf.entries[i]->entry, HEAP_INFLIGHT_GC);
}
else
{
if (j != i)
inf.entries[j] = inf.entries[i];
j++;
}
}
inf.entries.resize(j);
inf.has_garbage = false;
}
}
int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free)
{
if (last_allocated_block != UINT32_MAX)
@@ -1222,31 +1331,7 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
}
// Write into the same block
auto & inf = block_info.at(last_allocated_block);
if (inf.has_garbage)
{
size_t i = 0, j = 0;
for (; i < inf.entries.size(); i++)
{
if (inf.entries[i]->entry.is_garbage())
{
// old entry invalidated by a newer one, mark it as freeable on block write
// assign a 'virtual' LSN to track GC completion
assert(!inf.mod_lsn_to || inf.mod_lsn_to == next_lsn);
uint64_t gc_lsn = ++next_lsn;
inf.mod_lsn = inf.mod_lsn ? inf.mod_lsn : gc_lsn;
inf.mod_lsn_to = gc_lsn;
push_inflight_lsn(gc_lsn, &inf.entries[i]->entry, HEAP_INFLIGHT_GC);
}
else
{
if (j != i)
inf.entries[j] = inf.entries[i];
j++;
}
}
inf.entries.resize(j);
inf.has_garbage = false;
}
gc_block(inf);
*block_num = last_allocated_block;
modify_alloc(last_allocated_block, [&](heap_block_info_t & inf)
{
@@ -1327,7 +1412,7 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
insert_list_item(li);
li->block_num = block_num;
new_wr->size = wr_size;
new_wr->crc32c = new_wr->calc_crc32c();
new_wr->checksum = new_wr->calc_checksum(this);
return 0;
}
@@ -1387,7 +1472,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->data_csum_type)
if (dsk->csum_block_size)
{
memset(wr->get_checksums(this), 0, get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true, offset, len);
@@ -1416,9 +1501,9 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->data_csum_type)
if (dsk->csum_block_size)
memset(wr->get_checksums(this), 0, get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true, offset, len);
calc_checksums(wr, (uint8_t*)data, true);
*obj_ptr = wr;
});
}
@@ -1454,14 +1539,14 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->data_csum_type)
if (dsk->csum_block_size)
{
if (checksums)
memcpy(wr->get_checksums(this), checksums, dsk->clean_entry_bitmap_size);
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
else
{
memcpy(wr->get_checksums(this), obj->get_checksums(this), dsk->clean_entry_bitmap_size);
calc_checksums(wr, (uint8_t*)data, true, offset, len);
memcpy(wr->get_checksums(this), obj->get_checksums(this), get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true);
}
}
else
@@ -1501,7 +1586,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
new_wr->set_big_location(this, compact_location);
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
if (dsk->data_csum_type && new_csums)
if (dsk->csum_block_size && new_csums)
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
});
}
@@ -1582,7 +1667,7 @@ int blockstore_heap_t::add_commit(heap_entry_t *obj, uint64_t version, uint32_t
}
if (!uncommitted)
{
return EBUSY;
return 0;
}
return add_simple(obj, version, modified_block, BS_HEAP_COMMIT);
}
@@ -1592,23 +1677,32 @@ int blockstore_heap_t::add_rollback(heap_entry_t *obj, uint64_t version, uint32_
heap_entry_t *wr = obj;
bool found_uncommitted = false;
uint64_t commit_version = 0;
while (wr && !wr->is_overwrite())
uint64_t rollback_version = UINT64_MAX;
while (wr)
{
if (wr->type() == BS_HEAP_ROLLBACK)
{
auto rollback_version = wr->version;
wr = prev(wr);
while (wr->version > rollback_version)
if (wr->version <= version)
{
assert(!(wr->entry_type & BS_HEAP_STABLE));
wr = prev(wr);
// All previous writes are already rolled back, stop
break;
}
rollback_version = wr->version;
wr = prev(wr);
continue;
}
if (wr->type() == BS_HEAP_COMMIT)
{
if (commit_version < wr->version)
{
commit_version = wr->version;
}
wr = prev(wr);
continue;
}
if (wr->version > rollback_version)
{
// Already rolled back, skip
wr = prev(wr);
continue;
}
@@ -1619,14 +1713,10 @@ int blockstore_heap_t::add_rollback(heap_entry_t *obj, uint64_t version, uint32_
{
return EBUSY;
}
else if (wr->version == version)
else
{
break;
}
else if (wr->version < version)
{
return ENOENT;
}
}
else if (wr->version > version)
{
@@ -1807,9 +1897,9 @@ void blockstore_heap_t::iterate_with_stable(heap_entry_t *obj, uint64_t max_lsn,
}
else
{
// 1) 1 2 3 ROLLBACK(2) COMMIT(3) -> impossible
// 1) 1 2 3 ROLLBACK(2) COMMIT(3) -> 3 is unstable
// 2) 1 2 3 4 ROLLBACK(3) COMMIT(2) -> OK
// 3) 1 2 3 ROLLBACK(2) 3 COMMIT(3) -> first 3 shouldn't be treated as stable
// 3) 1 2 3 ROLLBACK(2) 3 COMMIT(3) -> first 3 is unstable
// 4) 1 2 3 COMMIT(3) ROLLBACK(2) -> impossible
// I.e. a rollback always has version >= previous commit
// 5) 1 2 3 4 5 ROLLBACK(4) 5 ROLLBACK(3)
@@ -1966,24 +2056,21 @@ int blockstore_heap_t::list_objects(uint32_t pg_num, object_id min_oid, object_i
return;
}
uint64_t stable_version = 0;
auto first_wr = obj;
for (auto wr = first_wr; wr; wr = prev(wr))
iterate_with_stable(obj, UINT64_MAX, [&](heap_entry_t* wr, bool stable)
{
if ((wr->entry_type & BS_HEAP_STABLE) || wr->type() == BS_HEAP_COMMIT || wr->type() == BS_HEAP_ROLLBACK)
if (stable)
{
stable_version = wr->version;
break;
return false;
}
else
if (unstable_size >= unstable_alloc)
{
if (unstable_size >= unstable_alloc)
{
unstable_alloc = (!unstable_alloc ? 128 : unstable_alloc*2);
unstable = (obj_ver_id*)realloc_or_die(unstable, sizeof(obj_ver_id) * unstable_alloc);
}
unstable[unstable_size++] = (obj_ver_id){ .oid = oid, .version = wr->version };
unstable_alloc = (!unstable_alloc ? 128 : unstable_alloc*2);
unstable = (obj_ver_id*)realloc_or_die(unstable, sizeof(obj_ver_id) * unstable_alloc);
}
}
unstable[unstable_size++] = (obj_ver_id){ .oid = oid, .version = wr->version };
return true;
});
if (stable_version)
{
if (res_size >= res_alloc)
@@ -2045,7 +2132,13 @@ void blockstore_heap_t::free_data(inode_t inode, uint64_t location)
inode = (INODE_POOL(inode) << POOL_ID_BITS);
assert(data_alloc->get(location / dsk->data_block_size));
data_alloc->set(location / dsk->data_block_size, false);
inode_space_stats[inode] -= dsk->data_block_size;
auto sp_it = inode_space_stats.find(inode);
if (sp_it != inode_space_stats.end())
{
sp_it->second -= dsk->data_block_size;
if (sp_it->second == 0)
inode_space_stats.erase(sp_it);
}
data_used_space -= dsk->data_block_size;
}
@@ -2274,7 +2367,9 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
}
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;
@@ -2328,7 +2423,7 @@ void blockstore_heap_t::set_no_inode_stats(const std::vector<uint64_t> & pool_id
{
// Recalculate if changed
if (ps.second.no_inode_stats == 2 || ps.second.no_inode_stats == 1)
recalc_inode_space_stats(ps.first, ps.second.no_inode_stats == 1);
recalc_inode_space_stats(ps.first, ps.second.no_inode_stats == 2);
ps.second.no_inode_stats &= 1;
}
}
@@ -2339,8 +2434,8 @@ void blockstore_heap_t::recalc_inode_space_stats(uint64_t pool_id, bool per_inod
auto sp_begin = inode_space_stats.lower_bound((pool_id << (64-POOL_ID_BITS)));
auto sp_end = inode_space_stats.lower_bound(((pool_id+1) << (64-POOL_ID_BITS)));
inode_space_stats.erase(sp_begin, sp_end);
uint32_t pg_count = ps.pg_count ? ps.pg_count : 1;
for (uint32_t pg_num = 1; pg_num <= pg_count; pg_num++)
uint32_t pg_count = ps.pg_count;
for (uint32_t pg_num = pg_count ? 1 : 0; pg_num <= pg_count; pg_num++)
{
auto & pg_idx = block_index[(pool_id << (64-POOL_ID_BITS)) | pg_num];
for (auto & ip: pg_idx)
@@ -2378,7 +2473,10 @@ void inode_map_get(void *inode_idx, heap_inode_map_t::iterator & li_it, heap_lis
size_t map_n = ((size_t)inode_idx & IMAP_MALLOC_LOW_BITS);
if (!map_n)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
li_it = ((heap_inode_map_t*)inode_idx)->find(list_item_key(&stripe));
#pragma GCC diagnostic pop
li = li_it != ((heap_inode_map_t*)inode_idx)->end() ? *li_it : NULL;
}
else if (map_n == 1)
+11 -10
View File
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
{
uint16_t size;
uint16_t entry_type;
uint32_t crc32c;
uint32_t checksum;
uint64_t lsn;
uint64_t inode;
uint64_t stripe;
@@ -69,7 +69,8 @@ struct __attribute__((__packed__)) heap_entry_t
uint32_t *get_checksum(blockstore_heap_t *heap);
uint64_t big_location(blockstore_heap_t *heap);
void set_big_location(blockstore_heap_t *heap, uint64_t location);
uint32_t calc_crc32c();
uint32_t calc_checksum(blockstore_heap_t *heap);
uint32_t calc_checksum(blockstore_disk_t *dsk);
};
struct __attribute__((__packed__)) heap_small_write_t
@@ -80,7 +81,7 @@ struct __attribute__((__packed__)) heap_small_write_t
uint32_t offset;
uint32_t len;
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
};
struct __attribute__((__packed__)) heap_big_write_t
@@ -98,7 +99,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
uint32_t offset;
uint32_t len;
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
};
struct __attribute__((__packed__)) heap_list_item_t
@@ -200,6 +201,7 @@ class blockstore_heap_t
bool marked_used_blocks = false;
bool recheck_queue_filled = false;
std::vector<heap_list_item_t*> loaded_list_items;
std::set<uint32_t> recheck_modified_blocks;
std::deque<heap_entry_t*> recheck_queue;
int recheck_in_progress = 0;
@@ -215,6 +217,7 @@ class blockstore_heap_t
void defragment_block(uint32_t block_num);
void reshard_add(heap_reshard_state_t *st, heap_list_item_t *li);
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);
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
@@ -237,31 +240,29 @@ public:
std::function<void(uint32_t, uint32_t, uint8_t*)> handle_block);
int load_blocks(uint64_t disk_offset, uint64_t size, uint8_t *buf,
bool allow_corrupted, uint64_t &entries_loaded);
// finish loading
int finish_load(bool allow_corrupted = false);
// finish loading - should be called after load_blocks
void finish_load();
// get blocks which are modified during loading and should be written to the disk
// before finishing initialization if not R/O
std::vector<uint32_t> get_recheck_modified_blocks();
// recheck small write data after reading the database from disk
bool recheck_small_writes(std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> read_buffer, int queue_depth);
int finish_recheck();
// reshard database according to the pool's PG count
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
bool reshard_continue(void* reshard_state, uint64_t chunk_limit);
bool reshard_check(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size);
void reshard_abort(void* reshard_state);
void set_no_inode_stats(const std::vector<uint64_t> & pool_ids);
void recalc_inode_space_stats(uint64_t pool_id, bool per_inode);
// read an object entry and lock it against removal
// in the future, may become asynchronous
heap_entry_t *lock_and_read_entry(object_id oid);
// re-read a locked object entry with the given lsn (pointer may be invalidated)
heap_entry_t *read_locked_entry(object_id oid, uint64_t lsn);
// read an object entry without locking it
heap_entry_t *read_entry(object_id oid);
// unlock an entry
bool unlock_entry(object_id oid);
// set or verify checksums in a write request
bool calc_checksums(heap_entry_t *wr, uint8_t *data, bool set, uint32_t offset = 0, uint32_t len = 0);
bool calc_checksums(heap_entry_t *wr, uint8_t *data, bool set, uint32_t offset = UINT32_MAX, uint32_t len = UINT32_MAX);
// set or verify raw block checksums
bool calc_block_checksums(uint32_t *block_csums, uint8_t *data, uint8_t *bitmap, uint32_t start, uint32_t end,
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb);
+1 -6
View File
@@ -193,12 +193,12 @@ void blockstore_impl_t::loop()
heap->start_block_write(block_num);
mb.sent = true;
}
pending_modified_blocks.clear();
int ret = ringloop->submit();
if (ret < 0)
{
throw std::runtime_error(std::string("io_uring_submit: ") + strerror(-ret));
}
pending_modified_blocks.clear();
if ((initial_ring_space - ringloop->space_left()) > 0)
{
live = true;
@@ -406,8 +406,3 @@ bool blockstore_impl_t::reshard_continue(void *reshard_state, uint64_t chunk_lim
{
return heap->reshard_continue(reshard_state, chunk_limit);
}
void blockstore_impl_t::reshard_abort(void *reshard_state)
{
return heap->reshard_abort(reshard_state);
}
+1 -1
View File
@@ -78,6 +78,7 @@ public:
// Suitable only for server SSDs with capacitors, requires disabled data and journal fsyncs
int immediate_commit = IMMEDIATE_NONE;
bool inmemory_meta = false;
bool skip_corrupted_meta_entries = false;
uint32_t meta_write_recheck_parallelism = 0;
// Maximum and minimum flusher count
unsigned max_flusher_count = 0, min_flusher_count = 0;
@@ -191,7 +192,6 @@ public:
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
bool reshard_continue(void *reshard_state, uint64_t chunk_limit);
void reshard_abort(void *reshard_state);
// Event loop
void loop();
+4 -3
View File
@@ -145,7 +145,7 @@ resume_1:
printf(
"Configuration stored in metadata superblock"
" (meta_block_size=%u, data_block_size=%u, bitmap_granularity=%u, data_csum_type=%u, csum_block_size=%u, meta_area_size=%ju)"
" differs from OSD configuration (%u/%u/%u, %u/%u, %ju).\n",
" differs from OSD configuration (%ju/%ju/%u, %u/%u, %ju).\n",
hdr->meta_block_size, hdr->data_block_size, hdr->bitmap_granularity,
hdr->data_csum_type, hdr->csum_block_size, hdr->meta_area_size,
bs->dsk.meta_block_size, bs->dsk.data_block_size, bs->dsk.bitmap_granularity,
@@ -225,7 +225,7 @@ resume_4:
{
// Handle result
uint64_t loaded = 0;
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, false, loaded);
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
if (r != 0)
exit(1);
entries_loaded += loaded;
@@ -239,6 +239,7 @@ resume_4:
return 1;
}
// 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);
if (zero_on_init && !bs->dsk.disable_meta_fsync)
{
@@ -284,7 +285,7 @@ resume_6:
}, bs->meta_write_recheck_parallelism);
return 1;
resume_7:
if (bs->heap->finish_load() != 0)
if (bs->heap->finish_recheck() != 0)
{
exit(1);
}
+1
View File
@@ -28,6 +28,7 @@ void blockstore_impl_t::parse_config(blockstore_config_t & config, bool init)
throttle_target_parallelism = strtoull(config["throttle_target_parallelism"].c_str(), NULL, 10);
throttle_threshold_us = strtoull(config["throttle_threshold_us"].c_str(), NULL, 10);
perfect_csum_update = config["perfect_csum_update"] == "true" || config["perfect_csum_update"] == "1" || config["perfect_csum_update"] == "yes";
skip_corrupted_meta_entries = config["skip_corrupted_meta_entries"] == "true" || config["skip_corrupted_meta_entries"] == "1" || config["skip_corrupted_meta_entries"] == "yes";
if (config["autosync_writes"] != "")
{
autosync_writes = strtoull(config["autosync_writes"].c_str(), NULL, 10);
+1 -1
View File
@@ -57,9 +57,9 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
}
assert(res == 0);
}
resume_1:
if (priv->modified_block != UINT32_MAX && priv->modified_block2 != priv->modified_block)
{
resume_1:
BS_SUBMIT_CHECK_SQES(1);
prepare_meta_block_write(priv->modified_block);
resume_2:
+10 -7
View File
@@ -22,21 +22,23 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
ring_data_t *data = ((ring_data_t*)sqe->user_data);
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, dsk.meta_block_size);
data->iov = (struct iovec){ buf, (size_t)dsk.meta_block_size };
data->callback = [this, modified_block, buf](ring_data_t *data)
data->callback = [this, modified_block](ring_data_t *data)
{
free(buf);
live = true;
if (data->res != data->iov.iov_len)
{
// FIXME: our state becomes corrupted after a write error. maybe do something better than just die
disk_error_abort("data write", data->res, data->iov.iov_len);
}
modified_blocks.erase(modified_block);
auto it = modified_blocks.find(modified_block);
assert(it != modified_blocks.end());
free(it->second.buf);
modified_blocks.erase(it);
heap->complete_block_write(modified_block);
ringloop->wakeup();
};
io_uring_prep_writev(
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + (modified_block+1)*dsk.meta_block_size
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
);
unsynced_meta_write_count++;
pending_modified_blocks.push_back(modified_block);
@@ -249,13 +251,12 @@ enospc:
goto enospc;
assert(res == 0);
PRIV(op)->lsn = obj->lsn;
if (op->len)
heap->use_buffer_area(op->oid.inode, loc, op->len);
prepare_meta_block_write(PRIV(op)->modified_block);
PRIV(op)->pending_ops++;
if (op->len > 0)
{
// Prepare buffered data write
heap->use_buffer_area(op->oid.inode, loc, op->len);
if (dsk.inmemory_journal)
{
memcpy((uint8_t*)buffer_area + loc, op->buf, op->len);
@@ -348,6 +349,7 @@ resume_12:
}
resume_4:
{
BS_SUBMIT_CHECK_SQES(1);
auto obj = heap->read_entry(op->oid);
int res = 0;
if (PRIV(op)->write_type == _REDIRECT_INTENT)
@@ -404,11 +406,12 @@ resume_6:
if (ref_us > exec_us + throttle_threshold_us)
{
// Pause reply
PRIV(op)->pending_ops++;
PRIV(op)->op_state = 7;
// Remember that the timer can in theory be called right here
tfd->set_timer_us(ref_us-exec_us, false, [this, op](int timer_id)
{
PRIV(op)->op_state = 8;
PRIV(op)->pending_ops--;
ringloop->wakeup();
});
return 1;
-11
View File
@@ -480,17 +480,6 @@ resume_1:
return true;
}
void blockstore_impl_t::reshard_abort(void *reshard_state)
{
bs_reshard_state_t *st = (bs_reshard_state_t*)reshard_state;
for (auto sh_it = st->old_shards.begin(); sh_it != st->old_shards.end(); sh_it++)
{
auto & to = clean_db_shards[sh_it->first];
to.swap(sh_it->second);
}
delete st;
}
void blockstore_impl_t::process_list(blockstore_op_t *op)
{
uint32_t list_pg = op->pg_number+1;
-1
View File
@@ -290,7 +290,6 @@ public:
// Reshard database for a pool
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
bool reshard_continue(void *reshard_state, uint64_t chunk_limit);
void reshard_abort(void *reshard_state);
// Event loop
void loop();
+1 -1
View File
@@ -189,7 +189,7 @@ resume_1:
printf(
"Configuration stored in metadata superblock"
" (meta_block_size=%u, data_block_size=%u, bitmap_granularity=%u, data_csum_type=%u, csum_block_size=%u)"
" differs from OSD configuration (%u/%u/%u, %u/%u).\n",
" differs from OSD configuration (%ju/%ju/%u, %u/%u).\n",
hdr->meta_block_size, hdr->data_block_size, hdr->bitmap_granularity,
hdr->data_csum_type, hdr->csum_block_size,
bs->dsk.meta_block_size, bs->dsk.data_block_size, bs->dsk.bitmap_granularity,
+1 -2
View File
@@ -620,8 +620,7 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
else if (from_journal)
{
// Don't scan bitmap - journal writes don't have holes (internal bitmap)!
uint8_t *csum = !dsk.csum_block_size ? 0 : (clean_entry_bitmap + dsk.clean_entry_bitmap_size +
item_start/dsk.csum_block_size*(dsk.data_csum_type & 0xFF));
uint8_t *csum = !dsk.csum_block_size ? 0 : (clean_entry_bitmap + dsk.clean_entry_bitmap_size);
if (!fulfill_read(read_op, fulfilled, item_start, item_end,
(BS_ST_BIG_WRITE | BS_ST_STABLE), 0, clean_loc + item_start, 0, csum, dyn_data))
{
+6 -3
View File
@@ -13,10 +13,10 @@ if (RDMACM_LIBRARIES)
endif (RDMACM_LIBRARIES)
add_library(vitastor_common STATIC
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp
msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
)
target_link_libraries(vitastor_common pthread)
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES})
target_compile_options(vitastor_common PUBLIC -fPIC)
# libvitastor_client.so
@@ -24,6 +24,7 @@ add_library(vitastor_client SHARED
cluster_client.cpp
cluster_client_list.cpp
cluster_client_wb.cpp
cluster_client_icache.cpp
vitastor_c.cpp
)
set_target_properties(vitastor_client PROPERTIES PUBLIC_HEADER "client/vitastor_c.h")
@@ -33,6 +34,7 @@ target_link_libraries(vitastor_client
${LIBURING_LIBRARIES}
${IBVERBS_LIBRARIES}
${RDMACM_LIBRARIES}
${OPENSSL_LIBRARIES}
)
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
configure_file(vitastor.pc.in vitastor.pc @ONLY)
@@ -98,9 +100,10 @@ endif (${WITH_QEMU})
add_executable(test_cluster_client
EXCLUDE_FROM_ALL
../test/test_cluster_client.cpp
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp
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
)
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES})
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
add_dependencies(build_tests test_cluster_client)
+95 -40
View File
@@ -62,6 +62,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
st_cli.on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
st_cli.on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
st_cli.on_reload_hook = [this]() { st_cli.load_global_config(); };
st_cli.on_inode_change_hook = [this](uint64_t inode, bool removed) { on_change_inode_hook(inode, removed); };
st_cli.parse_config(config);
st_cli.infinite_start = false;
@@ -77,6 +78,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
cluster_client_t::~cluster_client_t()
{
vault_destroy();
if (retry_timeout_id >= 0)
{
tfd->clear_timer(retry_timeout_id);
@@ -481,6 +483,8 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
self_tree_metrics.clear();
client_hostname = new_hostname;
}
// vault
vault_parse_config();
msgr.parse_config(config);
st_cli.parse_config(config);
st_cli.load_pgs();
@@ -607,6 +611,9 @@ void cluster_client_t::on_change_pool_config_hook()
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
}
}
inode_cache.clear();
inode_cache_children.clear();
vault_keys.clear();
continue_ops();
}
@@ -673,6 +680,10 @@ bool cluster_client_t::flush()
{
if (!ringloop)
{
if (vault_loading)
{
return false;
}
if (wb->writeback_queue.size())
{
wb->start_writebacks(this, 0);
@@ -695,7 +706,7 @@ bool cluster_client_t::flush()
sync_done = true;
};
execute(sync);
while (!sync_done)
while (!sync_done || vault_loading)
{
ringloop->loop();
if (!sync_done)
@@ -958,10 +969,40 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
{
op->flags |= OP_IMMEDIATE_COMMIT;
}
bool searched = false;
std::shared_ptr<inode_cache_t> icache;
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
{
if (!searched)
{
icache = inode_cache_get(op->inode);
searched = true;
}
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
{
op->retval = -EINVAL;
auto cb = std::move(op->callback);
cb(op);
return false;
}
if (icache && icache->op_enc)
{
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
}
else
op->enc.reset();
}
else
op->enc.reset();
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
{
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
if (!searched)
{
icache = inode_cache_get(op->inode);
searched = true;
}
if (icache && icache->readonly)
{
op->retval = -EROFS;
auto cb = std::move(op->callback);
@@ -972,33 +1013,39 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
op->deoptimise_snapshot = false;
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
{
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end())
if (!searched)
{
int chain_size = 0;
while (ino_it != st_cli.inode_config.end() && ino_it->second.parent_id)
icache = inode_cache_get(op->inode);
searched = true;
}
if (icache)
{
for (auto & parent: icache->chain)
{
// Check for loops - FIXME check it in etcd_state_client
if (ino_it->second.parent_id == op->inode ||
chain_size > st_cli.inode_config.size())
{
op->retval = -EINVAL;
auto cb = std::move(op->callback);
cb(op);
return false;
}
if (INODE_POOL(ino_it->second.parent_id) == INODE_POOL(ino_it->first) &&
wb->has_inode(ino_it->second.parent_id))
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
{
// Deoptimise reads - we have dirty data for one of the parent layer(s).
op->deoptimise_snapshot = true;
break;
}
chain_size++;
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
}
}
}
if (icache && icache->err_code)
{
if (icache->err_code == EPERM)
{
op->retval = -EPERM;
auto cb = std::move(op->callback);
cb(op);
return false;
}
else if (icache->err_code == EAGAIN)
{
key_wait_ops.push_back(op);
return false;
}
}
return true;
}
@@ -1121,31 +1168,33 @@ resume_2:
// because if some operations were invalid for the new PG count we'd get errors
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
{
// Check parent inode
auto ino_it = st_cli.inode_config.find(op->cur_inode);
// Skip parents from the same pool
int skipped = 0;
while (!op->deoptimise_snapshot &&
ino_it != st_cli.inode_config.end() && ino_it->second.parent_id &&
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
uint64_t next_inode = 0;
auto icache = inode_cache_get(op->cur_inode);
if (icache)
{
// Check for loops - FIXME check it in etcd_state_client
if (ino_it->second.parent_id == op->inode ||
skipped > st_cli.inode_config.size())
if (icache->has_parent_loop)
{
op->retval = -EINVAL;
erase_op(op);
return 1;
}
skipped++;
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
if (op->deoptimise_snapshot)
{
if (icache->chain.size() > 1)
next_inode = icache->chain[1];
}
else
{
if (icache->other_pool_parent_id)
next_inode = icache->other_pool_parent_id;
}
}
if (ino_it != st_cli.inode_config.end() &&
ino_it->second.parent_id &&
ino_it->second.parent_id != op->inode)
if (next_inode)
{
// Continue reading from the parent inode
op->cur_inode = ino_it->second.parent_id;
icache = inode_cache_get(next_inode);
op->cur_inode = next_inode;
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
op->parts.clear();
op->done_count = 0;
goto resume_0;
@@ -1252,7 +1301,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
// Allocate memory for the bitmap
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
unsigned bitmap_mem = object_bitmap_size +
op->parts.size() * pg_data_size *
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
// read chain info - 1 byte per block
+ (op->enc ? 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);
@@ -1407,9 +1460,9 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
int peer_fd = peer_it->second;
part->flags |= PART_SENT|PART_VALID;
op->inflight_count++;
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
);
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
+ (op->opcode == OSD_OP_READ && op->enc ? pool_cfg.data_block_size/pool_cfg.bitmap_granularity : 0));
uint64_t meta_rev = 0;
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
{
@@ -1428,6 +1481,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
.inode = op->cur_inode,
.offset = part->offset,
.len = part->len,
.flags = op->opcode == OSD_OP_READ && op->enc && !op->deoptimise_snapshot ? OSD_OP_RETURN_CHAIN : 0,
.meta_revision = meta_rev,
.version = op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE ? op->version : 0,
} },
@@ -1435,6 +1489,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
? pg_bitmap_size : 0),
.enc = op->enc,
.callback = cb ? cb : [this, part](osd_op_t *op_part)
{
handle_op_part(part);
+59 -2
View File
@@ -5,6 +5,7 @@
#include "messenger.h"
#include "etcd_state_client.h"
#include "../util/robin_hood.h"
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
#define DEFAULT_CLIENT_MAX_DIRTY_OPS 1024
@@ -71,6 +72,7 @@ protected:
cluster_op_t *prev = NULL, *next = NULL;
int prev_wait = 0;
uint64_t flush_id = 0;
std::shared_ptr<osd_op_enc_t> enc;
friend class cluster_client_t;
friend class writeback_cache_t;
};
@@ -80,6 +82,25 @@ struct inode_list_osd_t;
struct inode_list_pg_t;
class writeback_cache_t;
struct inode_cache_t
{
std::vector<inode_t> chain;
uint8_t *key_data = NULL;
osd_op_enc_t *op_enc = NULL;
bool readonly = false;
bool has_parent_loop = false;
inode_t other_pool_parent_id = 0;
int err_code = 0;
~inode_cache_t();
};
struct vault_load_key_t
{
int key_state = 0;
std::string key;
};
// FIXME: Split into public and private interfaces
class __attribute__((visibility("default"))) cluster_client_t
{
@@ -89,8 +110,8 @@ public:
timerfd_manager_t *tfd = NULL;
ring_loop_t *ringloop = NULL;
std::map<pool_id_t, uint64_t> pg_counts;
std::map<pool_pg_num_t, osd_num_t> pg_primary;
// config:
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
uint64_t client_max_dirty_bytes = 0;
uint64_t client_max_dirty_ops = 0;
@@ -102,12 +123,23 @@ public:
uint64_t client_max_writeback_iodepth = 0;
std::string conf_hostname;
std::string vault_url;
std::string vault_client_cert;
std::string vault_client_key;
std::string vault_ca;
std::string vault_secret_api_path;
uint64_t vault_timeout_ms = 0;
uint64_t vault_error_timeout_sec = 0;
uint64_t vault_refresh_leeway_sec = 0;
int log_level = 0;
int client_retry_interval = 50; // ms
int client_eio_retry_interval = 1000; // ms
bool client_retry_enospc = true;
int client_wait_up_timeout = 16; // sec (for listings)
// state:
std::string client_hostname;
std::map<std::string, int> self_tree_metrics;
std::map<osd_num_t, int> osd_tree_metrics;
@@ -115,6 +147,7 @@ public:
int retry_timeout_id = -1;
int retry_timeout_duration = 0;
std::vector<cluster_op_t*> offline_ops;
std::vector<cluster_op_t*> key_wait_ops;
cluster_op_t *op_queue_head = NULL, *op_queue_tail = NULL;
writeback_cache_t *wb = NULL;
std::set<osd_num_t> dirty_osds;
@@ -123,7 +156,22 @@ public:
void *scrap_buffer = NULL;
unsigned scrap_buffer_size = 0;
// inodes require some extra state for read/write, it's stored here.
// moreover, robin_hood access is slightly faster than std::map :)
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
http_context_t *vault_http_ctx = NULL;
http_co_t *vault_http_cli = NULL;
bool vault_loading = false;
std::string vault_token;
bool vault_auth_error = false;
timespec vault_token_expire = {};
std::vector<std::string> vault_key_load_queue;
std::map<std::string, vault_load_key_t> vault_keys;
bool pgs_loaded = false;
std::map<pool_id_t, uint64_t> pg_counts;
ring_consumer_t consumer;
std::vector<std::function<void(void)>> on_ready_hooks;
int list_retry_timeout_id = -1;
@@ -163,6 +211,13 @@ protected:
#endif
void continue_ops(int time_passed = 0);
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
void vault_parse_config();
bool vault_check_token();
void vault_load_keys();
void vault_destroy();
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
protected:
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
@@ -173,6 +228,7 @@ protected:
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
void on_change_osd_state_hook(uint64_t peer_osd);
void on_change_node_placement_hook();
void on_change_inode_hook(uint64_t inode, bool removed);
void execute_internal(cluster_op_t *op);
void execute_cas(cluster_op_t *op);
@@ -189,6 +245,7 @@ protected:
void erase_op(cluster_op_t *op);
void calc_wait(cluster_op_t *op);
void inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *next, int inc);
void continue_lists();
bool continue_listing(inode_list_t *lst);
bool restart_listing(inode_list_t* lst);
+367
View File
@@ -0,0 +1,367 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdexcept>
#include <assert.h>
#include "cluster_client_impl.h"
#include "http_client.h"
#include "str_util.h"
#define VAULT_KEY_NOT_LOADED 0
#define VAULT_KEY_LOADING 1
#define VAULT_KEY_LOADED 2
#define VAULT_KEY_ERROR 3
inode_cache_t::~inode_cache_t()
{
if (key_data)
{
free(key_data);
key_data = NULL;
op_enc = NULL;
}
}
void cluster_client_t::vault_destroy()
{
if (vault_http_ctx)
{
#ifndef __MOCK__
http_destroy(vault_http_cli);
http_context_destroy(vault_http_ctx);
vault_http_cli = NULL;
vault_http_ctx = NULL;
#endif
}
}
void cluster_client_t::vault_parse_config()
{
vault_url = config["vault_url"].string_value();
vault_client_cert = config["vault_client_cert"].string_value();
vault_client_key = config["vault_client_key"].string_value();
vault_ca = config["vault_ca"].string_value();
vault_secret_api_path = "/v1/secret/";
if (config["vault_secret_api_path"].is_string())
vault_secret_api_path = config["vault_secret_api_path"].string_value();
vault_timeout_ms = config["vault_timeout_ms"].uint64_value();
if (!vault_timeout_ms)
vault_timeout_ms = 5000;
vault_error_timeout_sec = config["vault_error_timeout_sec"].uint64_value();
if (!vault_error_timeout_sec)
vault_error_timeout_sec = 60;
vault_refresh_leeway_sec = config["vault_refresh_leeway_sec"].uint64_value();
if (!vault_refresh_leeway_sec)
vault_refresh_leeway_sec = 60;
}
// FIXME: Rework client API by adding open/close and cache inode information in the "FD" (maybe)
void cluster_client_t::on_change_inode_hook(uint64_t inode, bool removed)
{
std::vector<inode_t> children = { inode };
for (size_t i = 0; i < children.size(); i++)
{
auto it = inode_cache_children.lower_bound(std::make_pair(children[i], (inode_t)0));
while (it != inode_cache_children.end() && it->first == children[i])
{
children.push_back(it->second);
it++;
}
}
for (auto & inode: children)
{
auto it = inode_cache.find(inode);
if (it != inode_cache.end())
{
auto icache = it->second;
for (auto & parent: icache->chain)
{
inode_cache_children.erase(std::make_pair(parent, inode));
}
inode_cache.erase(it);
}
}
}
std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
{
auto icache_it = inode_cache.find(ino);
if (icache_it != inode_cache.end())
{
return icache_it->second;
}
// Fill inode cache
auto ino_it = st_cli.inode_config.find(ino);
if (ino_it == st_cli.inode_config.end())
{
inode_cache[ino] = NULL;
return NULL;
}
auto pool_it = st_cli.pool_config.find(INODE_POOL(ino));
if (pool_it == st_cli.pool_config.end())
{
inode_cache[ino] = NULL;
return NULL;
}
auto & inode_cfg = ino_it->second;
auto & pool_cfg = pool_it->second;
std::shared_ptr<inode_cache_t> icache = std::make_shared<inode_cache_t>();
icache->readonly = inode_cfg.readonly;
icache->chain.push_back(ino);
std::vector<inode_config_t*> chain_cfg;
// FIXME: Allow unencrypted read & write when all chain is encrypted with the same key
int enc_key_count = !inode_cfg.enc_key.empty() ? 1 : 0;
if (inode_cfg.parent_id)
{
// Check for loops and cache the chain
robin_hood::unordered_flat_set<inode_t> seen;
seen.insert(ino);
uint64_t parent_id = inode_cfg.parent_id;
while (parent_id)
{
if (seen.find(parent_id) != seen.end())
{
icache->has_parent_loop = true;
break;
}
seen.insert(parent_id);
ino_it = st_cli.inode_config.find(parent_id);
if (INODE_POOL(parent_id) == INODE_POOL(ino))
{
icache->chain.push_back(parent_id);
if (ino_it == st_cli.inode_config.end())
chain_cfg.push_back(NULL);
else
{
chain_cfg.push_back(&ino_it->second);
if (!ino_it->second.enc_key.empty())
enc_key_count++;
}
}
else if (!icache->other_pool_parent_id)
icache->other_pool_parent_id = parent_id;
if (ino_it == st_cli.inode_config.end())
break;
parent_id = ino_it->second.parent_id;
}
}
// Check external keys and wait for loading, if required
if (enc_key_count)
{
for (size_t i = 0; i <= chain_cfg.size(); i++)
{
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
{
auto & ik = vault_keys[inode_cfg.enc_key];
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
{
icache->err_code = EPERM;
enc_key_count = 0;
}
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
{
ik.key_state = VAULT_KEY_LOADING;
vault_key_load_queue.push_back(inode_cfg.enc_key);
vault_load_keys();
icache->err_code = EAGAIN;
enc_key_count = 0;
}
else if (ik.key_state == VAULT_KEY_LOADING)
{
icache->err_code = EAGAIN;
enc_key_count = 0;
}
else
{
assert(ik.key_state == VAULT_KEY_LOADED);
}
}
}
}
// Generate encryption key chain, if applicable
if (enc_key_count)
{
uint8_t *key_data = (uint8_t*)malloc_or_die(
AES_256_XTS_KEY_SIZE * enc_key_count +
sizeof(uint8_t*) * icache->chain.size() +
sizeof(osd_op_enc_t)
);
uint8_t **keys = (uint8_t**)(key_data + AES_256_XTS_KEY_SIZE * enc_key_count);
osd_op_enc_t *enc = (osd_op_enc_t*)((uint8_t*)keys + sizeof(uint8_t*)*icache->chain.size());
size_t key_pos = 0;
for (size_t i = 0; i <= chain_cfg.size(); i++)
{
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
if (cfg && !cfg->enc_key.empty())
{
const auto & key = cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX
? vault_keys.at(cfg->enc_key).key
: cfg->enc_key;
assert(key_pos < AES_256_XTS_KEY_SIZE * enc_key_count);
assert(key.size() == 2*AES_256_XTS_KEY_SIZE);
keys[i] = key_data + key_pos;
fromhexstr(key, AES_256_XTS_KEY_SIZE, key_data + key_pos);
key_pos += AES_256_XTS_KEY_SIZE;
}
else
keys[i] = NULL;
}
enc->key_chain = keys;
enc->chain_size = icache->chain.size();
enc->read_chain_bitmap_pos = pool_cfg.data_block_size/pool_cfg.bitmap_granularity/8;
enc->bitmap_granularity = pool_cfg.bitmap_granularity;
icache->key_data = key_data;
icache->op_enc = enc;
}
inode_cache[ino] = icache;
for (auto & parent: icache->chain)
{
if (parent != ino)
inode_cache_children.insert(std::make_pair(parent, ino));
}
return icache;
}
#ifndef __MOCK__
bool cluster_client_t::vault_check_token()
{
timespec now;
clock_gettime(CLOCK_REALTIME, &now);
if (!vault_token_expire.tv_sec || vault_token_expire.tv_sec < now.tv_sec)
{
vault_loading = true;
http_json_post(
vault_http_cli, vault_url+"/v1/auth/cert/login", json11::Json::object{}, "",
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
[this](http_message_t *response)
{
clock_gettime(CLOCK_REALTIME, &vault_token_expire);
vault_loading = false;
std::string err;
json11::Json data;
response->parse_json_response(err, data);
if (err != "")
{
vault_token_expire.tv_sec += vault_error_timeout_sec;
fprintf(stderr, "Vault request failed: %s\n", err.c_str());
}
else
{
uint64_t ttl = data["auth"]["lease_duration"].uint64_value();
vault_token = data["auth"]["client_token"].string_value();
if (vault_token.empty() || !ttl)
{
vault_token_expire.tv_sec += vault_error_timeout_sec;
fprintf(stderr, "No token or lease_duration in Vault response: %s\n", data.dump().c_str());
}
else
{
if (ttl < vault_refresh_leeway_sec)
vault_token_expire.tv_sec += ttl/2;
else
vault_token_expire.tv_sec += ttl - vault_refresh_leeway_sec;
}
}
vault_load_keys();
}
);
return false;
}
if (vault_token.empty())
{
// Auth error happened, mark all loads as failed
for (auto & key_id: vault_key_load_queue)
{
auto & k = vault_keys[key_id];
k.key_state = VAULT_KEY_ERROR;
}
vault_key_load_queue.clear();
auto ops = std::move(key_wait_ops);
for (cluster_op_t *op: ops)
inode_cache.erase(op->inode);
for (cluster_op_t *op: ops)
execute_internal(op);
return false;
}
return true;
}
#endif
void cluster_client_t::vault_load_keys()
{
if (vault_loading || !vault_key_load_queue.size())
{
return;
}
#ifdef __MOCK__
vault_loading = true;
#else
if (!vault_http_ctx)
{
std::string error;
vault_http_ctx = http_context_init(tfd, vault_client_cert, vault_client_key, vault_ca, true, error);
if (!vault_http_ctx)
{
fprintf(stderr, "Failed to initialize HTTP context for Vault: %s\n", error.c_str());
exit(1);
}
vault_http_cli = http_init(vault_http_ctx);
}
if (!vault_check_token())
{
return;
}
std::string key_id = vault_key_load_queue[0];
vault_key_load_queue.erase(vault_key_load_queue.begin());
vault_loading = true;
http_get(
vault_http_cli, vault_url+vault_secret_api_path+key_id.substr(strlen(VAULT_KEY_PREFIX)), "X-Vault-Token: "+vault_token+"\r\n",
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
[this, key_id](http_message_t *response)
{
vault_loading = false;
std::string err;
json11::Json data;
response->parse_json_response(err, data);
vault_parse_secret(key_id, err, data);
}
);
#endif
}
void cluster_client_t::vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data)
{
vault_loading = false;
auto & k = vault_keys[key_id];
if (err != "")
{
k.key_state = VAULT_KEY_ERROR;
fprintf(stderr, "Vault %s%s%s request failed: %s\n", vault_url.c_str(),
vault_secret_api_path.c_str(), key_id.c_str()+strlen(VAULT_KEY_PREFIX), err.c_str());
}
else
{
auto hexkey = data["data"]["key"].string_value();
if (hexkey.empty() || !ishexstr(hexkey) || hexkey.size() != 2*AES_256_XTS_KEY_SIZE)
{
k.key_state = VAULT_KEY_ERROR;
fprintf(stderr, "Vault /v1/secret/%s request failed: 'key' is empty or has invalid format\n", key_id.c_str());
}
else
{
k.key_state = VAULT_KEY_LOADED;
k.key = hexkey;
}
}
if (vault_key_load_queue.empty())
{
auto ops = std::move(key_wait_ops);
for (cluster_op_t *op: ops)
inode_cache.erase(op->inode);
for (cluster_op_t *op: ops)
execute_internal(op);
}
else
vault_load_keys();
}
+327 -168
View File
@@ -1,7 +1,10 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <assert.h>
#include "malloc_or_die.h"
#include "osd_ops.h"
#include "msgr_op.h"
#include "pg_states.h"
#include "etcd_state_client.h"
#ifndef __MOCK__
@@ -22,14 +25,19 @@ etcd_state_client_t::~etcd_state_client_t()
stop_ws_keepalive();
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
http_destroy(etcd_watch_ws);
etcd_watch_ws = NULL;
}
if (keepalive_client)
{
http_close(keepalive_client);
http_destroy(keepalive_client);
keepalive_client = NULL;
}
if (http_ctx)
{
http_context_destroy(http_ctx);
http_ctx = NULL;
}
#endif
if (load_pgs_timer_id >= 0)
{
@@ -72,55 +80,51 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
return addrs;
}
void etcd_state_client_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
http_context_t *etcd_state_client_t::get_http_ctx()
{
if (!http_ctx)
{
std::string error;
http_ctx = http_context_init(tfd, etcd_client_cert, etcd_client_key, etcd_ca, true, error);
if (!http_ctx)
{
fprintf(stderr, "Failed to initialize HTTP context: %s\n", error.c_str());
exit(1);
}
}
return http_ctx;
}
void etcd_state_client_t::etcd_call_oneshot(const std::string & etcd_url, const std::string & api, json11::Json payload,
int timeout, std::function<void(std::string, json11::Json)> callback)
{
std::string etcd_api_path;
int pos = etcd_address.find('/');
if (pos >= 0)
{
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
}
std::string req = payload.dump();
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
"Host: "+etcd_address+"\r\n"
"Content-Type: application/json\r\n"
"Content-Length: "+std::to_string(req.size())+"\r\n"
"Connection: close\r\n"
"\r\n"+req;
auto http_cli = http_init(tfd);
auto cb = [http_cli, callback](const http_response_t *response)
auto http_cli = http_init(get_http_ctx());
http_json_post(http_cli, etcd_url+api, payload, "", { .timeout = timeout }, [http_cli, callback](http_message_t *response)
{
std::string err;
json11::Json data;
response->parse_json_response(err, data);
callback(err, data);
http_close(http_cli);
};
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
http_destroy(http_cli);
});
}
void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int timeout,
void etcd_state_client_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
{
if (!etcd_addresses.size() && !etcd_local.size())
pick_next_etcd([=]()
{
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
exit(1);
}
pick_next_etcd();
std::string etcd_address = selected_etcd_address;
std::string etcd_api_path;
int pos = etcd_address.find('/');
if (pos >= 0)
{
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
}
etcd_call_selected(api, payload, timeout, retries, interval, callback);
});
}
void etcd_state_client_t::etcd_call_selected(const std::string & api, json11::Json payload, int timeout,
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
{
const auto & url = selected_etcd_url;
std::string req = payload.dump();
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
"Host: "+etcd_address+"\r\n"
req = "POST "+url.path+api+" HTTP/1.1\r\n"
"Host: "+url.hostname+"\r\n"
"Content-Type: application/json\r\n"
"Content-Length: "+std::to_string(req.size())+"\r\n"
"Connection: keep-alive\r\n"
@@ -128,15 +132,15 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
"\r\n"+req;
retries--;
auto cb = [this, api, payload, timeout, retries, interval, callback,
cur_addr = selected_etcd_address](const http_response_t *response)
cur_addr = url.addr](http_message_t *response)
{
std::string err;
json11::Json data;
response->parse_json_response(err, data);
if (err != "")
{
if (cur_addr == selected_etcd_address)
selected_etcd_address = "";
if (cur_addr == selected_etcd_url.addr)
selected_etcd_url = (http_url_t){};
if (retries > 0)
{
if (this->log_level > 0)
@@ -164,54 +168,58 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
callback(err, data);
};
if (!keepalive_client)
{
keepalive_client = http_init(tfd);
}
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
keepalive_client = http_init(get_http_ctx());
http_request(keepalive_client, url.addr, req, { .timeout = timeout, .keepalive = true, .ssl = url.ssl }, cb);
}
void etcd_state_client_t::add_etcd_url(std::string addr)
void etcd_state_client_t::add_etcd_url(std::string etcd_address)
{
if (addr.length() > 0)
if (etcd_address.size() > 0)
{
if (strtolower(addr.substr(0, 7)) == "http://")
addr = addr.substr(7);
else if (strtolower(addr.substr(0, 8)) == "https://")
{
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
exit(1);
}
if (!local_ips.size())
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
std::string check_addr;
int pos = addr.find('/');
int pos2 = addr.find(':');
if (pos2 >= 0)
check_addr = addr.substr(0, pos2);
else if (pos >= 0)
check_addr = addr.substr(0, pos);
else
check_addr = addr;
if (pos == std::string::npos)
addr += "/v3";
bool local = false;
int i;
for (i = 0; i < local_ips.size(); i++)
{
if (local_ips[i] == check_addr)
{
local = true;
break;
}
// Fill local_ips
for (auto & ip: getifaddr_list(std::vector<addr_mask_t>(), true))
local_ips.insert(ip);
}
auto & to = local ? this->etcd_local : this->etcd_addresses;
std::string etcd_api_path;
bool ssl = false;
if (etcd_address.substr(0, 8) == "https://")
{
ssl = true;
etcd_address = etcd_address.substr(8);
}
else if (etcd_address.substr(0, 7) == "http://")
etcd_address = etcd_address.substr(7);
auto pos = etcd_address.find('/');
if (pos != std::string::npos)
{
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
}
else
etcd_api_path = "/v3";
pos = etcd_address.find(':');
auto check_addr = (pos != std::string::npos ? etcd_address.substr(0, pos) : etcd_address);
bool is_local = local_ips.find(check_addr) != local_ips.end();
auto & to = (is_local ? etcd_local : etcd_addresses);
check_addr = (ssl ? "https://" : "http://") + etcd_address + etcd_api_path;
size_t i;
for (i = 0; i < to.size(); i++)
{
if (to[i] == addr)
if (to[i] == check_addr)
break;
}
if (i >= to.size())
to.push_back(addr);
{
to.push_back(check_addr);
// Check if it's a domain name
sockaddr_storage ss;
bool is_name = !is_local && !string_to_addr(etcd_address, true, 0, &ss);
auto & to_addr = (is_local ? etcd_local_addr_urls : (is_name ? etcd_name_urls : etcd_nonlocal_addr_urls));
to_addr.push_back((http_url_t){ .ssl = ssl, .addr = etcd_address, .hostname = etcd_address, .path = etcd_api_path });
}
}
}
@@ -219,6 +227,9 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
{
this->etcd_local.clear();
this->etcd_addresses.clear();
this->etcd_local_addr_urls.clear();
this->etcd_nonlocal_addr_urls.clear();
this->etcd_name_urls.clear();
if (config["etcd_address"].is_string())
{
std::string ea = config["etcd_address"].string_value();
@@ -239,6 +250,17 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
add_etcd_url(ea.string_value());
}
}
if (this->osd_num)
{
this->etcd_client_cert = config["osd_etcd_client_cert"].string_value();
this->etcd_client_key = config["osd_etcd_client_key"].string_value();
}
else
{
this->etcd_client_cert = config["etcd_client_cert"].string_value();
this->etcd_client_key = config["etcd_client_key"].string_value();
}
this->etcd_ca = config["etcd_ca"].string_value();
this->etcd_prefix = config["etcd_prefix"].string_value();
if (this->etcd_prefix == "")
{
@@ -282,6 +304,7 @@ 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__
@@ -291,66 +314,131 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
}
}
void etcd_state_client_t::pick_next_etcd()
{
if (selected_etcd_address != "")
return;
if (addresses_to_try.size() == 0)
{
// Prefer local etcd, if any
for (int i = 0; i < etcd_local.size(); i++)
addresses_to_try.push_back(etcd_local[i]);
std::vector<int> ns;
for (int i = 0; i < etcd_addresses.size(); i++)
ns.push_back(i);
if (!rand_initialized)
{
timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
rand_initialized = true;
}
while (ns.size())
{
int i = lrand48() % ns.size();
addresses_to_try.push_back(etcd_addresses[ns[i]]);
ns.erase(ns.begin()+i, ns.begin()+i+1);
}
}
selected_etcd_address = addresses_to_try[0];
addresses_to_try.erase(addresses_to_try.begin(), addresses_to_try.begin()+1);
}
void etcd_state_client_t::start_etcd_watcher()
void etcd_state_client_t::pick_next_etcd(std::function<void()> cb)
{
if (!etcd_addresses.size() && !etcd_local.size())
{
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
exit(1);
}
pick_next_etcd();
std::string etcd_address = selected_etcd_address;
std::string etcd_api_path;
int pos = etcd_address.find('/');
if (pos >= 0)
if (selected_etcd_url.addr != "")
{
etcd_api_path = etcd_address.substr(pos);
etcd_address = etcd_address.substr(0, pos);
cb();
return;
}
if (etcd_urls_to_try.size() != 0)
{
selected_etcd_url = std::move(etcd_urls_to_try[0]);
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
cb();
return;
}
on_resolve_queue.push_back(std::move(cb));
if (on_resolve_queue.size() > 1)
{
// Already resolving
return;
}
assert(!resolve_count);
local_to_try = 0;
for (auto & url: etcd_local_addr_urls)
{
// Prefer local IPs, if any
etcd_urls_to_try.push_back(url);
local_to_try++;
}
for (auto & url: etcd_nonlocal_addr_urls)
{
etcd_urls_to_try.push_back(url);
}
resolve_count++;
for (auto & url: etcd_name_urls)
{
resolve_count++;
http_resolve(get_http_ctx(), url.ssl, url.addr, [this, url](const std::string & error, const std::vector<std::string>& addresses)
{
if (error != "")
fprintf(stderr, "Error resolving %s: %s\n", url.addr.c_str(), error.c_str());
for (auto & addr: addresses)
{
auto url_copy = url;
url_copy.addr = addr;
if (local_ips.find(addr) != local_ips.end())
{
etcd_urls_to_try.insert(etcd_urls_to_try.begin(), std::move(url_copy));
local_to_try++;
}
else
etcd_urls_to_try.push_back(std::move(url_copy));
}
resolve_count--;
if (!resolve_count)
pick_next_etcd_on_resolve();
});
}
resolve_count--;
if (!resolve_count)
{
pick_next_etcd_on_resolve();
}
}
void etcd_state_client_t::pick_next_etcd_on_resolve()
{
if (!etcd_urls_to_try.size())
{
fprintf(stderr, "None of etcd_address could be resolved\n");
exit(1);
}
if (!rand_initialized)
{
timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
rand_initialized = true;
}
// Shuffle addresses
for (size_t i = etcd_urls_to_try.size()-1; i > local_to_try; i--)
{
size_t j = local_to_try + lrand48() % (i - local_to_try);
if (j != i)
std::swap(etcd_urls_to_try[i], etcd_urls_to_try[j]);
}
selected_etcd_url = std::move(etcd_urls_to_try[0]);
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
auto cbs = std::move(on_resolve_queue);
for (auto cb: cbs)
{
cb();
}
}
void etcd_state_client_t::start_etcd_watcher()
{
pick_next_etcd([this]()
{
start_etcd_watcher_selected();
});
}
void etcd_state_client_t::start_etcd_watcher_selected()
{
const auto & url = selected_etcd_url;
etcd_watches_initialised = 0;
ws_alive = 1;
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
}
if (this->log_level > 1)
{
fprintf(stderr, "Trying to connect to etcd websocket at %s, watch from revision %ju/%ju/%ju\n", etcd_address.c_str(),
fprintf(stderr, "Trying to connect to etcd websocket at %s%s%s (hostname %s), watch from revision %ju/%ju/%ju\n",
url.ssl ? "https://" : "http://", url.addr.c_str(), url.path.c_str(), url.hostname.c_str(),
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
}
etcd_watch_ws = open_websocket(tfd, etcd_address, etcd_api_path+"/watch", etcd_slow_timeout,
[this, cur_addr = selected_etcd_address](const http_response_t *msg)
if (!etcd_watch_ws)
etcd_watch_ws = http_init(get_http_ctx());
else
http_close(etcd_watch_ws);
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)
{
if (msg->body.length())
{
@@ -368,11 +456,12 @@ void etcd_state_client_t::start_etcd_watcher()
{
if (watch_id == ETCD_CONFIG_WATCH_ID ||
watch_id == ETCD_PG_STATE_WATCH_ID ||
watch_id == ETCD_OSD_STATE_WATCH_ID)
watch_id == ETCD_OSD_STATE_WATCH_ID ||
watch_id == ETCD_INDEX_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);
@@ -393,8 +482,7 @@ void etcd_state_client_t::start_etcd_watcher()
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
data["result"]["compact_revision"].uint64_value());
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = etcd_watch_revision_index = 0;
on_reload_hook();
}
return;
@@ -414,7 +502,10 @@ void etcd_state_client_t::start_etcd_watcher()
}
// 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"}}
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && !data["result"]["header"]["revision"].is_null())
// Also don't save revision from the initial created: true messages because they always contain the latest revision
if (etcd_watches_initialised == etcd_total_watches &&
!data["result"]["header"]["revision"].is_null() &&
!data["result"]["created"].bool_value())
{
// Restart watchers from the same revision number as in the last received message,
// not from the next one to protect against revision being split into multiple messages,
@@ -435,7 +526,9 @@ void etcd_state_client_t::start_etcd_watcher()
etcd_watch_revision_pg = watch_rev;
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
etcd_watch_revision_osd = watch_rev;
addresses_to_try.clear();
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
std::map<std::string, etcd_kv_t> changes;
@@ -465,13 +558,8 @@ void etcd_state_client_t::start_etcd_watcher()
if (msg->eof)
{
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
if (cur_addr == selected_etcd_address)
selected_etcd_address = "";
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
}
if (cur_addr == selected_etcd_url.addr)
selected_etcd_url = (http_url_t){};
if (etcd_watches_initialised == 0)
{
// Connection not established, retry in <etcd_quick_timeout>
@@ -515,6 +603,18 @@ void etcd_state_client_t::start_etcd_watcher()
{ "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)
{
@@ -538,7 +638,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
}
@@ -546,12 +646,7 @@ void etcd_state_client_t::start_ws_keepalive()
{
if (this->log_level > 0)
{
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_address.c_str());
}
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
}
start_etcd_watcher();
}
@@ -689,6 +784,15 @@ 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)
@@ -1262,33 +1366,40 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
}
else
{
inode_t parent_inode_num = value["parent_id"].uint64_value();
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
}
}
}
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)
{
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
if (!parent_pool_id)
parent_inode_num |= pool_id << (64-POOL_ID_BITS);
else if (parent_pool_id >= POOL_ID_MAX)
{
fprintf(
stderr, "Inode %ju/%ju parent_pool value is invalid, ignoring parent setting\n",
inode_num >> (64-POOL_ID_BITS), inode_num & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)
);
parent_inode_num = 0;
}
else
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
on_inode_change_hook(inode_num, true);
}
insert_inode_config((inode_config_t){
.num = inode_num,
.name = value["name"].string_value(),
.size = value["size"].uint64_value(),
.parent_id = parent_inode_num,
.readonly = value["readonly"].bool_value(),
.deleted = value["deleted"].bool_value(),
.meta = value["meta"],
.mod_revision = kv.mod_revision,
});
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));
}
}
}
@@ -1377,6 +1488,10 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
new_cfg["parent_pool"] = (uint64_t)INODE_POOL(cfg->parent_id);
new_cfg["parent_id"] = (uint64_t)INODE_NO_POOL(cfg->parent_id);
}
if (!cfg->enc_key.empty())
{
new_cfg["enc_key"] = cfg->enc_key;
}
if (cfg->readonly)
{
new_cfg["readonly"] = true;
@@ -1392,6 +1507,50 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
return new_cfg;
}
inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision)
{
inode_t parent_inode_num = value["parent_id"].uint64_value();
if (parent_inode_num && !INODE_POOL(parent_inode_num))
{
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
if (!parent_pool_id)
parent_inode_num = INODE_WITH_POOL(INODE_POOL(inode_num), parent_inode_num);
else if (parent_pool_id >= POOL_ID_MAX)
{
fprintf(
stderr, "Inode %u/%ju parent_pool value is invalid, ignoring parent setting\n",
INODE_POOL(inode_num), INODE_NO_POOL(inode_num)
);
parent_inode_num = 0;
}
else
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
}
std::string enc_key;
if (!value["enc_key"].is_null())
{
enc_key = value["enc_key"].string_value();
if (enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) != VAULT_KEY_PREFIX &&
(enc_key.size() != 2*AES_256_XTS_KEY_SIZE || !ishexstr(enc_key)))
{
enc_key = "";
fprintf(stderr, "Inode %u/%ju has invalid enc_key, should be %u bit hex string or Vault key reference\n",
INODE_POOL(inode_num), INODE_NO_POOL(inode_num), AES_256_XTS_KEY_SIZE);
}
}
return (inode_config_t){
.num = inode_num,
.name = value["name"].string_value(),
.size = value["size"].uint64_value(),
.parent_id = parent_inode_num,
.readonly = value["readonly"].bool_value(),
.deleted = value["deleted"].bool_value(),
.enc_key = std::move(enc_key),
.meta = value["meta"],
.mod_revision = mod_revision,
};
}
int etcd_state_client_t::address_count()
{
return etcd_addresses.size() + etcd_local.size();
+41 -9
View File
@@ -4,21 +4,24 @@
#pragma once
#include <set>
#include <memory>
#include "json11/json11.hpp"
#include "osd_id.h"
#include "object_id.h"
#include "timerfd_manager.h"
#define ETCD_CONFIG_WATCH_ID 1
#define ETCD_OSD_STATE_WATCH_ID 2
#define ETCD_PG_STATE_WATCH_ID 3
#define ETCD_TOTAL_WATCHES 3
#define ETCD_INDEX_WATCH_ID 4
#define DEFAULT_BLOCK_SIZE 128*1024
#define MIN_DATA_BLOCK_SIZE 4*1024
#define MAX_DATA_BLOCK_SIZE 128*1024*1024
#define DEFAULT_BITMAP_GRANULARITY 4096
#define VAULT_KEY_PREFIX "vault:"
#ifndef IMMEDIATE_NONE
#define IMMEDIATE_NONE 0
#define IMMEDIATE_SMALL 1
@@ -83,6 +86,7 @@ struct inode_config_t
inode_t parent_id = 0;
bool readonly = false;
bool deleted = false;
std::string enc_key;
// Arbitrary metadata
json11::Json meta;
// Change revision of the metadata in etcd
@@ -95,23 +99,42 @@ struct inode_watch_t
inode_config_t cfg = {};
};
struct http_url_t
{
bool ssl;
std::string addr;
std::string hostname;
std::string path;
};
struct http_co_t;
struct http_context_t;
struct __attribute__((visibility("default"))) etcd_state_client_t
{
protected:
std::vector<std::string> local_ips;
std::vector<std::string> etcd_addresses;
std::set<std::string> local_ips;
std::vector<std::string> etcd_local;
std::string selected_etcd_address;
std::vector<std::string> addresses_to_try;
std::vector<std::string> etcd_addresses;
std::vector<http_url_t> etcd_local_addr_urls;
std::vector<http_url_t> etcd_nonlocal_addr_urls;
std::vector<http_url_t> etcd_name_urls;
size_t local_to_try = 0;
std::vector<http_url_t> etcd_urls_to_try;
http_url_t selected_etcd_url;
size_t resolve_count = 0;
std::vector<inode_watch_t*> watches;
std::vector<std::function<void()>> on_resolve_queue;
bool new_pg_config = false;
bool use_image_index_for_auth = false;
int ws_keepalive_timer = -1;
int ws_alive = 0;
bool rand_initialized = false;
void add_etcd_url(std::string);
void pick_next_etcd();
void pick_next_etcd(std::function<void()> cb);
void pick_next_etcd_on_resolve();
void etcd_call_selected(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
void start_etcd_watcher_selected();
public:
int etcd_keepalive_timeout = 30;
int etcd_ws_keepalive_interval = 5;
@@ -124,15 +147,22 @@ public:
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
uint32_t global_immediate_commit = IMMEDIATE_NONE;
uint64_t osd_num = 0;
std::string etcd_prefix;
std::string etcd_client_cert;
std::string etcd_client_key;
std::string etcd_ca;
int log_level = 0;
timerfd_manager_t *tfd = NULL;
http_context_t *http_ctx = NULL;
http_co_t *etcd_watch_ws = NULL, *keepalive_client = NULL;
int etcd_watches_initialised = 0;
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;
@@ -158,10 +188,12 @@ public:
std::function<void(http_co_t *)> on_start_watcher_hook;
json11::Json::object serialize_inode_cfg(inode_config_t *cfg);
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
std::vector<std::string> get_addresses();
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
http_context_t *get_http_ctx();
void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
void etcd_txn(json11::Json txn, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
void etcd_txn_slow(json11::Json txn, std::function<void(std::string, json11::Json)> callback);
void start_etcd_watcher();
File diff suppressed because it is too large Load Diff
+25 -5
View File
@@ -17,14 +17,19 @@
class timerfd_manager_t;
#pragma GCC visibility push(default)
struct http_options_t
{
int timeout;
bool want_streaming;
bool keepalive;
bool ssl;
};
struct http_response_t
struct http_context_t;
struct http_message_t
{
std::string error;
@@ -41,10 +46,25 @@ struct http_response_t
// Opened websocket or keepalive HTTP connection
struct http_co_t;
http_co_t* http_init(timerfd_manager_t *tfd);
http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, const std::string & path,
int timeout, std::function<void(const http_response_t *msg)> on_message);
http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key,
const std::string & ssl_ca, bool verify_peer, std::string & error);
void http_resolve(http_context_t *ctx, bool ssl, std::string host,
std::function<void(const std::string & error, const std::vector<std::string> & addrs)> cb);
void http_context_destroy(http_context_t *ctx);
http_co_t* http_init(http_context_t *ctx = NULL);
void open_websocket(http_co_t *handler, const std::string & addr, const std::string & hostname, const std::string & path,
const http_options_t & options, std::function<void(http_message_t *msg)> on_message);
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
void http_get(http_co_t *handler, const std::string & url, const std::string & headers,
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
void http_json_post(http_co_t *handler, const std::string & url, json11::Json body, const std::string & headers,
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
void http_post_message(http_co_t *handler, uint8_t type, const std::string & msg);
void http_serve(http_co_t *handler, int peer_fd, const http_options_t & options,
std::function<void(http_message_t *msg)> request_callback);
void http_reply(http_co_t *handler, const std::string & reply);
void http_close(http_co_t *co);
void http_destroy(http_co_t *co);
#pragma GCC visibility pop
+14 -12
View File
@@ -145,7 +145,7 @@ void osd_messenger_t::init()
rdma_contexts = msgr_rdma_context_t::create_all(
osd_num && osd_cluster_network_masks.size() ? osd_cluster_network_masks : osd_network_masks,
rdma_device != "" ? rdma_device.c_str() : NULL,
rdma_port_num, rdma_gid_index, rdma_mtu, rdma_odp, log_level
rdma_port_num, rdma_gid_index, rdma_mtu, log_level
);
if (!rdma_contexts.size())
{
@@ -289,6 +289,14 @@ osd_messenger_t::~osd_messenger_t()
rdmacm_evch = NULL;
}
#endif
for (auto encrypt_ctx: encrypt_ctx_pool)
{
destroy_aes_xts_encrypt(encrypt_ctx);
}
for (auto decrypt_ctx: decrypt_ctx_pool)
{
destroy_aes_xts_decrypt(decrypt_ctx);
}
}
void osd_messenger_t::parse_config(const json11::Json & config)
@@ -322,8 +330,10 @@ void osd_messenger_t::parse_config(const json11::Json & config)
this->rdma_max_msg = config["rdma_max_msg"].uint64_value();
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
this->rdma_max_msg = 129*1024;
this->rdma_odp = config["rdma_odp"].bool_value();
#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)
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
else
@@ -503,7 +513,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
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 = malloc_or_die(receive_buffer_size);
clients[peer_fd]->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
tfd->set_fd_handler(peer_fd, true, [this](int peer_fd, int epoll_events)
{
// Either OUT (connected) or HUP
@@ -740,14 +750,6 @@ 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;
tfd->set_fd_handler(cl->peer_fd, false, [this](int peer_fd, int epoll_events)
{
// Do not miss the disconnection!
if (epoll_events & EPOLLRDHUP)
{
handle_peer_epoll(peer_fd, epoll_events);
}
});
// Add the initial receive request
init_recv_rdma(cl);
}
@@ -782,7 +784,7 @@ void osd_messenger_t::accept_connections(int listen_fd)
cl->peer_port = ntohs(((sockaddr_in*)&addr)->sin_port);
cl->peer_fd = peer_fd;
cl->peer_state = PEER_CONNECTED;
cl->in_buf = malloc_or_die(receive_buffer_size);
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
// Add FD to epoll
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
{
+43 -15
View File
@@ -34,9 +34,6 @@
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
#define MSGR_SENDP_HDR 1
#define MSGR_SENDP_FREE 2
struct msgr_sendp_t
{
osd_op_t *op;
@@ -48,6 +45,11 @@ struct msgr_rdma_connection_t;
struct msgr_rdma_context_t;
#endif
struct op_aes_xts_encrypt_t;
struct op_aes_xts_decrypt_t;
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
struct osd_client_t
{
int refs = 0;
@@ -63,7 +65,7 @@ struct osd_client_t
osd_num_t in_osd_num = 0;
bool is_incoming = false;
void *in_buf = NULL;
uint8_t *in_buf = NULL;
#ifdef WITH_RDMA
msgr_rdma_connection_t *rdma_conn = NULL;
@@ -72,14 +74,18 @@ struct osd_client_t
// Read state
int read_ready = 0;
osd_op_t *read_op = NULL;
size_t read_op_size = 0;
size_t read_op_pos = 0;
iovec read_iov = { 0 };
msghdr read_msg = { 0 };
int read_remaining = 0;
int read_state = 0;
osd_op_buf_list_t recv_list;
std::vector<iovec> recv_list;
size_t recv_list_size = 0;
uint64_t read_op_id = 1;
bool check_sequencing = false;
bool enable_pg_locks = false;
op_aes_xts_decrypt_t *decrypt_ctx = NULL;
size_t read_op_inline_decrypt_pos = 0;
size_t read_op_inline_decrypt_in = 0;
// Incoming operations
std::vector<osd_op_t*> received_ops;
@@ -92,11 +98,16 @@ struct osd_client_t
std::set<pool_pg_num_t> dirty_pgs;
// Write state
std::deque<osd_op_t *> write_ops;
osd_op_t *write_op = NULL;
size_t write_op_pos = 0;
msghdr write_msg = { 0 };
int write_state = 0;
std::vector<iovec> send_list, next_send_list;
std::vector<msgr_sendp_t> outbox, next_outbox;
std::vector<iovec> send_list;
size_t send_list_size = 0;
std::deque<osd_op_t*> send_free_ops;
std::vector<osd_op_t*> zc_free_list;
op_aes_xts_encrypt_t *encrypt_ctx = NULL;
~osd_client_t();
void cancel_ops();
@@ -188,6 +199,7 @@ 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;
#ifdef WITH_RDMA
bool use_rdma = true;
@@ -200,7 +212,6 @@ protected:
std::vector<msgr_rdma_context_t *> rdma_contexts;
uint64_t rdma_max_sge = 0, rdma_max_send = 0, rdma_max_recv = 0;
uint64_t rdma_max_msg = 0;
bool rdma_odp = false;
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;
@@ -210,7 +221,10 @@ protected:
std::vector<int> read_ready_clients;
std::vector<int> write_ready_clients;
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
std::vector<osd_op_t*> set_immediate_ops;
std::deque<osd_op_t*> set_immediate_ops;
std::vector<op_aes_xts_encrypt_t*> encrypt_ctx_pool;
std::vector<op_aes_xts_decrypt_t*> decrypt_ctx_pool;
public:
timerfd_manager_t *tfd = NULL;
@@ -275,9 +289,24 @@ protected:
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);
void handle_read(int result, osd_client_t *cl);
bool handle_read_buffer(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_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_decrypt_start(osd_client_t* cl);
void op_decrypt_inline(osd_client_t* cl);
void op_decrypt_free(osd_client_t* cl);
bool handle_read(int result, osd_client_t *cl);
bool handle_read_buffer(osd_client_t *cl, void *curbuf, int remain);
bool handle_finished_read(osd_client_t *cl);
void handle_op_hdr(osd_client_t *cl);
bool handle_reply_hdr(osd_client_t *cl);
@@ -287,8 +316,7 @@ protected:
#ifdef WITH_RDMA
void try_send_rdma(osd_client_t *cl);
void try_send_rdma_odp(osd_client_t *cl);
void try_send_rdma_nodp(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);
+474
View File
@@ -0,0 +1,474 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <assert.h>
#include "etcd_state_client.h"
#include "messenger.h"
#include "msgr_encrypt.h"
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
{
#ifdef WITH_OPENSSL
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
abort();
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
#else
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
abort();
#endif
}
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
{
assert(!encrypted);
#ifdef WITH_OPENSSL
EVP_CIPHER_CTX_free(ctx);
#endif
if (tmp)
free(tmp);
}
void op_aes_xts_encrypt_t::start(uint8_t *key, uint64_t start_offset, size_t block_size)
{
assert(!encrypted);
this->start_offset = start_offset;
this->key = key;
this->block_size = block_size;
this->offset = 0;
this->encrypted = false;
this->tmp_pos = 0;
if (tmp && tmp_size != block_size)
{
free(tmp);
tmp = NULL;
tmp_size = 0;
}
#ifdef WITH_OPENSSL
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
#endif
}
void op_aes_xts_encrypt_t::encrypt_block(uint8_t *in, uint8_t *out)
{
#ifdef WITH_OPENSSL
uint8_t iv[16] = { 0 };
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
int actual_out = 0;
if (EVP_EncryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == block_size);
#endif
}
// 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,
// crafting IV to resume encryption is slow, so we have to accumulate a full block
// and encrypt it at once :-(
// And then we have to support consuming it in parts because it's simpler for the
// higher layers.
if (encrypted)
{
// Copy accumulated and encrypted output
assert(tmp);
if (max_out > block_size - tmp_pos)
max_out = block_size - tmp_pos;
memcpy(out, tmp + tmp_pos, max_out);
done_out += max_out;
tmp_pos += max_out;
if (tmp_pos >= block_size)
encrypted = false;
}
else if (max_in < block_size - offset%block_size)
{
// Just accumulate input
if (!tmp)
{
tmp = (uint8_t*)malloc_or_die(block_size);
tmp_size = block_size;
}
memcpy(tmp + offset%block_size, in, max_in);
done_in += max_in;
offset += max_in;
}
else if (max_out < block_size)
{
// Accumulate and encrypt input in <tmp>, then copy part of it to <out>
if (!tmp)
{
tmp = (uint8_t*)malloc_or_die(block_size);
tmp_size = block_size;
}
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
encrypt_block(tmp, tmp);
encrypted = true;
memcpy(out, tmp, max_out);
tmp_pos = max_out;
done_in += max_in;
offset += max_in;
done_out += max_out;
}
else if (!(offset%block_size))
{
// Full block - simplest case
encrypt_block(in, out);
done_in += block_size;
offset += block_size;
done_out += block_size;
}
else
{
// Accumulate input and encrypt directly to <output>
assert(tmp);
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
encrypt_block(tmp, out);
done_in += max_in;
offset += max_in;
done_out += block_size;
}
}
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
{
delete encrypt_ctx;
}
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
{
#ifdef WITH_OPENSSL
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
abort();
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
#else
fprintf(stderr, "Error: Vitastor is built without encryption support\n");
abort();
#endif
}
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
{
assert(!decrypted);
#ifdef WITH_OPENSSL
EVP_CIPHER_CTX_free(ctx);
#endif
if (tmp)
free(tmp);
}
void op_aes_xts_decrypt_t::start(uint8_t **key_chain, size_t chain_size, uint8_t *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;
assert(chain_size <= 1 || key_indexes != NULL);
this->block_size = block_size;
this->offset = 0;
this->tmp_pos = 0;
if (tmp && tmp_size != block_size)
{
free(tmp);
tmp = NULL;
tmp_size = 0;
}
#ifdef WITH_OPENSSL
if (chain_size == 1 && key_chain[0] && EVP_DecryptInit_ex(ctx, NULL, NULL, key_chain[0], NULL) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
#endif
}
void op_aes_xts_decrypt_t::decrypt_block(uint8_t *in, uint8_t *out)
{
uint8_t *key = NULL;
if (chain_size)
{
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;
}
}
#ifdef WITH_OPENSSL
uint8_t iv[16] = { 0 };
*((uint64_t*)iv) = start_offset + offset - offset%block_size;
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
int actual_out = 0;
if (EVP_DecryptUpdate(ctx, out, &actual_out, in, block_size) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == block_size);
#endif
}
void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
{
// Fucking AES-XTS implementations (all of them) don't have streaming support,
// crafting IV to resume decryption is slow, so we have to accumulate a full block
// and decrypt it at once :-(
// And then we have to support consuming it in parts because clients sometimes need
// fragmented output.
if (decrypted)
{
// Copy accumulated and decrypted output
assert(tmp);
if (max_out > block_size - tmp_pos)
max_out = block_size - tmp_pos;
memcpy(out, tmp + tmp_pos, max_out);
done_out += max_out;
tmp_pos += max_out;
if (tmp_pos >= block_size)
decrypted = false;
}
else if (max_in < block_size - offset%block_size)
{
// Just accumulate input
if (!tmp)
{
tmp = (uint8_t*)malloc_or_die(block_size);
tmp_size = block_size;
}
memcpy(tmp + offset%block_size, in, max_in);
done_in += max_in;
offset += max_in;
}
else if (max_out < block_size)
{
// Accumulate and decrypt input in <tmp>, then copy part of it to <out>
if (!tmp)
{
tmp = (uint8_t*)malloc_or_die(block_size);
tmp_size = block_size;
}
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
decrypt_block(tmp, tmp);
decrypted = true;
memcpy(out, tmp, max_out);
tmp_pos = max_out;
done_in += max_in;
offset += max_in;
done_out += max_out;
}
else if (!(offset%block_size))
{
// Full block - simplest case
decrypt_block(in, out);
done_in += block_size;
offset += block_size;
done_out += block_size;
}
else
{
// Accumulate input and decrypt directly to <output>
assert(tmp);
max_in = block_size - offset%block_size;
memcpy(tmp + offset%block_size, in, max_in);
decrypt_block(tmp, out);
done_in += max_in;
offset += max_in;
done_out += block_size;
}
}
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
{
delete decrypt_ctx;
}
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)
{
auto op = cl->write_op;
auto & op_pos = cl->write_op_pos;
assert(op->req.hdr.opcode == OSD_OP_WRITE);
if (!from)
{
if (!cl->encrypt_ctx)
{
if (encrypt_ctx_pool.size())
{
cl->encrypt_ctx = encrypt_ctx_pool.back();
encrypt_ctx_pool.pop_back();
}
else
cl->encrypt_ctx = new op_aes_xts_encrypt_t();
}
assert(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;
}
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)
{
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++)
{
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;
}
op_decrypt_free(cl);
return true;
}
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
{
if (!cl->decrypt_ctx)
{
if (decrypt_ctx_pool.size())
{
cl->decrypt_ctx = decrypt_ctx_pool.back();
decrypt_ctx_pool.pop_back();
}
else
cl->decrypt_ctx = new op_aes_xts_decrypt_t();
auto & enc = cl->read_op->enc;
cl->decrypt_ctx->start(enc->key_chain, enc->chain_size,
(cl->read_op->req.rw.flags & OSD_OP_RETURN_CHAIN) ? (uint8_t*)cl->read_op->bitmap + enc->read_chain_bitmap_pos : 0,
cl->read_op->req.rw.offset, enc->bitmap_granularity);
}
}
void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
{
op_decrypt_start(cl);
osd_op_t *op = cl->read_op;
size_t from_in = cl->read_op_inline_decrypt_in;
int i = 0;
while (i < op->iov.count && from_in >= op->iov.buf[i].iov_len)
{
from_in -= op->iov.buf[i].iov_len;
i++;
}
size_t from_out = cl->read_op_inline_decrypt_pos - OSD_PACKET_SIZE - op->reply.rw.bitmap_len;
int j = 0;
while (j < op->iov.count && from_out >= op->iov.buf[j].iov_len)
{
from_out -= op->iov.buf[j].iov_len;
j++;
}
while (i < op->iov.count && j < op->iov.count)
{
uint8_t *in = (uint8_t*)op->iov.buf[i].iov_base + from_in;
size_t in_len = op->iov.buf[i].iov_len - from_in;
uint8_t *out = (uint8_t*)op->iov.buf[j].iov_base + from_out;
size_t out_len = op->iov.buf[j].iov_len - from_out;
size_t done_in = 0;
size_t done_out = 0;
cl->decrypt_ctx->update(in, in_len, out, out_len, done_in, done_out);
if (done_in >= in_len)
{
i++;
from_in = 0;
}
else
from_in += done_in;
if (done_out >= out_len)
{
j++;
from_out = 0;
}
else
from_out += done_out;
}
assert(j >= op->iov.count);
op_decrypt_free(cl);
}
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
{
if (cl->decrypt_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->decrypt_ctx;
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
}
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdint.h>
// WITH_OPENSSL is left to possibly support other crypto libraries
#ifdef WITH_OPENSSL
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#endif
class op_aes_xts_encrypt_t
{
#ifdef WITH_OPENSSL
EVP_CIPHER_CTX *ctx = NULL;
#endif
uint64_t start_offset = 0;
uint8_t *key = NULL;
size_t offset = 0;
size_t block_size = 0;
uint8_t *tmp = NULL;
size_t tmp_size = 0;
size_t tmp_pos = 0;
bool encrypted = false;
void encrypt_block(uint8_t *in, uint8_t *out);
public:
op_aes_xts_encrypt_t();
~op_aes_xts_encrypt_t();
inline bool has_buffered() { return encrypted; };
void start(uint8_t *key, uint64_t start_offset, size_t block_size);
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
};
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
class op_aes_xts_decrypt_t
{
#ifdef WITH_OPENSSL
EVP_CIPHER_CTX *ctx = NULL;
#endif
uint64_t start_offset = 0;
uint8_t **key_chain = NULL;
size_t chain_size = 0;
uint8_t *key_indexes = NULL;
size_t offset = 0;
size_t block_size = 0;
uint8_t *tmp = NULL;
size_t tmp_size = 0;
size_t tmp_pos = 0;
bool decrypted = false;
void decrypt_block(uint8_t *in, uint8_t *out);
public:
op_aes_xts_decrypt_t();
~op_aes_xts_decrypt_t();
inline bool has_buffered() { return decrypted; };
void start(uint8_t **key_chain, size_t chain_size, uint8_t *key_indexes, uint64_t start_offset, size_t block_size);
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
};
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);

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