Compare commits
166
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6af10a444 | ||
|
|
bc24936969 | ||
|
|
b6c854c860 | ||
|
|
0ce6a61fcb | ||
|
|
4498d0665a | ||
|
|
55cce76d36 | ||
|
|
70c0cd2d5f | ||
|
|
455dc9cb1c | ||
|
|
9243aed615 | ||
|
|
3dd0e4daca | ||
|
|
3779e2670d | ||
|
|
0d92e00e41 | ||
|
|
0d1eeb4310 | ||
|
|
b21c92cb7a | ||
|
|
9db0980fc1 | ||
|
|
4a8ee088c1 | ||
|
|
f2585bcd02 | ||
|
|
72d1447eec | ||
|
|
c67db91305 | ||
|
|
75dfafa0cf | ||
|
|
b1d9098904 | ||
|
|
f676d262ec | ||
|
|
954505b5fd | ||
|
|
c137860f18 | ||
|
|
f45fb987d1 | ||
|
|
d744bd43ae | ||
|
|
8da99ee24a | ||
|
|
3c3a58aa1e | ||
|
|
40d4162249 | ||
|
|
026aaf59e7 | ||
|
|
cb40ab3dfd | ||
|
|
459a412f91 | ||
|
|
2b52769d64 | ||
|
|
ff83e12b79 | ||
|
|
4340b2b7bc | ||
|
|
6be0be02fa | ||
|
|
a31a1c0ab1 | ||
|
|
1c18daa644 | ||
|
|
b367c81f44 | ||
|
|
197ef6c171 | ||
|
|
4a907f09df | ||
|
|
360ccbaa4d | ||
|
|
d960816a9b | ||
|
|
3dabffb1be | ||
|
|
5ba63a2fa5 | ||
|
|
26be916e3f | ||
|
|
eb1dbd7459 | ||
|
|
13842ecd90 | ||
|
|
27e2c08e38 | ||
|
|
f9975311ea | ||
|
|
652ca3f1c3 | ||
|
|
c81cbcf69e | ||
|
|
9e28c4c6e3 | ||
|
|
e14be3fdad | ||
|
|
2e5d9cb238 | ||
|
|
376226552a | ||
|
|
6bdd260b50 | ||
|
|
b72cfbad66 | ||
|
|
792efa673e | ||
|
|
f3c5c3b776 | ||
|
|
5e26376f1f | ||
|
|
55047c10fb | ||
|
|
e13edcdc2f | ||
|
|
aaf54f264d | ||
|
|
7e879bea9d | ||
|
|
af34be70ab | ||
|
|
00517e2bac | ||
|
|
e0a2615cbc | ||
|
|
63fe3c323a | ||
|
|
a88465df05 | ||
|
|
ad24be717a | ||
|
|
648e3b12f0 | ||
|
|
a675993c74 | ||
|
|
c9dfd0f67d | ||
|
|
84919a10a9 | ||
|
|
51ae4d6e24 | ||
|
|
572b20fedc | ||
|
|
4e2724b28f | ||
|
|
768b1675f8 | ||
|
|
38fa722725 | ||
|
|
e56d83fb7f | ||
|
|
ff95a85875 | ||
|
|
98203568a8 | ||
|
|
89df98ee08 | ||
|
|
0007a831b6 | ||
|
|
40517c335f | ||
|
|
c9f7308b6a | ||
|
|
85c7e3bde0 | ||
|
|
4fb55b3535 | ||
|
|
912aca11a3 | ||
|
|
7b454bd16c | ||
|
|
a0c8be46a4 | ||
|
|
53b4329fac | ||
|
|
a7f41c4a12 | ||
|
|
5d78057ac3 | ||
|
|
8efc5a353f | ||
|
|
603b26b896 | ||
|
|
a3b0fe0deb | ||
|
|
f504e356d5 | ||
|
|
4ed17b7070 | ||
|
|
1fd2819724 | ||
|
|
dcdabbc1ec | ||
|
|
625d5b7b9e | ||
|
|
9e507fd333 | ||
|
|
c2b5118127 | ||
|
|
4b926e2223 | ||
|
|
a5d9a6996a | ||
|
|
0ee03e7172 | ||
|
|
88b7d9afcd | ||
|
|
f271c8450c | ||
|
|
f78d7d4efc | ||
|
|
fdaf7c88ff | ||
|
|
2fb6eb0c30 | ||
|
|
36d2b56208 | ||
|
|
14b22f2ba9 | ||
|
|
fe8b1fe0cc | ||
|
|
1ec963e468 | ||
|
|
5100f822d8 | ||
|
|
7432494e88 | ||
|
|
d0c0f3ea39 | ||
|
|
f61190f31d | ||
|
|
3dc0ab5c33 | ||
|
|
de96efed2f | ||
|
|
87a5230798 | ||
|
|
0c5e6d4346 | ||
|
|
b278087410 | ||
|
|
a8e821b13b | ||
|
|
caa70317fa | ||
|
|
b8eaaabfe4 | ||
|
|
e4d80c415e | ||
|
|
553191c3ff | ||
|
|
ab385252b5 | ||
|
|
041185c673 | ||
|
|
b03ac80a57 | ||
|
|
2ba56074f9 | ||
|
|
4acfe149cb | ||
|
|
008ed5b269 | ||
|
|
4fffe0f032 | ||
|
|
a76d5ccc0d | ||
|
|
8ed1e180e0 | ||
|
|
8832fc3b14 | ||
|
|
0134934c99 | ||
|
|
2e36f292bd | ||
|
|
bcc6419760 | ||
|
|
dd5941b9a4 | ||
|
|
4005b88865 | ||
|
|
280b5cd675 | ||
|
|
e5c505eaf4 | ||
|
|
c1d244d4f0 | ||
|
|
9b264a212f | ||
|
|
ff7f5cb4f4 | ||
|
|
25ecca7625 | ||
|
|
99c4244004 | ||
|
|
9949b9fb4e | ||
|
|
e6881ad1d5 | ||
|
|
b30635b932 | ||
|
|
0c1154833c | ||
|
|
c227bb05b6 | ||
|
|
dd85315f22 | ||
|
|
47d2f4e0be | ||
|
|
2a5028d17f | ||
|
|
07915c2881 | ||
|
|
79141eb383 | ||
|
|
f7cbb6ed56 | ||
|
|
8f8172db99 | ||
|
|
94be147e80 |
+163
-1
@@ -63,7 +63,7 @@ jobs:
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
# leak sanitizer sometimes crashes
|
||||
- run: cd /root/vitastor/build && ASAN_OPTIONS=detect_leaks=0 make -j16 test
|
||||
- run: cd /root/vitastor/build && ASAN_OPTIONS=detect_leaks=0 make -j16 build_tests test
|
||||
|
||||
npm_lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -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,42 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_enc_gcm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=enc_gcm ENCRYPTED=1 VITASTOR_CFG=',"proto_checksums":"gcm"' /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_snapshot_chain:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1206,6 +1296,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_heal_ec_rdma:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: TEST_NAME=ec_rdma RDMA=1 SCHEME=ec /root/vitastor/tests/test_heal.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1224,6 +1332,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum_xxhash:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -2052,3 +2178,39 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted_ec:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
|
||||
@@ -38,6 +38,14 @@ for my $line (<>)
|
||||
{
|
||||
$test_name .= '_antietcd';
|
||||
}
|
||||
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
|
||||
{
|
||||
$test_name .= '_https';
|
||||
}
|
||||
elsif ($1 eq 'ENCRYPTED')
|
||||
{
|
||||
$test_name .= '_encrypted';
|
||||
}
|
||||
elsif ($1 eq 'OLD')
|
||||
{
|
||||
$test_name =~ s/^test_/test_old_/s;
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
package-lock.json
|
||||
fio
|
||||
qemu
|
||||
node_modules
|
||||
|
||||
+7
-7
@@ -1,20 +1,20 @@
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.6")
|
||||
set(VITASTOR_VERSION "3.0.12")
|
||||
|
||||
include(CTest)
|
||||
|
||||
add_custom_target(build_tests)
|
||||
add_custom_target(test
|
||||
COMMAND
|
||||
echo leak:tcmalloc > ${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt &&
|
||||
env LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt ${CMAKE_CTEST_COMMAND}
|
||||
set_property(TEST PROPERTY ENVIRONMENT LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt)
|
||||
add_test(gen_lsan_suppress
|
||||
${CMAKE_COMMAND} -E echo leak:tcmalloc > "${CMAKE_CURRENT_BINARY_DIR}/lsan-suppress.txt"
|
||||
)
|
||||
set_tests_properties(gen_lsan_suppress PROPERTIES FIXTURES_SETUP f_lsan_suppress)
|
||||
set_property(TEST PROPERTY FIXTURES_REQUIRED f_lsan_suppress)
|
||||
# make -j16 -C ../../build test_heap && ../../build/src/test/test_heap
|
||||
# make -j16 -C ../../build test_heap && rm -f $(find ../../build -name '*.gcda') && ctest -V -T test -T coverage -R heap --test-dir ../../build && (cd ../../build; gcovr -f ../src --html --html-nested -o coverage/index.html; cd ../src/test)
|
||||
# make -j16 -C ../../build test_blockstore && rm -f $(find ../../build -name '*.gcda') && ctest -V -T test -T coverage -R blockstore --test-dir ../../build && (cd ../../build; gcovr -f ../src --html --html-nested -o coverage/index.html; cd ../src/test)
|
||||
# kcov --include-path=../../../src ../../kcov ./test_blockstore
|
||||
add_dependencies(test build_tests)
|
||||
add_subdirectory(src)
|
||||
|
||||
@@ -62,6 +62,7 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
|
||||
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](docs/config/osd.ru.md)
|
||||
- [Параметры мониторов](docs/config/monitor.ru.md)
|
||||
- [Безопасность](docs/config/security.ru.md)
|
||||
- [Настройки пулов](docs/config/pool.ru.md)
|
||||
- [Метаданные образов в etcd](docs/config/inode.ru.md)
|
||||
- Использование
|
||||
|
||||
@@ -62,6 +62,7 @@ Read more details in the documentation. You can start from here: [Quick Start](d
|
||||
- [OSD Disk Layout](docs/config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](docs/config/osd.en.md)
|
||||
- [Monitor](docs/config/monitor.en.md)
|
||||
- [Security](docs/config/security.en.md)
|
||||
- [Pool configuration](docs/config/pool.en.md)
|
||||
- [Image metadata in etcd](docs/config/inode.en.md)
|
||||
- Usage
|
||||
|
||||
+1
-1
Submodule cpp-btree updated: 8de8b467ac...ebe44c9b66
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.6
|
||||
VITASTOR_VERSION ?= v3.0.12
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.6
|
||||
image: vitalif/vitastor-csi:v3.0.12
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.6
|
||||
image: vitalif/vitastor-csi:v3.0.12
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.6"
|
||||
vitastorCSIDriverVersion = "3.0.12"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.6-1) unstable; urgency=medium
|
||||
vitastor (3.0.12-1) unstable; urgency=medium
|
||||
|
||||
* Bugfixes
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Vitaliy Filippov <vitalif@yourcmc.ru>
|
||||
Build-Depends: debhelper, g++ (>= 8), libstdc++6 (>= 8),
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev, libc-ares-dev,
|
||||
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
|
||||
node-bindings <!nocheck>, node-gyp, node-nan
|
||||
Standards-Version: 4.5.0
|
||||
|
||||
Vendored
+1
-1
@@ -44,7 +44,7 @@ curl -s https://git.yourcmc.ru/vitalif/antietcd/archive/master.tar.gz | tar -zx
|
||||
curl -s https://git.yourcmc.ru/vitalif/tinyraft/archive/master.tar.gz | tar -zx
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL
|
||||
if [[ "$REL" = "trixie" && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
|
||||
if [[ ( "$REL" = "trixie" || "$REL" = "resolute" ) && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
|
||||
# Fucking shit, archives differ between bookworm (xz 5.4.1) and trixie (xz 5.8.1)
|
||||
cp ../vitastor-bookworm/vitastor_$VER.orig.tar.xz .
|
||||
else
|
||||
|
||||
Vendored
+6
-8
@@ -12,20 +12,18 @@ ARG REL=
|
||||
WORKDIR /root
|
||||
|
||||
RUN set -e -x; \
|
||||
if [ "$REL" = "buster" ]; then \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
fi; \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
|
||||
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
|
||||
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
|
||||
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
|
||||
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
|
||||
apt-get -y build-dep fio && \
|
||||
apt-get --download-only source fio
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.6
|
||||
VITASTOR_VERSION ?= v3.0.12
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -12,12 +12,7 @@ EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
SyslogIdentifier=etcd
|
||||
ExecStart=bash -c 'docker run --rm -i -v /var/lib/vitastor/etcd:/data \
|
||||
--log-driver none --network host $CONTAINER_OPTIONS --name vitastor-etcd \
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --name "$ETCD_NAME" --data-dir /data \
|
||||
--snapshot-count 10000 --advertise-client-urls http://$ETCD_IP:2379 --listen-client-urls http://$ETCD_IP:2379 \
|
||||
--initial-advertise-peer-urls http://$ETCD_IP:2380 --listen-peer-urls http://$ETCD_IP:2380 \
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster "$ETCD_INITIAL_CLUSTER" \
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision'
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --data-dir /data
|
||||
ExecStop=docker stop vitastor-etcd
|
||||
Restart=always
|
||||
StartLimitInterval=0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.6
|
||||
VITASTOR_VERSION=v3.0.12
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
|
||||
ETCD_NAME=""
|
||||
ETCD_IP=""
|
||||
ETCD_INITIAL_CLUSTER=""
|
||||
|
||||
@@ -38,3 +38,4 @@ In the future, additional configuration methods may be added:
|
||||
- [OSD Disk Layout](config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](config/osd.en.md)
|
||||
- [Monitor](config/monitor.en.md)
|
||||
- [Security Parameters](config/security.en.md)
|
||||
|
||||
@@ -41,3 +41,4 @@
|
||||
- [Дисковые параметры OSD](config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](config/osd.ru.md)
|
||||
- [Параметры мониторов](config/monitor.ru.md)
|
||||
- [Параметры безопасности](config/security.ru.md)
|
||||
|
||||
@@ -198,8 +198,14 @@ put a modified value into etcd key /vitastor/config/global.
|
||||
- Type: string
|
||||
- Default: none
|
||||
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
|
||||
## csum_block_size
|
||||
|
||||
|
||||
@@ -209,8 +209,12 @@ journal_block_size и meta_block_size. Однако на данный момен
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: none
|
||||
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -70,6 +70,7 @@ with an OSD restart or, for some of them, even without restarting by updating co
|
||||
- [use_atomic_flag](#use_atomic_flag)
|
||||
- [pg_reshard_chunk_size](#pg_reshard_chunk_size)
|
||||
- [pg_reshard_chunk_pause_ms](#pg_reshard_chunk_pause_ms)
|
||||
- [gc_on_start](#gc_on_start)
|
||||
|
||||
## bind_address
|
||||
|
||||
@@ -753,3 +754,9 @@ This option sets the maximum number of object is a chunk. Moving 100k objects us
|
||||
- Default: 100
|
||||
|
||||
This option sets the interval between handling two PG count change chunks.
|
||||
|
||||
## gc_on_start
|
||||
|
||||
- Type: boolean
|
||||
|
||||
Forcibly clean all garbage entries in the new store on every OSD restart.
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
- [use_atomic_flag](#use_atomic_flag)
|
||||
- [pg_reshard_chunk_size](#pg_reshard_chunk_size)
|
||||
- [pg_reshard_chunk_pause_ms](#pg_reshard_chunk_pause_ms)
|
||||
- [gc_on_start](#gc_on_start)
|
||||
|
||||
## bind_address
|
||||
|
||||
@@ -793,3 +794,9 @@ pg_minsize OSD во время переключений, что может по
|
||||
- Значение по умолчанию: 100
|
||||
|
||||
Данная опция задаёт интервал между обработкой двух порций изменения числа PG пулов.
|
||||
|
||||
## gc_on_start
|
||||
|
||||
- Тип: булево (да/нет)
|
||||
|
||||
Принудительно очищать все мусорные записи в новом хранилище при каждом запуске OSD.
|
||||
|
||||
@@ -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_cipher_pool_size](#max_cipher_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_cipher_pool_size
|
||||
|
||||
- Type: integer
|
||||
- Default: 256
|
||||
|
||||
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
|
||||
for each cipher and for encryption/decryption. Probably doesn't require modification.
|
||||
@@ -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_cipher_pool_size](#max_cipher_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_cipher_pool_size
|
||||
|
||||
- Тип: целое число
|
||||
- Значение по умолчанию: 256
|
||||
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.en.md|indent=2}}
|
||||
|
||||
{{../../config/security.en.md|indent=2}}
|
||||
|
||||
{{../../config/pool.en.md|indent=2}}
|
||||
|
||||
{{../../config/inode.en.md|indent=2}}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
{{../../config/monitor.ru.md|indent=2}}
|
||||
|
||||
{{../../config/security.ru.md|indent=2}}
|
||||
|
||||
{{../../config/pool.ru.md|indent=2}}
|
||||
|
||||
{{../../config/inode.ru.md|indent=2}}
|
||||
|
||||
@@ -233,11 +233,21 @@
|
||||
type: string
|
||||
default: none
|
||||
info: |
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
info_ru: |
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -938,3 +938,7 @@
|
||||
This option sets the interval between handling two PG count change chunks.
|
||||
info_ru: |
|
||||
Данная опция задаёт интервал между обработкой двух порций изменения числа PG пулов.
|
||||
- name: gc_on_start
|
||||
type: bool
|
||||
info: Forcibly clean all garbage entries in the new store on every OSD restart.
|
||||
info_ru: Принудительно очищать все мусорные записи в новом хранилище при каждом запуске OSD.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
@@ -0,0 +1,131 @@
|
||||
- name: etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vitastor client (not OSD and not monitor)
|
||||
etcd https connections. May be path to a file or just a PEM string with certificate.
|
||||
In the latter case, string must begin with "-----BEGIN CERTIFICATE-----".
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для https-подключений к etcd для клиентов Vitastor
|
||||
(не OSD и не мониторов). Может быть путём к файлу или просто строкой с
|
||||
сертификатом в формате PEM. В последнем случае строка должна начинаться с
|
||||
"-----BEGIN CERTIFICATE-----".
|
||||
- name: etcd_client_key
|
||||
type: string
|
||||
info: Private key for etcd_client_cert (also a file or a PEM string).
|
||||
info_ru: Закрытый ключ для сертификата etcd_client_cert (также путь к файлу или PEM строка).
|
||||
- name: etcd_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify etcd server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера etcd.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: osd_etcd_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Same as [etcd_client_cert](#etcd_client_cert), but only for OSDs.
|
||||
OSDs, clients and monitors should have different permissions, so they should
|
||||
use different certificates.
|
||||
info_ru: |
|
||||
Аналогично [etcd_client_cert](#etcd_client_cert), но только для OSD.
|
||||
OSD, клиенты и мониторы должны иметь разные привилегии, поэтому они должны
|
||||
использовать разные сертификаты.
|
||||
- name: osd_etcd_client_key
|
||||
type: string
|
||||
info: Same as [etcd_client_key](#etcd_client_key), but only for OSDs.
|
||||
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для OSD.
|
||||
- name: mon_etcd_client_cert
|
||||
type: string
|
||||
info: Same as [etcd_client_cert](#etcd_client_cert), but only for Vitastor monitors.
|
||||
info_ru: Аналогично [etcd_client_cert](#etcd_client_cert), но только для мониторов Vitastor.
|
||||
- name: mon_etcd_client_key
|
||||
type: string
|
||||
info: Same as [etcd_client_key](#etcd_client_key), but only for Vitastor monitors.
|
||||
info_ru: Аналогично [etcd_client_key](#etcd_client_key), но только для мониторов Vitastor.
|
||||
- name: vault_url
|
||||
type: string
|
||||
info: |
|
||||
Vault base URL.
|
||||
|
||||
Vitastor clients support AES-256-XTS image data encryption with different per-image keys.
|
||||
Encryption is performed by the client, OSDs don't have access to decrypted data.
|
||||
|
||||
Encryption keys may be stored in etcd or, for the increased security level, in an external
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) or [OpenBao](https://openbao.org/)
|
||||
instance.
|
||||
|
||||
Vitastor clients use [v1 k/v secrets engine](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
and [TLS authentication engine](https://openbao.org/api-docs/auth/cert/) in Vault.
|
||||
|
||||
In that case, only key IDs are stored in etcd.
|
||||
info_ru: |
|
||||
Базовый адрес Vault.
|
||||
|
||||
Клиенты Vitastor поддерживают AES-256-XTS шифрование данных образов с отдельными ключами на
|
||||
каждый образ. Данные шифруются клиентами, OSD не имеют доступа к незашифрованным данным.
|
||||
|
||||
Ключи шифрования могут храниться в etcd или, для повышенного уровня безопасности, во внешнем
|
||||
[HashiCorp Vault](https://developer.hashicorp.com/vault/) или [OpenBao](https://openbao.org/).
|
||||
|
||||
Клиенты Vitastor используют [движок секретов v1](https://openbao.org/api-docs/secret/kv/kv-v1/)
|
||||
и [TLS-аутентификацию](https://openbao.org/api-docs/auth/cert/) в Vault.
|
||||
|
||||
В этом случае, только ID ключей хранятся в etcd.
|
||||
- name: vault_secret_api_path
|
||||
type: string
|
||||
default: /v1/secret/
|
||||
info: Vault v1 secret API mount path to use.
|
||||
info_ru: Путь к API секретов v1 для использования клиентами.
|
||||
- name: vault_client_cert
|
||||
type: string
|
||||
info: |
|
||||
Client TLS certificate to use for Vault connections. Just like [etcd_client_cert](#etcd_client_cert),
|
||||
may be path to a file or just a certificate in PEM string.
|
||||
info_ru: |
|
||||
Клиентский TLS сертификат для подключений к Vault. Как и [etcd_client_cert](#etcd_client_cert),
|
||||
может быть путём к файлу или просто PEM-строкой с сертификатом.
|
||||
- name: vault_client_key
|
||||
type: string
|
||||
info: Private key for vault_client_cert (also a file or a PEM string).
|
||||
info_ru: Закрытый ключ для сертификата vault_client_cert (также путь к файлу или PEM строка).
|
||||
- name: vault_ca
|
||||
type: string
|
||||
info: |
|
||||
Trusted TLS CA to verify Vault server certificate. May be path to a file,
|
||||
directory or just a PEM string with certificate.
|
||||
info_ru: |
|
||||
Доверенный корневой TLS-сертификат для проверки сертификата сервера Vault.
|
||||
Может быть путём к файлу, директории или просто строкой с сертификатом в
|
||||
формате PEM.
|
||||
- name: vault_timeout_ms
|
||||
type: int
|
||||
default: 5000
|
||||
info: Timeout for Vault requests in milliseconds.
|
||||
info_ru: Максимально время выполнения Vault-запросов в миллисекундах.
|
||||
- name: vault_error_timeout_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Time (in seconds) to wait before retrying after receiving an error from Vault.
|
||||
info_ru: |
|
||||
Время (в секундах) для ожидания перед повторной попыткой при получении ошибки от Vault.
|
||||
- name: vault_refresh_leeway_sec
|
||||
type: int
|
||||
default: 60
|
||||
info: |
|
||||
Extra time (in seconds) before real Vault token lease_timeout to refresh it, just
|
||||
in case of system clock drift.
|
||||
info_ru: |
|
||||
Зазор времени (в секундах), чтобы обновлять токены Vault чуть раньше их реального
|
||||
lease_timeout, на случай "ухода" системных часов.
|
||||
- name: max_cipher_pool_size
|
||||
type: int
|
||||
default: 256
|
||||
info: |
|
||||
Maximum number of OpenSSL cipher contexts cached in OSD memory, counted separately
|
||||
for each cipher and for encryption/decryption. Probably doesn't require modification.
|
||||
info_ru: |
|
||||
Максимальное количество кэшируемых в памяти OSD контекстов шифра OpenSSL, учитываемое
|
||||
отдельно для каждого шифра и для шифрования и расшифровки. Вряд ли требует изменения.
|
||||
@@ -26,9 +26,9 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
|
||||
The instruction is very simple.
|
||||
|
||||
1. Download a Docker image of the desired version: \
|
||||
`docker pull vitalif/vitastor:v3.0.6`
|
||||
`docker pull vitalif/vitastor:v3.0.12`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.6 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
|
||||
3. Reload udev rules: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Enable the vitastor-host service: \
|
||||
|
||||
@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
|
||||
Инструкция по установке максимально простая.
|
||||
|
||||
1. Скачайте Docker-образ желаемой версии: \
|
||||
`docker pull vitalif/vitastor:v3.0.6`
|
||||
`docker pull vitalif/vitastor:v3.0.12`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.6 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- Debian 10 (Buster): `deb https://vitastor.io/debian buster main`
|
||||
- Ubuntu 22.04 (Jammy): `deb https://vitastor.io/debian jammy main`
|
||||
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
|
||||
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
|
||||
- Add `-oldstable` to bookworm/bullseye/buster in this line to install the last
|
||||
stable version from 0.9.x branch instead of 1.x
|
||||
- To always prefer vitastor-patched QEMU and Libvirt versions, add the following to `/etc/apt/preferences`:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
- Debian 10 (Buster): `deb https://vitastor.io/debian buster main`
|
||||
- Ubuntu 22.04 (Jammy): `deb https://vitastor.io/debian jammy main`
|
||||
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
|
||||
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
|
||||
- Добавьте `-oldstable` к слову bookworm/bullseye/buster в этой строке, чтобы
|
||||
установить последнюю стабильную версию из ветки 0.9.x вместо 1.x
|
||||
- Чтобы всегда предпочитались версии пакетов QEMU и Libvirt с патчами Vitastor, добавьте в `/etc/apt/preferences`:
|
||||
|
||||
@@ -15,9 +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)
|
||||
- tcmalloc (google-perftools-dev)
|
||||
- jerasure, c-ares headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
|
||||
назначенных инициализаторов (designated initializers) из C++20
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm
|
||||
- tcmalloc (google-perftools-dev)
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
|
||||
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
|
||||
- [S3](../installation/s3.en.md)
|
||||
- [TLS support for etcd connections](../config/security.en.md)
|
||||
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
|
||||
|
||||
## Plugins and tools
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
|
||||
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
|
||||
- [S3](../installation/s3.ru.md)
|
||||
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
|
||||
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
|
||||
|
||||
## Драйверы и инструменты
|
||||
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
[Документация](../../README-ru.md#документация) → Безопасность
|
||||
|
||||
-----
|
||||
|
||||
[Read in English](security.en.md)
|
||||
|
||||
# Оглавление
|
||||
|
||||
⚠️ Предупреждение: детальное описание настроек безопасности достаточно длинное.
|
||||
|
||||
Если не боитесь - читайте [Подробное описание](#подробное-описание).
|
||||
|
||||
Если хотите просто быстро настроить Vitastor с шифрованием - читайте начало статьи.
|
||||
|
||||
- [Быстрая настройка](#быстрая-настройка)
|
||||
-
|
||||
|
||||
# Быстрая настройка
|
||||
|
||||
|
||||
|
||||
# Пользовательские сценарии
|
||||
|
||||
Зачем всё это нужно вам?
|
||||
|
||||
|
||||
|
||||
# Подробное описание
|
||||
|
||||
Начиная с версии 3.1.0, в Vitastor есть следующие функции:
|
||||
1. Шифрование соединений с etcd (TLS)
|
||||
2. Шифрование соединений с OSD (AES-GCM) - по выбору либо только заголовков, либо и заголовков, и данных
|
||||
3. Сквозное шифрование данных образов (AES-XTS)
|
||||
4. Хранения ключей шифрования AES-XTS во внешнем Vault
|
||||
5. Контрольных сумм данных на транспортном уровне с секретной "солью"
|
||||
6. Аутентификация с помощью TLS (X.509) сертификатов и закрытых ключей
|
||||
7. Разграничение прав доступа клиентов к данным etcd
|
||||
8. Разграничение прав доступа клиентов к данным самих образов (на стороне OSD)
|
||||
|
||||
По умолчанию шифрование, аутентификация и авторизация отключены, но, начиная с 3.1.0,
|
||||
используются контрольные суммы данных на транспортном уровне (`proto_checksums=payload`).
|
||||
|
||||
## Шифрование соединений с etcd (TLS)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования (http)
|
||||
- С шифрованием (https)
|
||||
- С клиентским сертификатом, но при выключенной авторизации (`use_auth=false`) - используется
|
||||
отдельный сертификат и ключ: `etcd_client_cert`, `etcd_client_key`
|
||||
- С клиентским сертификатом, при включённой аутентификации на уровне OSD - используется общий
|
||||
сертификат и ключ: для OSD - `osd_cert` и `osd_pkey`, для клиентов - `cert` и `pkey`
|
||||
|
||||
## Шифрование соединений с OSD (AES-GCM)
|
||||
|
||||
Варианты настройки:
|
||||
- Без шифрования и без контрольных сумм: `proto_checksums=none`.
|
||||
- Без шифрования, с контрольными суммами данных: `proto_checksums=payload` (можно не указывать,
|
||||
т.к. это значение по умолчанию). При этом контрольные суммы можно отключить на стороне
|
||||
клиента либо использовать более старые версии клиента, не поддерживающие контрольные суммы.
|
||||
Если нужно запретить подключение клиентов без контрольных сумм, можно использовать опцию
|
||||
`force_proto_checksums=payload`.
|
||||
- С шифрованием заголовков и контрольными суммами данных: активируется при установленных опциях
|
||||
`cert`, `pkey`, `osd_ca` на стороне клиента и `osd_cert`, `osd_pkey`, `osd_ca`, `client_ca`
|
||||
на стороне OSD, при `proto_checksums=payload`. При этом по умолчанию запрещается
|
||||
отключение контрольных сумм на уровне клиента, то есть используется `force_proto_checksums=payload`.
|
||||
- С полным шифрованием всего трафика: аналогично прошлому варианту, но с `proto_checksums=gcm`.
|
||||
Клиенту при этом по умолчанию разрешается понизить уровень защиты до контрольных сумм, но
|
||||
это тоже можно запретить через `force_proto_checksums=gcm`. Данный вариант не является рекомендуемым,
|
||||
так как добавлен в первую очередь для возможной поддержки небезопасных (публичных) сетей и
|
||||
больше всего снижает производительность. В частности, если одновременно использовать полное
|
||||
шифрование трафика и сквозное шифрование образов AES-XTS, то данные будут шифроваться дважды.
|
||||
|
||||
Для шифрования используется алгоритм AES-256-GCM и собственный упрощённый протокол согласования
|
||||
ключей, полностью аналогичный TLS 1.3 ECDHE.
|
||||
|
||||
## Сквозное шифрование данных образов (AES-XTS)
|
||||
|
||||
Клиент Vitastor поддерживает шифрование данных каждого образа своим ключом. В этом случае на OSD
|
||||
уходят уже зашифрованные данные и сами OSD не видят настоящее содержимое образов. Разные ключи
|
||||
в том числе могут иметь разные снимки или клоны одного и того же образа. Например, можно сделать
|
||||
базовый образ ВМ (условный Debian Linux) нешифрованным, но наследовать от него шифрованные образы
|
||||
клиентских ВМ.
|
||||
|
||||
Ключи шифрования образов могут храниться либо в etcd, либо во внешнем Vault. Во втором случае
|
||||
в etcd хранятся только ID ключей, а Vitastor вообще не имеет доступа к данным образов. Для
|
||||
использования Vault нужно создать образ с опцией `--enc_key vault:ID`, а в конфигурации указать
|
||||
опции:
|
||||
- vault_url
|
||||
- vault_ca
|
||||
- vault_client_cert
|
||||
- vault_client_key
|
||||
|
||||
Ещё раз повторимся, что если AES-XTS используется с полным шифрованием трафика (`proto_checksums=gcm`),
|
||||
то данные образов шифруются дважды - сначала AES-XTS, а потом AES-GCM. Можете использовать,
|
||||
только если вы совсем параноик :-).
|
||||
|
||||
## Производительность шифрования
|
||||
|
||||
У вас может возникнуть вопрос - а как быстро всё это прекрасное шифрование работает?
|
||||
|
||||
Ответ - скорость сильно зависит от процессора. Складывается она из нескольких вещей:
|
||||
|
||||
-
|
||||
|
||||
TODO: vitastor-cli bench.
|
||||
|
||||
## Аутентификация по сертификатам
|
||||
|
||||
При включённом шифровании клиенты, OSD и мониторы Vitastor аутентифицируются по сертификатам
|
||||
как при соединениях с etcd (Antietcd), так и с OSD.
|
||||
|
||||
Для OSD и мониторов должны использоваться отдельные сертификаты - либо самоподписанные, либо
|
||||
подписанные отдельными CA (`osd_ca` и `mon_ca`). При этом все OSD могут использовать один и
|
||||
тот же сертификат и все мониторы тоже могут использовать один и тот же сертификат, так как
|
||||
привилегии разных OSD или разных мониторов ничем не отличаются (теоретически можно было бы
|
||||
сделать разграничение сертификатов OSD по пулам, но пока что такой необходимости не было).
|
||||
|
||||
Также сертификат монитора может быть вообще не нужен, если Antietcd встраивается в сам монитор.
|
||||
В этом случае монитор и так имеет доступ ко всем данным etcd прямо в памяти.
|
||||
|
||||
Каждый клиент должен иметь свой сертификат, подписанный общим корневым сертификатом
|
||||
для клиентов (`client_ca`). Common Name сертификата должно равняться имени пользователя.
|
||||
|
||||
## Модель прав доступа
|
||||
|
||||
Привилегии пользователей хранятся в данных etcd в ключах `/vitastor/config/user/<имя>`.
|
||||
|
||||
У пользователя есть 2 свойства:
|
||||
- Тип:
|
||||
- Клиент (`type=client` или не указано) - может читать и модифицировать только явным образом
|
||||
разрешённые образы.
|
||||
- Администратор (`type=admin`) - может читать и модифицировать все образы, а также администрировать
|
||||
кластер: смотреть общую статистику и состояние, создавать и удалять OSD и так далее.
|
||||
- Список имён групп, членом которых пользователь является.
|
||||
|
||||
У образов есть 3 свойства:
|
||||
- Владелец (owner) - имя пользователя, которому разрешено и читать, и менять образ
|
||||
- Группа владельцев (owner_group) - имя группы владельцев
|
||||
- Группа читатетей (reader_group) - имя группы пользователей, которым разрешено читать образ
|
||||
|
||||
У пулов есть 1 свойство:
|
||||
- Группа создателей (creator_group) - имя группы пользователей, которым разрешено создавать образы в пуле
|
||||
|
||||
## Права доступа к данным etcd
|
||||
|
||||
Привилегии реализуются через Antietcd во всех режимах работы. Если используется etcd, то
|
||||
Antietcd выступает в роли фильтрующего прокси, при этом он может быть встроен в монитор
|
||||
Vitastor или запущен отдельно. В этом случае etcd должен разрешать входящие подключения
|
||||
только от Antietcd, а все остальные компоненты должны соединяться с Antietcd.
|
||||
|
||||
Если же используется Antietcd, то привилегии реализуются в нём самом.
|
||||
|
||||
Если используется встроенный в монитор Antietcd, то привилегии включаются либо параметром
|
||||
`use_auth: true`, либо, если этот параметр не указан - включается автоматически, если задан
|
||||
любой из параметров `client_ca`, `osd_ca`, `mon_ca`. При этом монитор требует указания
|
||||
параметров `client_ca` и `osd_ca`, а если не используется режим проксирования в etcd -
|
||||
также `antietcd_server_ca`, чтобы Antietcd мог отличать кластерные соединения от клиентских.
|
||||
|
||||
Если используется отдельно стоящий Antietcd, привилегии нужно включать явным образом.
|
||||
|
||||
Встроенные привилегии etcd не поддерживаются по причине их многочисленных недоработок:
|
||||
- Аутентификация по сертификатам не работает в REST интерфейсе etcd,
|
||||
- Привилегии хранятся отдельно от k/v и не могут участвовать в транзакциях,
|
||||
- Менять привилегии может только администратор (root)
|
||||
- Нет поддержки фильтрации ответов чтения по привилегиям.
|
||||
|
||||
Подробный список привилегий на ключи в etcd [смотрите ниже](#привилегии-etcd).
|
||||
|
||||
## Права доступа к данным OSD
|
||||
|
||||
Регулируется опцией `use_auth`, либо, если она не указана, включается автоматически,
|
||||
если используется шифрование, то есть, если заданы опции `osd_ca` и `client_ca`.
|
||||
|
||||
OSD аутентифицирует клиентов по сертификатам и разрешает каждому клиенту только
|
||||
то, что ему разрешено согласно модели прав доступа.
|
||||
|
||||
Подробный список разрешаемых OSD операций [смотрите ниже](#привилегии-osd).
|
||||
|
||||
## Права доступа к API
|
||||
|
||||
[vitastor-cli serve](../usage/cli.ru.md#serve) также поддерживает клиентскую
|
||||
аутентификацию по сертификатам. Принимаются только сертификаты, подписанные
|
||||
`client_ca`. В качестве серверного сертификата используется отдельный сертификат
|
||||
`server_cert` с ключом `server_key`.
|
||||
|
||||
При этом для корректной работы `vitastor-cli serve` он сам должен использовать
|
||||
для доступа в Vitastor сертификат (`cert`+`pkey`) пользователя с правами
|
||||
администратора (`type=admin`).
|
||||
|
||||
Обычным клиентам при доступе к API разрешаются только API-операции с образами,
|
||||
доступными им либо на чтение (для чтения), либо на запись (для модификации).
|
||||
Все остальные API-вызовы разрешаются только для администраторов.
|
||||
|
||||
Подробный список разрешаемых API операций [смотрите ниже](#привилегии-api).
|
||||
|
||||
## Привилегии etcd
|
||||
|
||||
Ниже все названия ключей приведены без общего префикса `/vitastor`.
|
||||
|
||||
Разрешённые операции с ключами в Antietcd для клиентов (`type=client`):
|
||||
- Только чтение:
|
||||
- Разрешено всегда:
|
||||
- `/config/global`
|
||||
- `/config/node_placement`
|
||||
- `/config/pools`
|
||||
- `/pg/config`
|
||||
- `/osd/state/*`
|
||||
- `/pg/state/*`
|
||||
- `/index/maxid/*`
|
||||
- Для образов, которые [может читать пользователь](#модель-прав-доступа):
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
- `/inode/stats/*`
|
||||
- Чтение и запись:
|
||||
- Для пулов, в которых может создавать образы пользователь:
|
||||
- `/index/maxid/*`
|
||||
- Для образов, которыми владеет пользователь:
|
||||
- `/config/inode/*`
|
||||
- `/index/image/*`
|
||||
|
||||
Разрешённые операции с ключами в Antietcd для администраторов (`type=admin`):
|
||||
- Чтение:
|
||||
- `/stats`
|
||||
- `/mon/*`
|
||||
- `/pg/*`
|
||||
- `/pgstats/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
- Чтение и запись:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/index/*`
|
||||
- `/pg/history/*`
|
||||
|
||||
Разрешённые операции с ключами в etcd для OSD:
|
||||
- Чтение:
|
||||
- `/pg/config`
|
||||
- `/config/*`
|
||||
- Чтение и запись:
|
||||
- `/osd/*`
|
||||
- `/pg/state/*`
|
||||
- `/pg/history/*`
|
||||
- `/pgstats/*`
|
||||
|
||||
Разрешённые операции с ключами в etcd для мониторов:
|
||||
- Чтение:
|
||||
- `/config/*`
|
||||
- `/osd/*`
|
||||
- `/pgstats/*`
|
||||
- Чтение и запись:
|
||||
- `/pg/config`
|
||||
- `/stats`
|
||||
- `/history/last_clean_pgs`
|
||||
- `/mon/*`
|
||||
- `/pg/history/*`
|
||||
- `/inode/stats/*`
|
||||
- `/pool/stats/*`
|
||||
|
||||
## Привилегии OSD
|
||||
|
||||
Клиентские операции:
|
||||
- READ - разрешено для образов, доступных пользователю на чтение.
|
||||
- WRITE, DELETE, SCRUB - разрешены для образов, доступных пользователю на запись.
|
||||
- SYNC - операция не связана с образом и разрешена всегда.
|
||||
- DESCRIBE - операция разрешена только для администраторов (используются командами
|
||||
`vitastor-cli describe` и `fix`).
|
||||
- PING - операция разрешена всегда.
|
||||
- SHOW_CONFIG - операция разрешена всегда, однако если в ней клиент представляется
|
||||
как OSD, то проверяется, что он использует сертификат, подписанный `osd_ca`.
|
||||
- SEC_LIST (листинг) - разрешена другим OSD и администраторам с любыми параметрами,
|
||||
а обычным клиентам разрешена только для запросов, ограниченных образом, доступным
|
||||
пользователю на чтение.
|
||||
|
||||
Кластерные операции - разрешаются только другим OSD:
|
||||
- SEC_READ
|
||||
- SEC_WRITE
|
||||
- SEC_WRITE_STABLE
|
||||
- SEC_SYNC
|
||||
- SEC_STABILIZE
|
||||
- SEC_ROLLBACK
|
||||
- SEC_DELETE
|
||||
- SEC_READ_BMP
|
||||
- SEC_LOCK
|
||||
|
||||
## Привилегии API
|
||||
|
||||
Клиентам (пользователям с `type=client`) разрешаются операции:
|
||||
- image/list - для образов, которые пользователь может читать.
|
||||
- image/create - для пулов, в которых пользователю разрешено создавать образы, либо
|
||||
для создания снимков образов, которыми пользователь владеет.
|
||||
- image/delete, image/flatten, image/modify - для образов, которыми пользователь владеет.
|
||||
|
||||
Все остальные операции разрешаются только администраторам (`type=admin`).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Таким образом, доступны следующие варианты настройки:
|
||||
|
||||
### Mon в роли Etcd proxy
|
||||
|
||||
Mon
|
||||
- use_antietcd: true
|
||||
- etcd_proxy = {
|
||||
urls: [],
|
||||
cert = <antietcd.pem>,
|
||||
key,
|
||||
ca = <etcd.pem>,
|
||||
}
|
||||
- antietcd_cert = antietcd.pem
|
||||
- antietcd_key
|
||||
|
||||
etcd
|
||||
--client-cert-auth --cert-file=etcd.pem --key-file=etcd.key --trusted-ca-file=antietcd.pem \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.pem --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.pem
|
||||
|
||||
### Mon с отдельным Antietcd Proxy
|
||||
|
||||
Mon
|
||||
- use_antietcd: false
|
||||
- etcd_ca = antietcd.pem
|
||||
|
||||
Antietcd
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
|
||||
--cert antietcd.pem --key antietcd.key --ca client_ca.pem --osd_ca osd_ca.pem \
|
||||
--etcd_cert antietcd.pem --etcd_key antietcd.key --etcd_ca etcd.pem
|
||||
|
||||
etcd
|
||||
--client-cert-auth --cert-file=etcd.pem --key-file=etcd.key --trusted-ca-file=antietcd.pem \
|
||||
--peer-client-cert-auth --peer-cert-file=etcd.pem --peer-key-file=etcd.key --peer-trusted-ca-file=etcd.pem
|
||||
|
||||
### Mon со встроенным Antietcd
|
||||
|
||||
Mon
|
||||
- use_antietcd: true
|
||||
- use_auth: true
|
||||
- antietcd_cert = antietcd.pem
|
||||
- antietcd_key
|
||||
|
||||
### Отдельный Antietcd
|
||||
|
||||
Mon
|
||||
- use_antietcd: false
|
||||
- etcd_ca = antietcd.pem
|
||||
|
||||
Antietcd
|
||||
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js
|
||||
|
||||
## Варианты настройки
|
||||
|
||||
### Настройка по умолчанию
|
||||
|
||||
Используются только контрольные суммы данных на транспортном уровне. Соединения с etcd не шифруются.
|
||||
Аутентификация и авторизация не используется, любой клиент имеет доступ ко всем данным кластера.
|
||||
|
||||
Аналог настройки:
|
||||
- proto_checksums: payload
|
||||
|
||||
### Полная защита
|
||||
|
||||
Везде
|
||||
- osd_ca
|
||||
- client_ca
|
||||
- etcd_ca = antietcd.pem
|
||||
|
||||
OSD
|
||||
- osd_cert
|
||||
- osd_pkey
|
||||
|
||||
Клиент
|
||||
- cert
|
||||
- pkey
|
||||
|
||||
### Только защита etcd
|
||||
|
||||
- etcd_ca
|
||||
- etcd_cert
|
||||
- etcd_key
|
||||
|
||||
### antietcd и только защита antietcd
|
||||
|
||||
- etcd_ca
|
||||
- etcd_cert
|
||||
- etcd_key
|
||||
- use_antietcd: true
|
||||
- antietcd_cert = etcd_ca
|
||||
- antietcd_key
|
||||
- antietcd_ca = etcd_cert
|
||||
|
||||
### Полное шифрование протокола, включая данные
|
||||
|
||||
Внимание: если включить этот вариант защиты и при этом
|
||||
|
||||
### Только контрольные суммы на транспортном уровне, без шифрования
|
||||
|
||||
## Настройка Vault/OpenBao
|
||||
|
||||
openssl req -days 3650 -x509 -addext basicConstraints=critical,CA:TRUE,pathlen:1 --addext subjectAltName=DNS:vault \
|
||||
-new -newkey rsa:4096 -nodes -keyout vault.key -out vault.crt
|
||||
|
||||
bao status -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
|
||||
|
||||
bao operator init -n 1 -t 1 -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
|
||||
|
||||
bao operator unseal -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200
|
||||
|
||||
bao auth enable -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 cert
|
||||
|
||||
bao secrets enable -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 -path=secret kv-v1
|
||||
|
||||
bao kv put -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 secret/vitastor/testimg3 key=$(openssl rand -hex 64)
|
||||
|
||||
cat >testimg3.policy <<EOF
|
||||
path "/secret/vitastor/testimg3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
bao policy write -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 testimg3 testimg3.policy
|
||||
|
||||
bao write -ca-cert /etc/openbao/tls/vault.crt -address=https://vault:8200 auth/cert/certs/testimg3 certificate=@testimg3.crt display_name=testimg3 token_ttl=24h token_policies=testimg3
|
||||
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key --json '{}' https://vault:8200/v1/auth/cert/login
|
||||
|
||||
curl --cacert /etc/vitastor/vault.crt --cert testimg3.crt --key testimg3.key -H 'X-Vault-Token: s.Qkrm78BeK7Rqdz5MA3eJZNbu' https://vault:8200/v1/secret/vitastor/testimg3
|
||||
+21
-7
@@ -125,18 +125,31 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
|
||||
|
||||
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
|
||||
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
|
||||
Pool must be specified if there is more than one pool.
|
||||
Create an image. Options:
|
||||
|
||||
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
|
||||
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
|
||||
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
|
||||
If parent is not a snapshot, it must be a read-only image.
|
||||
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
|
||||
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
|
||||
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
|
||||
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
|
||||
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
|
||||
|
||||
Options:
|
||||
|
||||
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
|
||||
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
|
||||
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
|
||||
By default, the image retains its old encryption key when taking a snapshot.
|
||||
|
||||
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
|
||||
|
||||
@@ -151,6 +164,7 @@ You should resize file system in the image, if present, before shrinking it.
|
||||
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
|
||||
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
|
||||
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
|
||||
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+22
-8
@@ -127,19 +127,32 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
|
||||
|
||||
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
|
||||
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
|
||||
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
|
||||
Создать образ. Опции:
|
||||
|
||||
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
|
||||
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
|
||||
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
|
||||
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
|
||||
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
|
||||
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
|
||||
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
|
||||
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
|
||||
клиентов, если пишущий клиент максимум 1.
|
||||
Создать снимок образа `<image>` (можно использовать любую форму команды).
|
||||
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
|
||||
|
||||
Опции:
|
||||
|
||||
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
|
||||
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
|
||||
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
|
||||
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
|
||||
|
||||
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
|
||||
|
||||
@@ -156,6 +169,7 @@ vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
|
||||
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
|
||||
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
|
||||
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
@@ -262,3 +262,4 @@ Options:
|
||||
| `--logfile <FILE>` | log to the specified file |
|
||||
| `--enforce 1` | enforce permissions at the server side (no by default) |
|
||||
| `--foreground 1` | stay in foreground, do not daemonize |
|
||||
| `--trace` | trace all NFS requests |
|
||||
|
||||
@@ -274,3 +274,4 @@ VitastorFS из GPUDirect.
|
||||
| `--logfile <FILE>` | записывать логи в заданный файл |
|
||||
| `--enforce 1` | проверять права доступа на стороне сервера (по умолчанию нет) |
|
||||
| `--foreground 1` | не уходить в фон после запуска |
|
||||
| `--trace` | логгировать все запросы NFS |
|
||||
|
||||
+1
-1
Submodule json11 updated: fd37016cf8...edcd85b8bd
+50
-5
@@ -3,6 +3,7 @@
|
||||
|
||||
const AntiEtcd = require('antietcd');
|
||||
|
||||
const vitastor_auth_filter = require('./vitastor_auth_filter.js');
|
||||
const vitastor_persist_filter = require('./vitastor_persist_filter.js');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -18,7 +19,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,10 +27,16 @@ 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 cluster_local = cluster.map(s =>
|
||||
{
|
||||
const m = /^https?:\/\/(?:\[(.*)\]|([^\[\:]+))(?::(\d+))?$/.exec(s);
|
||||
return [ m[2] || m[1], m[3] || 2379 ];
|
||||
});
|
||||
const selected = cluster_local.filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
console.error('More than 1 etcd_address matches local IPs: '+(selected.join(', '))+', please specify port');
|
||||
process.exit(1);
|
||||
}
|
||||
else if (selected.length == 1)
|
||||
@@ -37,14 +44,52 @@ class AntiEtcdAdapter
|
||||
const antietcd_config = {
|
||||
ip: selected[0][0],
|
||||
port: selected[0][1],
|
||||
cert: config.antietcd_cert,
|
||||
key: config.antietcd_key,
|
||||
ca: config.antietcd_ca,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
|
||||
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
|
||||
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
|
||||
cluster_key: (config.etcd_prefix || '/vitastor'),
|
||||
stale_read: 1,
|
||||
log_level: 1,
|
||||
};
|
||||
if (config.etcd_proxy)
|
||||
{
|
||||
// Monitor may use the builtin etcd_proxy mode
|
||||
if (!config.etcd_proxy.urls)
|
||||
{
|
||||
console.error('etcd_proxy.urls are empty');
|
||||
process.exit(1);
|
||||
}
|
||||
antietcd_config.etcd_proxy = config.etcd_proxy.urls;
|
||||
antietcd_config.etcd_cert = config.etcd_proxy.cert;
|
||||
antietcd_config.etcd_key = config.etcd_proxy.key;
|
||||
antietcd_config.etcd_ca = config.etcd_proxy.ca;
|
||||
delete antietcd_config.data;
|
||||
delete antietcd_config.persist_filter;
|
||||
delete antietcd_config.cluster;
|
||||
delete antietcd_config.cluster_key;
|
||||
}
|
||||
const use_auth = config.use_auth || config.use_auth == null && config.client_ca;
|
||||
if (use_auth)
|
||||
{
|
||||
antietcd_config.client_cert_auth = true;
|
||||
antietcd_config.auth_filter = vitastor_auth_filter;
|
||||
antietcd_config.ca = config.client_ca;
|
||||
antietcd_config.osd_ca = config.osd_ca;
|
||||
antietcd_config.mon_ca = config.mon_ca;
|
||||
if (!config.etcd_proxy)
|
||||
{
|
||||
antietcd_config.peer_ca = config.antietcd_server_ca;
|
||||
if (!config.antietcd_server_ca || config.antietcd_server_ca == config.client_ca)
|
||||
{
|
||||
console.error('Secure setup requires separate antietcd_server_ca (for signing antietcd server certificates) and client_ca (for signing client certificates)');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in config)
|
||||
{
|
||||
if (key.substr(0, 9) === 'antietcd_')
|
||||
@@ -169,7 +214,7 @@ class AntiEtcdAdapter
|
||||
await new Promise(ok => setTimeout(ok, timeout-(Date.now()-prev)));
|
||||
}
|
||||
prev = Date.now();
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body);
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { user_type: 'mon' });
|
||||
if (res.error)
|
||||
{
|
||||
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
|
||||
|
||||
+27
-6
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -15,11 +17,30 @@ class EtcdAdapter
|
||||
this.ws = null;
|
||||
this.ws_alive = false;
|
||||
this.ws_keepalive_timer = null;
|
||||
this.opts = {};
|
||||
}
|
||||
|
||||
parse_config(config)
|
||||
{
|
||||
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
|
||||
if (config.mon_etcd_client_cert || config.etcd_client_cert)
|
||||
{
|
||||
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
|
||||
if (this.opts.cert.substr(0, 5) != '-----')
|
||||
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.mon_etcd_client_key || config.etcd_client_key)
|
||||
{
|
||||
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
|
||||
if (this.opts.key.substr(0, 5) != '-----')
|
||||
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.etcd_ca)
|
||||
{
|
||||
this.opts.ca = config.etcd_ca;
|
||||
if (this.opts.ca.substr(0, 5) != '-----')
|
||||
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
|
||||
}
|
||||
}
|
||||
|
||||
parse_etcd_addresses(addrs)
|
||||
@@ -39,7 +60,7 @@ class EtcdAdapter
|
||||
for (let url of addrs)
|
||||
{
|
||||
let scheme = 'http';
|
||||
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
|
||||
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
|
||||
const slash = url.indexOf('/');
|
||||
const colon = url.indexOf(':');
|
||||
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
|
||||
@@ -130,7 +151,7 @@ class EtcdAdapter
|
||||
}
|
||||
ok(false);
|
||||
}, this.mon.config.etcd_mon_timeout);
|
||||
this.ws = new WebSocket(base+'/watch');
|
||||
this.ws = new WebSocket(base+'/watch', this.opts);
|
||||
this.ws_used_url = cur_addr;
|
||||
const fail = () =>
|
||||
{
|
||||
@@ -272,7 +293,7 @@ class EtcdAdapter
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
}
|
||||
const res = await POST(base+path, body, timeout);
|
||||
const res = await POST(base+path, body, timeout, this.opts);
|
||||
if (this.mon.stopped)
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
@@ -298,7 +319,7 @@ class EtcdAdapter
|
||||
}
|
||||
}
|
||||
|
||||
function POST(url, body, timeout)
|
||||
function POST(url, body, timeout, opts)
|
||||
{
|
||||
return new Promise(ok =>
|
||||
{
|
||||
@@ -310,10 +331,10 @@ function POST(url, body, timeout)
|
||||
req = null;
|
||||
ok({ error: 'timeout' });
|
||||
}, timeout) : null;
|
||||
let req = http.request(url, { method: 'POST', headers: {
|
||||
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body_text.length,
|
||||
} }, (res) =>
|
||||
}, ...(opts||{}) }, (res) =>
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
|
||||
+22
-1
@@ -16,6 +16,7 @@ const etcd_allow = new RegExp('^'+[
|
||||
'config/pools',
|
||||
'config/osd/[1-9]\\d*',
|
||||
'config/pgs', // old name
|
||||
'config/user/.*',
|
||||
'pg/config',
|
||||
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
||||
'osd/state/[1-9]\\d*',
|
||||
@@ -45,7 +46,14 @@ const etcd_tree = {
|
||||
config_path: "/etc/vitastor/vitastor.conf",
|
||||
etcd_prefix: "/vitastor",
|
||||
// etcd connection - configurable online
|
||||
etcd_address: "10.0.115.10:2379/v3",
|
||||
etcd_address: "http://10.0.115.10:2379/v3",
|
||||
etcd_client_cert: "",
|
||||
etcd_client_key: "",
|
||||
osd_etcd_client_cert: "",
|
||||
osd_etcd_client_key: "",
|
||||
mon_etcd_client_cert: "",
|
||||
mon_etcd_client_key: "",
|
||||
etcd_ca: "",
|
||||
// mon
|
||||
etcd_mon_ttl: 5, // min: 1
|
||||
etcd_mon_timeout: 1000, // ms. min: 0
|
||||
@@ -201,6 +209,8 @@ const etcd_tree = {
|
||||
primary_affinity_tags?: 'nvme' | [ 'nvme', ... ],
|
||||
// scrub interval
|
||||
scrub_interval?: '30d',
|
||||
// users allowed to create images in this pool
|
||||
creator_group?: '',
|
||||
},
|
||||
...
|
||||
}, */
|
||||
@@ -217,10 +227,21 @@ const etcd_tree = {
|
||||
parent_id?: <inode_t>,
|
||||
readonly?: boolean,
|
||||
deleted?: boolean,
|
||||
enc_key?: string,
|
||||
owner?: string,
|
||||
owner_group?: string,
|
||||
reader_group?: string,
|
||||
}
|
||||
}
|
||||
}, */
|
||||
inode: {},
|
||||
/* user: {
|
||||
<username>: {
|
||||
type: 'osd'|'mon'|'admin'|'client',
|
||||
groups: string[],
|
||||
},
|
||||
}, */
|
||||
user: {},
|
||||
},
|
||||
osd: {
|
||||
state: {
|
||||
|
||||
+2
-2
@@ -627,7 +627,7 @@ class Mon
|
||||
if (this.state.pg.history[pool_id] &&
|
||||
this.state.pg.history[pool_id][pg])
|
||||
{
|
||||
pg_history[pg-1] = this.state.pg.history[pool_id][pg];
|
||||
pg_history[pg-1] = JSON.parse(JSON.stringify(this.state.pg.history[pool_id][pg]));
|
||||
}
|
||||
}
|
||||
const real_prev_pgs = [];
|
||||
@@ -719,7 +719,7 @@ class Mon
|
||||
this.next_recheck_timer = null;
|
||||
this.next_recheck_at = 0;
|
||||
this.schedule_recheck();
|
||||
}, now-this.next_recheck_at);
|
||||
}, (this.next_recheck_at-now)*1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.12",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ function scale_pg_history(prev_pg_history, prev_pgs, new_pgs)
|
||||
finish_pg_history(merged_history[1]);
|
||||
for (let i = 0; i < new_pg_count; i++)
|
||||
{
|
||||
new_pg_history[i] = { ...merged_history[1] };
|
||||
new_pg_history[i] = JSON.parse(JSON.stringify(merged_history[1]));
|
||||
}
|
||||
}
|
||||
// Mark history keys for removed PGs as removed
|
||||
@@ -102,7 +102,7 @@ function scale_pg_count(prev_pgs, new_pg_count)
|
||||
{
|
||||
for (let i = prev_pgs.length; i < new_pg_count; i++)
|
||||
{
|
||||
prev_pgs[i] = prev_pgs[i % prev_pgs.length];
|
||||
prev_pgs[i] = [ ...prev_pgs[i % prev_pgs.length] ];
|
||||
}
|
||||
}
|
||||
else if (prev_pgs.length > new_pg_count)
|
||||
|
||||
+327
-44
@@ -1,38 +1,278 @@
|
||||
#!/usr/bin/node
|
||||
// Simple systemd unit generator for etcd
|
||||
// Simple Vitastor etcd / antietcd / TLS configurator
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: MIT
|
||||
|
||||
// USAGE:
|
||||
// 1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes
|
||||
// 2) Run ./make-etcd.js. It will create the etcd service on one of specified IPs
|
||||
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const readline = require('readline');
|
||||
|
||||
run().catch(e => { console.error(e); process.exit(1); });
|
||||
|
||||
const help_text = `Initialize a Vitastor cluster (etcd, vitastor.conf and TLS certificates)
|
||||
(c) Vitaliy Filippov, 2019+ (MIT)
|
||||
|
||||
USAGE:
|
||||
1) Create a minimal vitastor.conf with etcd_address, osd_network and (optionally) use_auth.
|
||||
Example: {"etcd_address":["http://10.0.0.10:2379","http://10.0.0.11:2379","http://10.0.0.12:2379"],"use_auth":false,"osd_network":"10.0.0.0/24"}
|
||||
Or: {"etcd_address":["https://10.0.0.10:2379","https://10.0.0.11:2379","https://10.0.0.12:2379"],"use_auth":true,"osd_network":"10.0.0.0/24"}
|
||||
2) Run: ${process.argv[1]} [./vitastor.conf]
|
||||
You can run it on etcd/monitor nodes or on an external node.
|
||||
It configures etcd, generates TLS certificates (on the first or external node), copies them
|
||||
to other etcd/monitor nodes, and updates vitastor.conf with TLS options.
|
||||
3) If you have OSD-only nodes, run:
|
||||
${process.argv[1]} --copy-to-osd-node NODE_NAME ./vitastor.conf
|
||||
It copies vitastor.conf and required TLS certificates to that node.
|
||||
|
||||
OPTIONS:
|
||||
--gen-certs
|
||||
force certificate generation even if it's not the first node
|
||||
--no-certs
|
||||
disable certificate generation
|
||||
--no-copy
|
||||
do not copy initial certificates to other nodes
|
||||
--copy-to-osd-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates required for OSDs to NODES using scp
|
||||
--copy-to-mon-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates required for monitor and etcd to NODES using scp
|
||||
--copy-to-client-node NODE[,NODE2,...]
|
||||
copy vitastor.conf and TLS certificates required for clients to NODES using scp
|
||||
`;
|
||||
|
||||
async function run()
|
||||
{
|
||||
const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
|
||||
if (config_path == '-h' || config_path == '--help')
|
||||
let config_path = '/etc/vitastor/vitastor.conf';
|
||||
let config_dir = '/etc/vitastor/';
|
||||
let gen_certs = 'auto';
|
||||
let copy_initial = true;
|
||||
let copy_to_osd =
|
||||
for (let i = 2; i < process.argv.length; i++)
|
||||
{
|
||||
console.log(
|
||||
'Initialize systemd etcd service for Vitastor\n'+
|
||||
'(c) Vitaliy Filippov, 2019+ (MIT)\n'+
|
||||
'\n'+
|
||||
'USAGE:\n'+
|
||||
'1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes\n'+
|
||||
'2) Run '+process.argv[1]+' [config_path]\n'
|
||||
);
|
||||
process.exit(0);
|
||||
if (arg == '-h' || arg == '--help')
|
||||
{
|
||||
console.log(help_text);
|
||||
process.exit(0);
|
||||
}
|
||||
else if (arg == '--only-certs')
|
||||
{
|
||||
i++;
|
||||
gen_certs =
|
||||
}
|
||||
else if (arg == '--copy')
|
||||
{
|
||||
i++;
|
||||
copy = process.argv[i];
|
||||
if (copy !== 'ask' && copy !== 'yes' && copy !== 'no')
|
||||
{
|
||||
console.error('--copy should be "ask", "yes" or "no"');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
else if (arg[0] == '-')
|
||||
{
|
||||
console.error('Unknown option: '+arg[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
config_path = arg;
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(config_path))
|
||||
{
|
||||
console.log(config_path+' is missing');
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => /^(https?):\/\/(\[[^\]]+\]|[^\[\]\:\/]+)(?::(\d+))?/.exec(s.toLowerCase()))
|
||||
.filter(s => s)
|
||||
.map(s => ({
|
||||
scheme: s[1],
|
||||
addr: s[2].indexOf(':') && s[2][0] != '[' ? '['+s[2]+']' : s[2],
|
||||
ip: s[2][0] == '[' ? s[2].substr(1, s[2].length-2) : s[2],
|
||||
port: s[3],
|
||||
}));
|
||||
const tls = etcds.filter(e => e.scheme === 'https').length > 0;
|
||||
const use_auth = tls && config.use_auth;
|
||||
const num = select_local_etcd(etcds);
|
||||
if (tls)
|
||||
{
|
||||
if (gen_certs === 'yes')
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('Certificate generation is requested explicitly, generating');
|
||||
}
|
||||
else if (gen_certs === 'no')
|
||||
{
|
||||
gen_certs = false;
|
||||
console.log('Certificate generation is disabled explicitly, skipping');
|
||||
}
|
||||
else if (num < 0)
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('No matching IPs in etcd_address from '+config_path+', only generating certificates');
|
||||
}
|
||||
else if (fs.existsSync("/etc/vitastor/etcd.crt"))
|
||||
{
|
||||
gen_certs = false;
|
||||
console.log('/etc/vitastor/etcd.crt already exists, assuming certificates are already generated');
|
||||
}
|
||||
else if (num === 0)
|
||||
{
|
||||
gen_certs = true;
|
||||
console.log('This is monitor node 1, generating certificates');
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('This is monitor node '+(num+1)+', /etc/vitastor/etcd.crt does not exist, please copy certificates to this node');
|
||||
process.exit(1);
|
||||
}
|
||||
if (gen_certs)
|
||||
{
|
||||
if (copy === 'ask')
|
||||
copy = await ask_copy('Copy certificates and vitastor.conf to other nodes after generation?');
|
||||
copy = (copy === 'y' || copy === 'yes');
|
||||
await make_certs(dir, copy);
|
||||
}
|
||||
await write_auth_config(config, config_path);
|
||||
}
|
||||
if (num < 0)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(tls && gen_certs ? 0 : 1);
|
||||
}
|
||||
await configure_etcd();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function ask_copy(question)
|
||||
{
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: '> ',
|
||||
});
|
||||
let copy;
|
||||
while (true)
|
||||
{
|
||||
copy = await new Promise(ok => rl.question(question, ok));
|
||||
if (copy != 'y' && copy != 'n' && copy != 'yes' && copy != 'no')
|
||||
console.log('Please type "yes" or "no"');
|
||||
else
|
||||
break;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
async function make_certs(dir, copy)
|
||||
{
|
||||
console.log(`-----
|
||||
Generating certificates in ${dir}
|
||||
-----
|
||||
`);
|
||||
await make_ca("/O=Vitastor etcd CA", dir+"etcd_ca");
|
||||
await make_signed("/CN=Vitastor etcd", dir+"etcd", dir+"etcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
|
||||
if (use_auth)
|
||||
{
|
||||
await make_ca("/O=Vitastor Antietcd CA", dir+"antietcd_ca");
|
||||
await make_signed("/CN=Vitastor Antietcd", dir+"antietcd", dir+"antietcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
|
||||
await make_ca("/CN=Vitastor OSD", dir+"osd");
|
||||
await make_ca("/O=Vitastor Client CA", dir+"client_ca");
|
||||
await make_signed("/CN=admin", dir+"admin", dir+"client_ca");
|
||||
}
|
||||
if (use_auth)
|
||||
{
|
||||
console.log(`-----
|
||||
Certificates generated, commands to copy them:
|
||||
- Monitor+OSD node:
|
||||
cd ${dir} && scp antietcd_ca.crt antietcd.crt antietcd.key osd.crt osd.key client_ca.crt etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
|
||||
- Monitor node:
|
||||
cd ${dir} && scp antietcd_ca.crt antietcd.crt antietcd.key osd.crt client_ca.crt etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
|
||||
- OSD node:
|
||||
cd ${dir} && scp antietcd_ca.crt osd.crt osd.key client_ca.crt root@NODE:/etc/vitastor/
|
||||
-----
|
||||
`);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(`-----
|
||||
Certificates generated, commands to copy them:
|
||||
- Monitor node:
|
||||
cd ${dir} && scp etcd_ca.crt etcd.crt etcd.key root@NODE:/etc/vitastor/
|
||||
-----
|
||||
`);
|
||||
}
|
||||
if (copy)
|
||||
{
|
||||
const to_copy = use_auth
|
||||
? [ "antietcd_ca.crt", "antietcd.crt", "antietcd.key", "osd.crt", "osd.key", "client_ca.crt", "etcd_ca.crt", "etcd.crt", "etcd.key" ]
|
||||
: [ "etcd_ca.crt", "etcd.crt", "etcd.key" ];
|
||||
for (const node of etcds)
|
||||
{
|
||||
await system("scp "+dir+to_copy.join(" "+dir)+" root@"+node.ip+"/etc/vitastor/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.warn('Certificates generated in /etc/vitastor, please copy them to other nodes');
|
||||
}
|
||||
}
|
||||
|
||||
async function write_auth_config(config, config_path)
|
||||
{
|
||||
const auth = {};
|
||||
if (use_auth)
|
||||
{
|
||||
auth["use_antietcd"] = true;
|
||||
auth["etcd_proxy"] = {
|
||||
urls: etcds.map(e => e.ip+':2381'),
|
||||
cert: "/etc/vitastor/antietcd.crt",
|
||||
key: "/etc/vitastor/antietcd.key",
|
||||
ca: "/etc/vitastor/etcd_ca.crt",
|
||||
};
|
||||
auth["antietcd_cert"] = "/etc/vitastor/antietcd.crt";
|
||||
auth["antietcd_key"] = "/etc/vitastor/antietcd.key";
|
||||
auth["etcd_ca"] = "/etc/vitastor/antietcd_ca.crt";
|
||||
auth["osd_cert"] = "/etc/vitastor/osd.crt";
|
||||
auth["osd_pkey"] = "/etc/vitastor/osd.key";
|
||||
auth["osd_ca"] = "/etc/vitastor/osd.crt";
|
||||
auth["client_ca"] = "/etc/vitastor/client_ca.crt";
|
||||
auth["cert"] = "/etc/vitastor/admin.crt";
|
||||
auth["pkey"] = "/etc/vitastor/admin.key";
|
||||
}
|
||||
else
|
||||
{
|
||||
auth["etcd_ca"] = "/etc/vitastor/etcd.crt";
|
||||
}
|
||||
for (const k in auth)
|
||||
{
|
||||
if ((k in config) && JSON.stringify(auth[k]) != JSON.stringify(config[k]))
|
||||
{
|
||||
// Auth options already overridden with non-default
|
||||
console.log(k+" is already overridden in "+config_path+", skipping config update");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const k in auth)
|
||||
{
|
||||
config[k] = auth[k];
|
||||
}
|
||||
console.log(`-----
|
||||
Updating ${config_path}
|
||||
-----
|
||||
`);
|
||||
fs.writeFileSync(config_path, JSON.stringify(config, 0, 4));
|
||||
}
|
||||
|
||||
async configure_etcd()
|
||||
{
|
||||
const in_docker = fs.existsSync("/etc/vitastor/etcd.conf") &&
|
||||
fs.existsSync("/etc/vitastor/docker.conf");
|
||||
if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service"))
|
||||
@@ -45,30 +285,49 @@ async function run()
|
||||
console.log("/etc/systemd/system/etcd.service already exists");
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
const etcd_url = etcds[num].scheme + '://' + etcds[num].addr;
|
||||
const options = {
|
||||
name: 'etcd'+etcds[num].ip.replace(/[^0-9a-z_]/ig, '_'),
|
||||
advertise_client_urls: etcd_url+':'+(use_auth ? 2381 : 2379),
|
||||
listen_client_urls: etcd_url+':'+(use_auth ? 2381 : 2379),
|
||||
initial_advertise_peer_urls: etcd_url+':2380',
|
||||
listen_peer_urls: etcd_url+':2380',
|
||||
initial_cluster_token: 'vitastor-etcd-1',
|
||||
initial_cluster_state: 'new',
|
||||
initial_cluster: etcds.map(e => `etcd${e.ip.replace(/[^0-9a-z_]/ig, '_')}=${e.scheme}://${e.addr}:2380`).join(','),
|
||||
snapshot_count: 10000,
|
||||
max_txn_ops: 100000,
|
||||
max_request_bytes: 104857600,
|
||||
auto_compaction_retention: 10,
|
||||
auto_compaction_mode: 'revision',
|
||||
};
|
||||
if (tls)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
options['cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_auth)
|
||||
{
|
||||
options['client_cert_auth'] = '1';
|
||||
options['trusted_ca_file'] = '/etc/vitastor/antietcd.crt';
|
||||
}
|
||||
options['peer_cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['peer_key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_auth)
|
||||
{
|
||||
options['peer_client_cert_auth'] = '1';
|
||||
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd.crt';
|
||||
}
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => (''+s).replace(/^https?:\/\/|(:\d+)?(\/.*)?$/g, '').replace(/^\[(.*)\]$/, '$1').toLowerCase());
|
||||
const num = select_local_etcd(etcds);
|
||||
if (num < 0)
|
||||
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
|
||||
? fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' })
|
||||
: "";
|
||||
for (const k in options)
|
||||
{
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(0);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_'+k.toUpperCase().replace(/-/, '_'), options[k]);
|
||||
}
|
||||
const etcd_url = 'http://' + (etcds[num].indexOf(':') >= 0 ? '['+etcds[num]+']' : etcds[num]);
|
||||
const etcd_name = 'etcd'+etcds[num].replace(/[^0-9a-z_]/ig, '_');
|
||||
const etcd_cluster = etcds.map(e => `etcd${e.replace(/[^0-9a-z_]/ig, '_')}=http://${e.indexOf(':') >= 0 ? '['+e+']' : e}:2380`).join(',');
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
if (in_docker)
|
||||
{
|
||||
let etcd_conf = fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' });
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_NAME', etcd_name);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_IP', etcds[num]);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_INITIAL_CLUSTER', etcd_cluster);
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
console.log('etcd for Vitastor configured. Run `systemctl enable --now vitastor-etcd` to start etcd');
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -83,12 +342,8 @@ Wants=network-online.target local-fs.target time-sync.target
|
||||
[Service]
|
||||
Restart=always
|
||||
Environment=GOGC=50
|
||||
ExecStart=etcd --name ${etcd_name} --data-dir /var/lib/etcd/vitastor \\
|
||||
--snapshot-count 10000 --advertise-client-urls ${etcd_url}:2379 --listen-client-urls ${etcd_url}:2379 \\
|
||||
--initial-advertise-peer-urls ${etcd_url}:2380 --listen-peer-urls ${etcd_url}:2380 \\
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster ${etcd_cluster} \\
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \\
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision
|
||||
EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
ExecStart=etcd --data-dir /var/lib/etcd/vitastor
|
||||
WorkingDirectory=/var/lib/etcd/vitastor
|
||||
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
|
||||
User=etcd
|
||||
@@ -106,7 +361,6 @@ WantedBy=multi-user.target
|
||||
// Disable distribution etcd unit and enable our one
|
||||
await system(`systemctl disable --now etcd`);
|
||||
await system(`systemctl enable --now vitastor-etcd`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function replace_env(text, key, value)
|
||||
@@ -119,16 +373,29 @@ function replace_env(text, key, value)
|
||||
function select_local_etcd(etcds)
|
||||
{
|
||||
const ifaces = os.networkInterfaces();
|
||||
const local = {};
|
||||
for (const ifname in ifaces)
|
||||
{
|
||||
for (const iface of ifaces[ifname])
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
if (etcds[i] == iface.address.toLowerCase())
|
||||
return i;
|
||||
{
|
||||
const addr = iface.address;
|
||||
if (iface.family == 'IPv6')
|
||||
local[addr.toLowerCase()] = local['['+addr.toLowerCase()+']'] = true;
|
||||
else
|
||||
local[addr] = true;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
{
|
||||
if (local[etcds[i].addr])
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
async function system(cmd)
|
||||
{
|
||||
console.log('Running '+cmd);
|
||||
const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
|
||||
let finish_cb;
|
||||
cp.on('exit', () => finish_cb && finish_cb());
|
||||
@@ -136,3 +403,19 @@ async function system(cmd)
|
||||
await new Promise(ok => finish_cb = ok);
|
||||
return cp.exitCode;
|
||||
}
|
||||
|
||||
async function make_ca(subj, filename)
|
||||
{
|
||||
if (await system("openssl req -days 3650 -x509 -subj '"+subj+"' -addext basicConstraints=critical,CA:TRUE,pathlen:1"+
|
||||
" -new -newkey rsa:4096 -nodes -keyout "+filename+".key -out "+filename+".crt"))
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function make_signed(subj, f, ca, san)
|
||||
{
|
||||
if (await system(`openssl req -subj '${subj}' ${san ? "-addext 'subjectAltName="+san+"'" : ""} -nodes -new -keyout ${f}.key -out ${f}.csr`))
|
||||
process.exit(1);
|
||||
if (await system(`openssl x509 -req -days 3650 -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
|
||||
process.exit(1);
|
||||
fs.unlinkSync(f+".csr");
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ function derive_osd_stats(st, prev, prev_diff)
|
||||
const n = c.count - BigInt(pr && pr.count||0);
|
||||
diff.recovery_stats[op] = { ...c, bps: n > 0 ? b*1000n/timediff : 0n, iops: n > 0 ? n*1000n/timediff : 0n };
|
||||
}
|
||||
diff.inode_stats = {};
|
||||
for (const pool_id in st.inode_stats||{})
|
||||
{
|
||||
diff.inode_stats[pool_id] = {};
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
// AntiEtcd authentication filter for Vitastor
|
||||
// (c) Vitaliy Filippov, 2026
|
||||
// License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
|
||||
|
||||
// Permissions are based on:
|
||||
// 1. Users.
|
||||
// Stored in /vitastor/config/user/<username>.
|
||||
// Has 2 properties:
|
||||
// - type, one of: admin, client.
|
||||
// admin has full access to all images and also to cluster config.
|
||||
// client has r/w access to owned images and r/o access to images with reader_group.
|
||||
// - groups, a list of group names the user is included in.
|
||||
// 2. Images.
|
||||
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
|
||||
// - owner (user name)
|
||||
// - owner_group (group name)
|
||||
// - reader_group
|
||||
// 3. Certificates.
|
||||
// - osd, mon use their own trusted certificates.
|
||||
|
||||
const { X509Certificate } = require('node:crypto');
|
||||
|
||||
const static_perms = {
|
||||
invalid: {
|
||||
keys: {},
|
||||
prefixes: {},
|
||||
},
|
||||
osd: {
|
||||
keys: { '/pg/config': false },
|
||||
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
|
||||
},
|
||||
mon: {
|
||||
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
|
||||
prefixes: {
|
||||
'/config/': false, '/osd/': false, '/mon/': true, '/pg/history/': true,
|
||||
'/pgstats/': false, '/inode/stats/': true, '/pool/stats/': true,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
keys: { '/stats': false },
|
||||
prefixes: {
|
||||
'/config/': true, '/osd/': true, '/index/': true, '/pg/history/': true,
|
||||
'/mon/': false, '/pg/': false, '/pgstats/': false, '/inode/stats/': false, '/pool/stats/': false,
|
||||
},
|
||||
},
|
||||
client: {
|
||||
keys: { '/config/global': false, '/config/node_placement': false, '/config/pools': false, '/pg/config': false },
|
||||
prefixes: { '/osd/state/': false, '/pg/state/': false, '/index/maxid/': false },
|
||||
},
|
||||
};
|
||||
|
||||
const api_perms = {
|
||||
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
admin: { maintenance_status: true },
|
||||
client: { maintenance_status: true },
|
||||
};
|
||||
|
||||
class VitastorAuthFilter
|
||||
{
|
||||
constructor(antietcd)
|
||||
{
|
||||
this.cfg = antietcd.cfg;
|
||||
this.antietcd = antietcd;
|
||||
this.prefix = this.cfg.vitastor_prefix || '/vitastor';
|
||||
this.prefix_parts = this.prefix.split('/');
|
||||
}
|
||||
|
||||
async init()
|
||||
{
|
||||
if (!this.cfg.cert || !this.cfg.key || !this.cfg.osd_ca || !this.cfg.etcd_proxy && !this.cfg.peer_ca || !this.cfg.client_cert_auth)
|
||||
{
|
||||
throw new Error('Authenticated Vitastor setups require enabled client_cert_auth, cert, key'+
|
||||
' and separate ca (client CA), osd_ca'+(this.cfg.etcd_proxy ? '' : ', peer_ca')+' and optionally mon_ca');
|
||||
}
|
||||
this.osd_ca = await this.antietcd.readPEM(this.cfg.osd_ca);
|
||||
this.osd_ca_obj = new X509Certificate(this.osd_ca);
|
||||
this.antietcd.tls.ca.push(this.osd_ca);
|
||||
if (this.cfg.mon_ca)
|
||||
{
|
||||
this.mon_ca = await this.antietcd.readPEM(this.cfg.mon_ca);
|
||||
this.mon_ca_obj = new X509Certificate(this.mon_ca_obj);
|
||||
this.antietcd.tls.ca.push(this.mon_ca);
|
||||
}
|
||||
}
|
||||
|
||||
init_context(context, clientCert)
|
||||
{
|
||||
let cert = clientCert;
|
||||
while (cert)
|
||||
{
|
||||
if (cert.fingerprint256 == this.osd_ca_obj.fingerprint256)
|
||||
{
|
||||
context.user_type = 'osd';
|
||||
break;
|
||||
}
|
||||
if (this.mon_ca_obj && cert.fingerprint256 == this.mon_ca_obj.fingerprint256)
|
||||
{
|
||||
context.user_type = 'mon';
|
||||
break;
|
||||
}
|
||||
cert = cert.issuerCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
_get(path, decode)
|
||||
{
|
||||
let cur = this.antietcd.etctree.state;
|
||||
path = path instanceof Array ? path : path.split('/');
|
||||
for (const p of path)
|
||||
{
|
||||
if (!cur.children)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
cur = cur.children[p];
|
||||
if (!cur)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (decode)
|
||||
{
|
||||
return this._decode(path, cur.value);
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
_decode(path, cur)
|
||||
{
|
||||
if (!cur)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (cur)
|
||||
{
|
||||
try
|
||||
{
|
||||
cur = JSON.parse(cur);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.warn('Invalid JSON in '+(path instanceof Array ? path.join('/') : path)+': '+e);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
// userInfo: { name: string, type: string, perms: static_perms[type], groups: { [string]: true } }
|
||||
_check_compare(check, userInfo, checked)
|
||||
{
|
||||
let key = String(check.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (key in userInfo.perms.keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userInfo.type == 'client')
|
||||
{
|
||||
// Image permissions
|
||||
if (key.substr(0, 14) == '/config/inode/')
|
||||
{
|
||||
// Allowed to check that a key does not exist
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
const data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
const inode = this._decode(check.key, data.value);
|
||||
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/image/')
|
||||
{
|
||||
// Allowed to check that a key does not exist
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
let data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
data = this._decode(check.key, data.value);
|
||||
if (data)
|
||||
{
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
|
||||
if (inode && (inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
checked['M'+key] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/maxid/')
|
||||
{
|
||||
const pool_id = key.substr(13);
|
||||
const pool_cfg = this._get([ ...this.prefix_parts, 'config', 'pools' ], true);
|
||||
if (!pool_cfg || !pool_cfg[pool_id] || !pool_cfg[pool_id].creator_group || !userInfo.groups[pool_cfg[pool_id].creator_group])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (check.target == 'VERSION' && check.version == 0)
|
||||
{
|
||||
checked['I'+parseInt(key.substr(13))+'_0'] = true;
|
||||
return true;
|
||||
}
|
||||
else if (check.target == 'MOD')
|
||||
{
|
||||
const data = this._get(check.key);
|
||||
if (!data || data.mod_revision != check.mod_revision)
|
||||
{
|
||||
// Break check to trigger CAS failure
|
||||
check.mod_revision = '18446744073709551615'; // UINT64_MAX
|
||||
return true;
|
||||
}
|
||||
checked['I'+parseInt(key.substr(13))+'_'+data.value] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_read(kv, userInfo)
|
||||
{
|
||||
let key = String(kv.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (key in userInfo.perms.keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (userInfo.type == 'client')
|
||||
{
|
||||
// Image permissions
|
||||
if (key.substr(0, 14) == '/config/inode/')
|
||||
{
|
||||
const inode = this._decode(kv.key, kv.value);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/index/image/')
|
||||
{
|
||||
const data = this._decode(kv.key, kv.value);
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.pool_id, data.id ], true);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (key.substr(0, 13) == '/inode/stats/')
|
||||
{
|
||||
const [ pool_id, id ] = key.substr(13).split('/');
|
||||
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', pool_id, id ], true);
|
||||
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
|
||||
inode.owner_group && userInfo.groups[inode.owner_group] ||
|
||||
inode.owner === userInfo.name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_write(put, userInfo, checked)
|
||||
{
|
||||
let key = String(put.key);
|
||||
if (key.substr(0, this.prefix.length) !== this.prefix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key = key.substr(this.prefix.length);
|
||||
if (userInfo.perms.keys[key])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (checked && userInfo.type == 'client')
|
||||
{
|
||||
if (key.substr(0, 13) == '/index/maxid/' &&
|
||||
checked['I'+parseInt(key.substr(13))+'_'+(put.value-1)])
|
||||
{
|
||||
// Allowed to increment maxid
|
||||
return true;
|
||||
}
|
||||
if (checked['M'+key])
|
||||
{
|
||||
// Allowed to modify known images with CAS checks
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_check_req(req, userInfo, checked)
|
||||
{
|
||||
let r;
|
||||
if ((r = (req.request_range || req.requestRange)))
|
||||
{
|
||||
// All range queries are allowed, but responses are filtered - it's simpler
|
||||
}
|
||||
else if ((r = (req.request_put || req.requestPut)))
|
||||
{
|
||||
if (!this._check_write(r, userInfo, checked))
|
||||
return false;
|
||||
}
|
||||
else if ((r = (req.request_delete_range || req.requestDeleteRange)))
|
||||
{
|
||||
if (!r.range_end || r.range_end === r.key)
|
||||
{
|
||||
if (!this._check_write({ key: r.key }, userInfo))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All keys in range must satisfy prefix
|
||||
r.range_end = String(r.range_end);
|
||||
if (r.key.length != r.range_end.length ||
|
||||
r.key[r.key.length-1] != '/' ||
|
||||
r.range_end[r.range_end.length-1] != '0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let key = r.key.substr(this.prefix.length);
|
||||
let found = false;
|
||||
for (const pfx in userInfo.perms.prefixes)
|
||||
{
|
||||
if (userInfo.perms.prefixes[pfx] && key.substr(0, pfx.length) == pfx)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_get_user(context)
|
||||
{
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
{
|
||||
return {
|
||||
name: context.user_type,
|
||||
type: context.user_type,
|
||||
perms: static_perms[context.user_type],
|
||||
};
|
||||
}
|
||||
if (!context.username)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
if (!userInfo)
|
||||
{
|
||||
userInfo = { type: 'client' };
|
||||
}
|
||||
else if (userInfo.type !== 'client' && userInfo.type !== 'admin')
|
||||
{
|
||||
userInfo.type = 'client';
|
||||
}
|
||||
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
|
||||
userInfo.name = context.username;
|
||||
if (userInfo.groups instanceof Array)
|
||||
{
|
||||
userInfo.groups = userInfo.groups.reduce((a, c) => { a[c] = true; return a; }, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
userInfo.groups = {};
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
filter_api(context, api/*, data*/)
|
||||
{
|
||||
let type = 'client';
|
||||
if (context.user_type === 'osd' || context.user_type === 'mon')
|
||||
{
|
||||
type = context.user_type;
|
||||
}
|
||||
else if (context.username)
|
||||
{
|
||||
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
|
||||
if (userInfo && userInfo.type === 'admin')
|
||||
{
|
||||
type = 'admin';
|
||||
}
|
||||
}
|
||||
return api_perms[type] && api_perms[type][api];
|
||||
}
|
||||
|
||||
filter_txn(context, txn)
|
||||
{
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
const checked = {};
|
||||
if (txn.compare)
|
||||
{
|
||||
for (const check of txn.compare)
|
||||
{
|
||||
if (!this._check_compare(check, userInfo, checked))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Special transactions:
|
||||
// 1. create image: create config/inode and index/image, increment index/maxid/<pool> (with CAS)
|
||||
// 2. create snapshot: same as create image but also rename previous to @snap
|
||||
if (txn.success)
|
||||
{
|
||||
for (const req of txn.success)
|
||||
{
|
||||
if (!this._check_req(req, userInfo, checked))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (txn.failure)
|
||||
{
|
||||
for (const req of txn.failure)
|
||||
{
|
||||
if (!this._check_req(req, userInfo, null))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return txn;
|
||||
}
|
||||
|
||||
filter_txn_response(context, txn, res)
|
||||
{
|
||||
if (!res.responses)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
for (const resp of res.responses)
|
||||
{
|
||||
if (resp.response_range && resp.response_range.kvs)
|
||||
{
|
||||
resp.response_range.kvs = [];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const resp of res.responses)
|
||||
{
|
||||
if (resp.response_range && resp.response_range.kvs)
|
||||
{
|
||||
resp.response_range.kvs = resp.response_range.kvs.filter(kv => this._check_read(kv, userInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter_watch_message(context, msg)
|
||||
{
|
||||
if (!msg.result || !msg.result.events)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const userInfo = this._get_user(context);
|
||||
if (!userInfo)
|
||||
{
|
||||
msg.result.events = [];
|
||||
return;
|
||||
}
|
||||
msg.result.events = msg.result.events.filter(ev => this._check_read(ev.kv, userInfo));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VitastorAuthFilter;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.12",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.6'
|
||||
VITASTOR_VERSION = '3.0.12'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -0,0 +1,637 @@
|
||||
diff --git a/include/libvirt/libvirt-storage.h b/include/libvirt/libvirt-storage.h
|
||||
index aaad4a3da1..5f5daa8341 100644
|
||||
--- a/include/libvirt/libvirt-storage.h
|
||||
+++ b/include/libvirt/libvirt-storage.h
|
||||
@@ -326,6 +326,7 @@ typedef enum {
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS = 1 << 17, /* (Since: 1.2.8) */
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE = 1 << 18, /* (Since: 3.1.0) */
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI_DIRECT = 1 << 19, /* (Since: 5.6.0) */
|
||||
+ VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR = 1 << 20, /* (Since: 5.0.0) */
|
||||
} virConnectListAllStoragePoolsFlags;
|
||||
|
||||
int virConnectListAllStoragePools(virConnectPtr conn,
|
||||
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
|
||||
index 9ca5c2450c..cc52f00c0c 100644
|
||||
--- a/src/conf/domain_conf.c
|
||||
+++ b/src/conf/domain_conf.c
|
||||
@@ -7453,7 +7453,8 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
|
||||
src->configFile = virXPathString("string(./config/@file)", ctxt);
|
||||
|
||||
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
|
||||
- src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS)
|
||||
+ src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
|
||||
+ src->protocol == VIR_STORAGE_NET_PROTOCOL_VITASTOR)
|
||||
src->query = virXMLPropString(node, "query");
|
||||
|
||||
if (virDomainStorageNetworkParseHosts(node, ctxt, &src->hosts, &src->nhosts) < 0)
|
||||
@@ -32187,6 +32188,7 @@ virDomainStorageSourceTranslateSourcePool(virStorageSource *src,
|
||||
|
||||
case VIR_STORAGE_POOL_MPATH:
|
||||
case VIR_STORAGE_POOL_RBD:
|
||||
+ case VIR_STORAGE_POOL_VITASTOR:
|
||||
case VIR_STORAGE_POOL_SHEEPDOG:
|
||||
case VIR_STORAGE_POOL_GLUSTER:
|
||||
case VIR_STORAGE_POOL_LAST:
|
||||
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
|
||||
index 7346a61731..83e94d762e 100644
|
||||
--- a/src/conf/domain_validate.c
|
||||
+++ b/src/conf/domain_validate.c
|
||||
@@ -520,6 +520,7 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
|
||||
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
||||
break;
|
||||
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
||||
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
||||
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
||||
@@ -592,7 +593,7 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
|
||||
}
|
||||
}
|
||||
|
||||
- /* internal snapshots and config files are currently supported only with rbd: */
|
||||
+ /* internal snapshots are currently supported only with rbd: */
|
||||
if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK &&
|
||||
src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD) {
|
||||
if (src->snapshot) {
|
||||
@@ -600,10 +601,14 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
|
||||
_("<snapshot> element is currently supported only with 'rbd' disks"));
|
||||
return -1;
|
||||
}
|
||||
-
|
||||
+ }
|
||||
+ /* config files are currently supported only with rbd and vitastor: */
|
||||
+ if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK &&
|
||||
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD &&
|
||||
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_VITASTOR) {
|
||||
if (src->configFile) {
|
||||
virReportError(VIR_ERR_XML_ERROR, "%s",
|
||||
- _("<config> element is currently supported only with 'rbd' disks"));
|
||||
+ _("<config> element is currently supported only with 'rbd' and 'vitastor' disks"));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
|
||||
index 114dd3f96f..c71f9a3277 100644
|
||||
--- a/src/conf/schemas/domaincommon.rng
|
||||
+++ b/src/conf/schemas/domaincommon.rng
|
||||
@@ -2093,6 +2093,35 @@
|
||||
</element>
|
||||
</define>
|
||||
|
||||
+ <define name="diskSourceNetworkProtocolVitastor">
|
||||
+ <element name="source">
|
||||
+ <interleave>
|
||||
+ <attribute name="protocol">
|
||||
+ <value>vitastor</value>
|
||||
+ </attribute>
|
||||
+ <ref name="diskSourceCommon"/>
|
||||
+ <optional>
|
||||
+ <attribute name="name"/>
|
||||
+ </optional>
|
||||
+ <optional>
|
||||
+ <attribute name="query"/>
|
||||
+ </optional>
|
||||
+ <zeroOrMore>
|
||||
+ <ref name="diskSourceNetworkHost"/>
|
||||
+ </zeroOrMore>
|
||||
+ <optional>
|
||||
+ <element name="config">
|
||||
+ <attribute name="file">
|
||||
+ <ref name="absFilePath"/>
|
||||
+ </attribute>
|
||||
+ <empty/>
|
||||
+ </element>
|
||||
+ </optional>
|
||||
+ <empty/>
|
||||
+ </interleave>
|
||||
+ </element>
|
||||
+ </define>
|
||||
+
|
||||
<define name="diskSourceNetworkProtocolISCSI">
|
||||
<element name="source">
|
||||
<attribute name="protocol">
|
||||
@@ -2443,6 +2472,7 @@
|
||||
<ref name="diskSourceNetworkProtocolSimple"/>
|
||||
<ref name="diskSourceNetworkProtocolVxHS"/>
|
||||
<ref name="diskSourceNetworkProtocolNFS"/>
|
||||
+ <ref name="diskSourceNetworkProtocolVitastor"/>
|
||||
</choice>
|
||||
</define>
|
||||
|
||||
diff --git a/src/conf/storage_conf.c b/src/conf/storage_conf.c
|
||||
index 1dc9365bf2..a8a736be81 100644
|
||||
--- a/src/conf/storage_conf.c
|
||||
+++ b/src/conf/storage_conf.c
|
||||
@@ -56,7 +56,7 @@ VIR_ENUM_IMPL(virStoragePool,
|
||||
"logical", "disk", "iscsi",
|
||||
"iscsi-direct", "scsi", "mpath",
|
||||
"rbd", "sheepdog", "gluster",
|
||||
- "zfs", "vstorage",
|
||||
+ "zfs", "vstorage", "vitastor",
|
||||
);
|
||||
|
||||
VIR_ENUM_IMPL(virStoragePoolFormatFileSystem,
|
||||
@@ -242,6 +242,18 @@ static virStoragePoolTypeInfo poolTypeInfo[] = {
|
||||
.formatToString = virStorageFileFormatTypeToString,
|
||||
}
|
||||
},
|
||||
+ {.poolType = VIR_STORAGE_POOL_VITASTOR,
|
||||
+ .poolOptions = {
|
||||
+ .flags = (VIR_STORAGE_POOL_SOURCE_HOST |
|
||||
+ VIR_STORAGE_POOL_SOURCE_NETWORK |
|
||||
+ VIR_STORAGE_POOL_SOURCE_NAME),
|
||||
+ },
|
||||
+ .volOptions = {
|
||||
+ .defaultFormat = VIR_STORAGE_FILE_RAW,
|
||||
+ .formatFromString = virStorageVolumeFormatFromString,
|
||||
+ .formatToString = virStorageFileFormatTypeToString,
|
||||
+ }
|
||||
+ },
|
||||
{.poolType = VIR_STORAGE_POOL_SHEEPDOG,
|
||||
.poolOptions = {
|
||||
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
|
||||
@@ -538,6 +550,11 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
|
||||
_("element 'name' is mandatory for RBD pool"));
|
||||
return -1;
|
||||
}
|
||||
+ if (pool_type == VIR_STORAGE_POOL_VITASTOR && source->name == NULL) {
|
||||
+ virReportError(VIR_ERR_XML_ERROR, "%s",
|
||||
+ _("element 'name' is mandatory for Vitastor pool"));
|
||||
+ return -1;
|
||||
+ }
|
||||
|
||||
if (options->formatFromString) {
|
||||
g_autofree char *format = NULL;
|
||||
@@ -1127,6 +1144,7 @@ virStoragePoolDefFormatBuf(virBuffer *buf,
|
||||
/* RBD, Sheepdog, Gluster and Iscsi-direct devices are not local block devs nor
|
||||
* files, so they don't have a target */
|
||||
if (def->type != VIR_STORAGE_POOL_RBD &&
|
||||
+ def->type != VIR_STORAGE_POOL_VITASTOR &&
|
||||
def->type != VIR_STORAGE_POOL_SHEEPDOG &&
|
||||
def->type != VIR_STORAGE_POOL_GLUSTER &&
|
||||
def->type != VIR_STORAGE_POOL_ISCSI_DIRECT) {
|
||||
diff --git a/src/conf/storage_conf.h b/src/conf/storage_conf.h
|
||||
index fc67957cfe..720c07ef74 100644
|
||||
--- a/src/conf/storage_conf.h
|
||||
+++ b/src/conf/storage_conf.h
|
||||
@@ -103,6 +103,7 @@ typedef enum {
|
||||
VIR_STORAGE_POOL_GLUSTER, /* Gluster device */
|
||||
VIR_STORAGE_POOL_ZFS, /* ZFS */
|
||||
VIR_STORAGE_POOL_VSTORAGE, /* Virtuozzo Storage */
|
||||
+ VIR_STORAGE_POOL_VITASTOR, /* Vitastor */
|
||||
|
||||
VIR_STORAGE_POOL_LAST,
|
||||
} virStoragePoolType;
|
||||
@@ -454,6 +455,7 @@ VIR_ENUM_DECL(virStoragePartedFs);
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_SCSI | \
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_MPATH | \
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_RBD | \
|
||||
+ VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR | \
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG | \
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER | \
|
||||
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS | \
|
||||
diff --git a/src/conf/storage_source_conf.c b/src/conf/storage_source_conf.c
|
||||
index d7b9bdfecb..38aefd0dd4 100644
|
||||
--- a/src/conf/storage_source_conf.c
|
||||
+++ b/src/conf/storage_source_conf.c
|
||||
@@ -90,6 +90,7 @@ VIR_ENUM_IMPL(virStorageNetProtocol,
|
||||
"ssh",
|
||||
"vxhs",
|
||||
"nfs",
|
||||
+ "vitastor",
|
||||
);
|
||||
|
||||
|
||||
@@ -1317,6 +1318,7 @@ virStorageSourceNetworkDefaultPort(virStorageNetProtocol protocol)
|
||||
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
||||
return 24007;
|
||||
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
||||
/* we don't provide a default for RBD */
|
||||
return 0;
|
||||
diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h
|
||||
index 22c35d420d..f1e32ea83d 100644
|
||||
--- a/src/conf/storage_source_conf.h
|
||||
+++ b/src/conf/storage_source_conf.h
|
||||
@@ -131,6 +131,7 @@ typedef enum {
|
||||
VIR_STORAGE_NET_PROTOCOL_SSH,
|
||||
VIR_STORAGE_NET_PROTOCOL_VXHS,
|
||||
VIR_STORAGE_NET_PROTOCOL_NFS,
|
||||
+ VIR_STORAGE_NET_PROTOCOL_VITASTOR,
|
||||
|
||||
VIR_STORAGE_NET_PROTOCOL_LAST
|
||||
} virStorageNetProtocol;
|
||||
diff --git a/src/conf/virstorageobj.c b/src/conf/virstorageobj.c
|
||||
index 59fa5da372..4739167f5f 100644
|
||||
--- a/src/conf/virstorageobj.c
|
||||
+++ b/src/conf/virstorageobj.c
|
||||
@@ -1438,6 +1438,7 @@ virStoragePoolObjSourceFindDuplicateCb(const void *payload,
|
||||
return 1;
|
||||
break;
|
||||
|
||||
+ case VIR_STORAGE_POOL_VITASTOR:
|
||||
case VIR_STORAGE_POOL_ISCSI_DIRECT:
|
||||
case VIR_STORAGE_POOL_RBD:
|
||||
case VIR_STORAGE_POOL_LAST:
|
||||
@@ -1921,6 +1922,8 @@ virStoragePoolObjMatch(virStoragePoolObj *obj,
|
||||
(obj->def->type == VIR_STORAGE_POOL_MPATH)) ||
|
||||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_RBD) &&
|
||||
(obj->def->type == VIR_STORAGE_POOL_RBD)) ||
|
||||
+ (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR) &&
|
||||
+ (obj->def->type == VIR_STORAGE_POOL_VITASTOR)) ||
|
||||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG) &&
|
||||
(obj->def->type == VIR_STORAGE_POOL_SHEEPDOG)) ||
|
||||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER) &&
|
||||
diff --git a/src/libvirt-storage.c b/src/libvirt-storage.c
|
||||
index db7660aac4..561df34709 100644
|
||||
--- a/src/libvirt-storage.c
|
||||
+++ b/src/libvirt-storage.c
|
||||
@@ -94,6 +94,7 @@ virStoragePoolGetConnect(virStoragePoolPtr pool)
|
||||
* VIR_CONNECT_LIST_STORAGE_POOLS_SCSI
|
||||
* VIR_CONNECT_LIST_STORAGE_POOLS_MPATH
|
||||
* VIR_CONNECT_LIST_STORAGE_POOLS_RBD
|
||||
+ * VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR
|
||||
* VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG
|
||||
* VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER
|
||||
* VIR_CONNECT_LIST_STORAGE_POOLS_ZFS
|
||||
diff --git a/src/libxl/libxl_conf.c b/src/libxl/libxl_conf.c
|
||||
index 2b988157fa..9d0eb47b25 100644
|
||||
--- a/src/libxl/libxl_conf.c
|
||||
+++ b/src/libxl/libxl_conf.c
|
||||
@@ -1069,6 +1069,7 @@ libxlMakeNetworkDiskSrcStr(virStorageSource *src,
|
||||
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
||||
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
||||
virReportError(VIR_ERR_NO_SUPPORT,
|
||||
diff --git a/src/libxl/xen_xl.c b/src/libxl/xen_xl.c
|
||||
index e72e7d7f44..8482c21805 100644
|
||||
--- a/src/libxl/xen_xl.c
|
||||
+++ b/src/libxl/xen_xl.c
|
||||
@@ -1461,6 +1461,7 @@ xenFormatXLDiskSrcNet(virStorageSource *src)
|
||||
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
||||
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
||||
virReportError(VIR_ERR_NO_SUPPORT,
|
||||
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
|
||||
index 9b43279797..459d8e8a65 100644
|
||||
--- a/src/qemu/qemu_block.c
|
||||
+++ b/src/qemu/qemu_block.c
|
||||
@@ -743,6 +743,38 @@ qemuBlockStorageSourceGetRBDProps(virStorageSource *src,
|
||||
}
|
||||
|
||||
|
||||
+static virJSONValue *
|
||||
+qemuBlockStorageSourceGetVitastorProps(virStorageSource *src)
|
||||
+{
|
||||
+ virJSONValue *ret = NULL;
|
||||
+ virStorageNetHostDef *host;
|
||||
+ size_t i;
|
||||
+ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
||||
+ g_autofree char *etcd = NULL;
|
||||
+
|
||||
+ for (i = 0; i < src->nhosts; i++) {
|
||||
+ host = src->hosts + i;
|
||||
+ if ((virStorageNetHostTransport)host->transport != VIR_STORAGE_NET_HOST_TRANS_TCP) {
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ virBufferAsprintf(&buf, i > 0 ? ",%s:%u" : "%s:%u", host->name, host->port);
|
||||
+ }
|
||||
+ if (src->nhosts > 0) {
|
||||
+ etcd = virBufferContentAndReset(&buf);
|
||||
+ }
|
||||
+
|
||||
+ if (virJSONValueObjectAdd(&ret,
|
||||
+ "S:etcd-host", etcd,
|
||||
+ "S:etcd-prefix", src->query,
|
||||
+ "S:config-path", src->configFile,
|
||||
+ "s:image", src->path,
|
||||
+ NULL) < 0)
|
||||
+ return NULL;
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+
|
||||
static virJSONValue *
|
||||
qemuBlockStorageSourceGetSshProps(virStorageSource *src)
|
||||
{
|
||||
@@ -1094,6 +1126,12 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
|
||||
return NULL;
|
||||
break;
|
||||
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
+ driver = "vitastor";
|
||||
+ if (!(fileprops = qemuBlockStorageSourceGetVitastorProps(src)))
|
||||
+ return NULL;
|
||||
+ break;
|
||||
+
|
||||
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
||||
driver = "ssh";
|
||||
if (!(fileprops = qemuBlockStorageSourceGetSshProps(src)))
|
||||
@@ -1997,6 +2035,7 @@ qemuBlockGetBackingStoreString(virStorageSource *src,
|
||||
|
||||
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
||||
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
||||
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
||||
@@ -2377,6 +2416,12 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src,
|
||||
return -1;
|
||||
break;
|
||||
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
+ driver = "vitastor";
|
||||
+ if (!(location = qemuBlockStorageSourceGetVitastorProps(src)))
|
||||
+ return -1;
|
||||
+ break;
|
||||
+
|
||||
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
||||
if (srcPriv->nbdkitProcess) {
|
||||
/* disk creation not yet supported with nbdkit, and even if it
|
||||
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
|
||||
index ac56fc7cb4..9e407b4aab 100644
|
||||
--- a/src/qemu/qemu_domain.c
|
||||
+++ b/src/qemu/qemu_domain.c
|
||||
@@ -4677,7 +4677,8 @@ qemuDomainValidateStorageSource(virStorageSource *src,
|
||||
if (src->query &&
|
||||
(actualType != VIR_STORAGE_TYPE_NETWORK ||
|
||||
(src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS &&
|
||||
- src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP))) {
|
||||
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP &&
|
||||
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_VITASTOR))) {
|
||||
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
||||
_("query is supported only with HTTP(S) protocols"));
|
||||
return -1;
|
||||
@@ -9103,6 +9104,7 @@ qemuDomainPrepareStorageSourceTLS(virStorageSource *src,
|
||||
break;
|
||||
|
||||
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
||||
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
||||
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
||||
diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
|
||||
index e738afffc3..37d64f469b 100644
|
||||
--- a/src/qemu/qemu_snapshot.c
|
||||
+++ b/src/qemu/qemu_snapshot.c
|
||||
@@ -665,6 +665,7 @@ qemuSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDef *snapdisk,
|
||||
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
||||
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
||||
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
||||
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
||||
@@ -893,6 +894,7 @@ qemuSnapshotPrepareDiskInternal(virDomainDiskDef *disk,
|
||||
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
||||
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
||||
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
||||
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
||||
diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c
|
||||
index e19e032427..59f91f4710 100644
|
||||
--- a/src/storage/storage_driver.c
|
||||
+++ b/src/storage/storage_driver.c
|
||||
@@ -1626,6 +1626,7 @@ storageVolLookupByPathCallback(virStoragePoolObj *obj,
|
||||
|
||||
case VIR_STORAGE_POOL_GLUSTER:
|
||||
case VIR_STORAGE_POOL_RBD:
|
||||
+ case VIR_STORAGE_POOL_VITASTOR:
|
||||
case VIR_STORAGE_POOL_SHEEPDOG:
|
||||
case VIR_STORAGE_POOL_ZFS:
|
||||
case VIR_STORAGE_POOL_LAST:
|
||||
diff --git a/src/storage_file/storage_source_backingstore.c b/src/storage_file/storage_source_backingstore.c
|
||||
index 821378883c..2211f6891b 100644
|
||||
--- a/src/storage_file/storage_source_backingstore.c
|
||||
+++ b/src/storage_file/storage_source_backingstore.c
|
||||
@@ -264,6 +264,75 @@ virStorageSourceParseRBDColonString(const char *rbdstr,
|
||||
}
|
||||
|
||||
|
||||
+static int
|
||||
+virStorageSourceParseVitastorColonString(const char *colonstr,
|
||||
+ virStorageSource *src)
|
||||
+{
|
||||
+ char *p, *e, *next;
|
||||
+ g_autofree char *options = NULL;
|
||||
+
|
||||
+ /* optionally skip the "vitastor:" prefix if provided */
|
||||
+ if (STRPREFIX(colonstr, "vitastor:"))
|
||||
+ colonstr += strlen("vitastor:");
|
||||
+
|
||||
+ options = g_strdup(colonstr);
|
||||
+
|
||||
+ p = options;
|
||||
+ while (*p) {
|
||||
+ /* find : delimiter or end of string */
|
||||
+ for (e = p; *e && *e != ':'; ++e) {
|
||||
+ if (*e == '\\') {
|
||||
+ e++;
|
||||
+ if (*e == '\0')
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ if (*e == '\0') {
|
||||
+ next = e; /* last kv pair */
|
||||
+ } else {
|
||||
+ next = e + 1;
|
||||
+ *e = '\0';
|
||||
+ }
|
||||
+
|
||||
+ if (STRPREFIX(p, "image=")) {
|
||||
+ src->path = g_strdup(p + strlen("image="));
|
||||
+ } else if (STRPREFIX(p, "etcd-prefix=")) {
|
||||
+ src->query = g_strdup(p + strlen("etcd-prefix="));
|
||||
+ } else if (STRPREFIX(p, "config-path=")) {
|
||||
+ src->configFile = g_strdup(p + strlen("config-path="));
|
||||
+ } else if (STRPREFIX(p, "etcd-host=")) {
|
||||
+ char *h, *sep;
|
||||
+
|
||||
+ h = p + strlen("etcd-host=");
|
||||
+ while (h < e) {
|
||||
+ for (sep = h; sep < e; ++sep) {
|
||||
+ if (*sep == '\\' && (sep[1] == ',' ||
|
||||
+ sep[1] == ';' ||
|
||||
+ sep[1] == ' ')) {
|
||||
+ *sep = '\0';
|
||||
+ sep += 2;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (virStorageSourceRBDAddHost(src, h) < 0)
|
||||
+ return -1;
|
||||
+
|
||||
+ h = sep;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ p = next;
|
||||
+ }
|
||||
+
|
||||
+ if (!src->path) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+
|
||||
static int
|
||||
virStorageSourceParseNBDColonString(const char *nbdstr,
|
||||
virStorageSource *src)
|
||||
@@ -379,6 +448,11 @@ virStorageSourceParseBackingColon(virStorageSource *src,
|
||||
return -1;
|
||||
break;
|
||||
|
||||
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
|
||||
+ if (virStorageSourceParseVitastorColonString(path, src) < 0)
|
||||
+ return -1;
|
||||
+ break;
|
||||
+
|
||||
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
||||
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
||||
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
||||
@@ -953,6 +1027,54 @@ virStorageSourceParseBackingJSONRBD(virStorageSource *src,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int
|
||||
+virStorageSourceParseBackingJSONVitastor(virStorageSource *src,
|
||||
+ virJSONValue *json,
|
||||
+ const char *jsonstr G_GNUC_UNUSED,
|
||||
+ int opaque G_GNUC_UNUSED)
|
||||
+{
|
||||
+ const char *filename;
|
||||
+ const char *image = virJSONValueObjectGetString(json, "image");
|
||||
+ const char *conf = virJSONValueObjectGetString(json, "config-path");
|
||||
+ const char *etcd_prefix = virJSONValueObjectGetString(json, "etcd-prefix");
|
||||
+ virJSONValue *servers = virJSONValueObjectGetArray(json, "server");
|
||||
+ size_t nservers;
|
||||
+ size_t i;
|
||||
+
|
||||
+ src->type = VIR_STORAGE_TYPE_NETWORK;
|
||||
+ src->protocol = VIR_STORAGE_NET_PROTOCOL_VITASTOR;
|
||||
+
|
||||
+ /* legacy syntax passed via 'filename' option */
|
||||
+ if ((filename = virJSONValueObjectGetString(json, "filename")))
|
||||
+ return virStorageSourceParseVitastorColonString(filename, src);
|
||||
+
|
||||
+ if (!image) {
|
||||
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
|
||||
+ _("missing image name in Vitastor backing volume "
|
||||
+ "JSON specification"));
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ src->path = g_strdup(image);
|
||||
+ src->configFile = g_strdup(conf);
|
||||
+ src->query = g_strdup(etcd_prefix);
|
||||
+
|
||||
+ if (servers) {
|
||||
+ nservers = virJSONValueArraySize(servers);
|
||||
+
|
||||
+ src->hosts = g_new0(virStorageNetHostDef, nservers);
|
||||
+ src->nhosts = nservers;
|
||||
+
|
||||
+ for (i = 0; i < nservers; i++) {
|
||||
+ if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i,
|
||||
+ virJSONValueArrayGet(servers, i)) < 0)
|
||||
+ return -1;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int
|
||||
virStorageSourceParseBackingJSONRaw(virStorageSource *src,
|
||||
virJSONValue *json,
|
||||
@@ -1130,6 +1252,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = {
|
||||
{"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0},
|
||||
{"ssh", false, virStorageSourceParseBackingJSONSSH, 0},
|
||||
{"rbd", false, virStorageSourceParseBackingJSONRBD, 0},
|
||||
+ {"vitastor", false, virStorageSourceParseBackingJSONVitastor, 0},
|
||||
{"raw", true, virStorageSourceParseBackingJSONRaw, 0},
|
||||
{"nfs", false, virStorageSourceParseBackingJSONNFS, 0},
|
||||
{"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0},
|
||||
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
|
||||
index 1165689de7..bba846351c 100644
|
||||
--- a/src/test/test_driver.c
|
||||
+++ b/src/test/test_driver.c
|
||||
@@ -7345,6 +7345,7 @@ testStorageVolumeTypeForPool(int pooltype)
|
||||
case VIR_STORAGE_POOL_ISCSI_DIRECT:
|
||||
case VIR_STORAGE_POOL_GLUSTER:
|
||||
case VIR_STORAGE_POOL_RBD:
|
||||
+ case VIR_STORAGE_POOL_VITASTOR:
|
||||
return VIR_STORAGE_VOL_NETWORK;
|
||||
case VIR_STORAGE_POOL_LOGICAL:
|
||||
case VIR_STORAGE_POOL_DISK:
|
||||
diff --git a/tests/storagepoolcapsschemadata/poolcaps-fs.xml b/tests/storagepoolcapsschemadata/poolcaps-fs.xml
|
||||
index eee75af746..8bd0a57bdd 100644
|
||||
--- a/tests/storagepoolcapsschemadata/poolcaps-fs.xml
|
||||
+++ b/tests/storagepoolcapsschemadata/poolcaps-fs.xml
|
||||
@@ -204,4 +204,11 @@
|
||||
</enum>
|
||||
</volOptions>
|
||||
</pool>
|
||||
+ <pool type='vitastor' supported='no'>
|
||||
+ <volOptions>
|
||||
+ <defaultFormat type='raw'/>
|
||||
+ <enum name='targetFormatType'>
|
||||
+ </enum>
|
||||
+ </volOptions>
|
||||
+ </pool>
|
||||
</storagepoolCapabilities>
|
||||
diff --git a/tests/storagepoolcapsschemadata/poolcaps-full.xml b/tests/storagepoolcapsschemadata/poolcaps-full.xml
|
||||
index 805950a937..852df0de16 100644
|
||||
--- a/tests/storagepoolcapsschemadata/poolcaps-full.xml
|
||||
+++ b/tests/storagepoolcapsschemadata/poolcaps-full.xml
|
||||
@@ -204,4 +204,11 @@
|
||||
</enum>
|
||||
</volOptions>
|
||||
</pool>
|
||||
+ <pool type='vitastor' supported='yes'>
|
||||
+ <volOptions>
|
||||
+ <defaultFormat type='raw'/>
|
||||
+ <enum name='targetFormatType'>
|
||||
+ </enum>
|
||||
+ </volOptions>
|
||||
+ </pool>
|
||||
</storagepoolCapabilities>
|
||||
diff --git a/tests/storagepoolxml2argvtest.c b/tests/storagepoolxml2argvtest.c
|
||||
index d5c2531ab8..b19308ac38 100644
|
||||
--- a/tests/storagepoolxml2argvtest.c
|
||||
+++ b/tests/storagepoolxml2argvtest.c
|
||||
@@ -57,6 +57,7 @@ testCompareXMLToArgvFiles(bool shouldFail,
|
||||
case VIR_STORAGE_POOL_GLUSTER:
|
||||
case VIR_STORAGE_POOL_ZFS:
|
||||
case VIR_STORAGE_POOL_VSTORAGE:
|
||||
+ case VIR_STORAGE_POOL_VITASTOR:
|
||||
case VIR_STORAGE_POOL_LAST:
|
||||
default:
|
||||
VIR_TEST_DEBUG("pool type '%s' has no xml2argv test", defTypeStr);
|
||||
diff --git a/tools/virsh-pool.c b/tools/virsh-pool.c
|
||||
index 2010ef1356..072e2ff9e8 100644
|
||||
--- a/tools/virsh-pool.c
|
||||
+++ b/tools/virsh-pool.c
|
||||
@@ -1187,6 +1187,9 @@ cmdPoolList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
|
||||
case VIR_STORAGE_POOL_VSTORAGE:
|
||||
flags |= VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE;
|
||||
break;
|
||||
+ case VIR_STORAGE_POOL_VITASTOR:
|
||||
+ flags |= VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR;
|
||||
+ break;
|
||||
case VIR_STORAGE_POOL_LAST:
|
||||
break;
|
||||
}
|
||||
@@ -1,29 +1,172 @@
|
||||
diff --git a/src/client/qemu_driver.c b/src/client/qemu_driver.c
|
||||
index d8356dab..5f4cd50d 100644
|
||||
--- a/src/client/qemu_driver.c
|
||||
+++ b/src/client/qemu_driver.c
|
||||
@@ -974,14 +974,21 @@ static void vitastor_co_read_bitmap_cb(void *opaque, long retval, uint8_t *bitma
|
||||
#endif
|
||||
}
|
||||
diff --git a/block/meson.build b/block/meson.build
|
||||
index 34b1b2a306..24ca0f1e52 100644
|
||||
--- a/block/meson.build
|
||||
+++ b/block/meson.build
|
||||
@@ -114,6 +114,7 @@ foreach m : [
|
||||
[libnfs, 'nfs', files('nfs.c')],
|
||||
[libssh, 'ssh', files('ssh.c')],
|
||||
[rbd, 'rbd', files('rbd.c')],
|
||||
+ [vitastor, 'vitastor', files('vitastor.c')],
|
||||
]
|
||||
if m[0].found()
|
||||
module_ss = ss.source_set()
|
||||
diff --git a/meson.build b/meson.build
|
||||
index 50c774a195..e5c7a3a4b1 100644
|
||||
--- a/meson.build
|
||||
+++ b/meson.build
|
||||
@@ -1652,6 +1652,26 @@ if not get_option('rbd').auto() or have_block
|
||||
endif
|
||||
endif
|
||||
|
||||
-static int coroutine_fn vitastor_co_block_status(
|
||||
- BlockDriverState *bs, bool want_zero, int64_t offset, int64_t bytes,
|
||||
- int64_t *pnum, int64_t *map, BlockDriverState **file)
|
||||
+static int coroutine_fn vitastor_co_block_status(BlockDriverState *bs,
|
||||
+#if QEMU_VERSION_MAJOR > 10 || QEMU_VERSION_MAJOR == 10 && QEMU_VERSION_MINOR >= 1
|
||||
+ unsigned int mode,
|
||||
+#else
|
||||
+ bool want_zero,
|
||||
+#endif
|
||||
+ int64_t offset, int64_t bytes, int64_t *pnum, int64_t *map, BlockDriverState **file)
|
||||
{
|
||||
// Allocated => return BDRV_BLOCK_DATA|BDRV_BLOCK_OFFSET_VALID
|
||||
// Not allocated => return 0
|
||||
// Error => return -errno
|
||||
// Set pnum to length of the extent, `*map` = `offset`, `*file` = `bs`
|
||||
+#if QEMU_VERSION_MAJOR > 10 || QEMU_VERSION_MAJOR == 10 && QEMU_VERSION_MINOR >= 1
|
||||
+ int want_zero = (mode == BDRV_WANT_PRECISE);
|
||||
+#endif
|
||||
VitastorRPC task;
|
||||
VitastorClient *client = bs->opaque;
|
||||
uint64_t inode = client->watch ? vitastor_c_inode_get_num(client->watch) : client->inode;
|
||||
+vitastor = not_found
|
||||
+if not get_option('vitastor').auto() or have_block
|
||||
+ libvitastor_client = cc.find_library('vitastor_client', has_headers: ['vitastor_c.h'],
|
||||
+ required: get_option('vitastor'))
|
||||
+ if libvitastor_client.found()
|
||||
+ if cc.links('''
|
||||
+ #include <vitastor_c.h>
|
||||
+ int main(void) {
|
||||
+ vitastor_c_create_qemu(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
+ return 0;
|
||||
+ }''', dependencies: libvitastor_client)
|
||||
+ vitastor = declare_dependency(dependencies: libvitastor_client)
|
||||
+ elif get_option('vitastor').enabled()
|
||||
+ error('could not link libvitastor_client')
|
||||
+ else
|
||||
+ warning('could not link libvitastor_client, disabling')
|
||||
+ endif
|
||||
+ endif
|
||||
+endif
|
||||
+
|
||||
glusterfs = not_found
|
||||
glusterfs_ftruncate_has_stat = false
|
||||
glusterfs_iocb_has_stat = false
|
||||
@@ -2547,6 +2567,7 @@ endif
|
||||
config_host_data.set('CONFIG_OPENGL', opengl.found())
|
||||
config_host_data.set('CONFIG_PLUGIN', get_option('plugins'))
|
||||
config_host_data.set('CONFIG_RBD', rbd.found())
|
||||
+config_host_data.set('CONFIG_VITASTOR', vitastor.found())
|
||||
config_host_data.set('CONFIG_RDMA', rdma.found())
|
||||
config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
|
||||
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
|
||||
@@ -4972,6 +4993,7 @@ summary_info += {'fdt support': fdt_opt == 'internal' ? 'internal' : fdt}
|
||||
summary_info += {'libcap-ng support': libcap_ng}
|
||||
summary_info += {'bpf support': libbpf}
|
||||
summary_info += {'rbd support': rbd}
|
||||
+summary_info += {'vitastor support': vitastor}
|
||||
summary_info += {'smartcard support': cacard}
|
||||
summary_info += {'U2F support': u2f}
|
||||
summary_info += {'libusb': libusb}
|
||||
diff --git a/meson_options.txt b/meson_options.txt
|
||||
index fff1521e58..f0844c0e00 100644
|
||||
--- a/meson_options.txt
|
||||
+++ b/meson_options.txt
|
||||
@@ -202,6 +202,8 @@ option('pvg', type: 'feature', value: 'auto',
|
||||
description: 'macOS paravirtualized graphics support')
|
||||
option('rbd', type : 'feature', value : 'auto',
|
||||
description: 'Ceph block device driver')
|
||||
+option('vitastor', type : 'feature', value : 'auto',
|
||||
+ description: 'Vitastor block device driver')
|
||||
option('opengl', type : 'feature', value : 'auto',
|
||||
description: 'OpenGL support')
|
||||
option('rdma', type : 'feature', value : 'auto',
|
||||
diff --git a/qapi/block-core.json b/qapi/block-core.json
|
||||
index dc6eb4ae23..d043f4340e 100644
|
||||
--- a/qapi/block-core.json
|
||||
+++ b/qapi/block-core.json
|
||||
@@ -3280,7 +3280,7 @@
|
||||
'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum',
|
||||
'raw', 'rbd',
|
||||
{ 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
|
||||
- 'ssh', 'throttle', 'vdi', 'vhdx',
|
||||
+ 'ssh', 'throttle', 'vdi', 'vhdx', 'vitastor',
|
||||
{ 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' },
|
||||
{ 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' },
|
||||
{ 'name': 'virtio-blk-vhost-vdpa', 'if': 'CONFIG_BLKIO' },
|
||||
@@ -4363,6 +4363,28 @@
|
||||
'*key-secret': 'str',
|
||||
'*server': ['InetSocketAddressBase'] } }
|
||||
|
||||
+##
|
||||
+# @BlockdevOptionsVitastor:
|
||||
+#
|
||||
+# Driver specific block device options for vitastor
|
||||
+#
|
||||
+# @image: Image name
|
||||
+# @inode: Inode number
|
||||
+# @pool: Pool ID
|
||||
+# @size: Desired image size in bytes
|
||||
+# @config-path: Path to Vitastor configuration
|
||||
+# @etcd-host: etcd connection address(es)
|
||||
+# @etcd-prefix: etcd key/value prefix
|
||||
+##
|
||||
+{ 'struct': 'BlockdevOptionsVitastor',
|
||||
+ 'data': { '*inode': 'uint64',
|
||||
+ '*pool': 'uint64',
|
||||
+ '*size': 'uint64',
|
||||
+ '*image': 'str',
|
||||
+ '*config-path': 'str',
|
||||
+ '*etcd-host': 'str',
|
||||
+ '*etcd-prefix': 'str' } }
|
||||
+
|
||||
##
|
||||
# @ReplicationMode:
|
||||
#
|
||||
@@ -4831,6 +4853,7 @@
|
||||
'throttle': 'BlockdevOptionsThrottle',
|
||||
'vdi': 'BlockdevOptionsGenericFormat',
|
||||
'vhdx': 'BlockdevOptionsGenericFormat',
|
||||
+ 'vitastor': 'BlockdevOptionsVitastor',
|
||||
'virtio-blk-vfio-pci':
|
||||
{ 'type': 'BlockdevOptionsVirtioBlkVfioPci',
|
||||
'if': 'CONFIG_BLKIO' },
|
||||
@@ -5304,6 +5327,20 @@
|
||||
'*cluster-size' : 'size',
|
||||
'*encrypt' : 'RbdEncryptionCreateOptions' } }
|
||||
|
||||
+##
|
||||
+# @BlockdevCreateOptionsVitastor:
|
||||
+#
|
||||
+# Driver specific image creation options for Vitastor.
|
||||
+#
|
||||
+# @location: Where to store the new image file. This location cannot
|
||||
+# point to a snapshot.
|
||||
+#
|
||||
+# @size: Size of the virtual disk in bytes
|
||||
+##
|
||||
+{ 'struct': 'BlockdevCreateOptionsVitastor',
|
||||
+ 'data': { 'location': 'BlockdevOptionsVitastor',
|
||||
+ 'size': 'size' } }
|
||||
+
|
||||
##
|
||||
# @BlockdevVmdkSubformat:
|
||||
#
|
||||
@@ -5526,6 +5563,7 @@
|
||||
'ssh': 'BlockdevCreateOptionsSsh',
|
||||
'vdi': 'BlockdevCreateOptionsVdi',
|
||||
'vhdx': 'BlockdevCreateOptionsVhdx',
|
||||
+ 'vitastor': 'BlockdevCreateOptionsVitastor',
|
||||
'vmdk': 'BlockdevCreateOptionsVmdk',
|
||||
'vpc': 'BlockdevCreateOptionsVpc'
|
||||
} }
|
||||
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
|
||||
index 0ebe6bc52a..2c37ad3892 100644
|
||||
--- a/scripts/meson-buildoptions.sh
|
||||
+++ b/scripts/meson-buildoptions.sh
|
||||
@@ -175,6 +175,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' qga-vss build QGA VSS support (broken with MinGW)'
|
||||
printf "%s\n" ' qpl Query Processing Library support'
|
||||
printf "%s\n" ' rbd Ceph block device driver'
|
||||
+ printf "%s\n" ' vitastor Vitastor block device driver'
|
||||
printf "%s\n" ' rdma Enable RDMA-based migration'
|
||||
printf "%s\n" ' replication replication support'
|
||||
printf "%s\n" ' rust Rust support'
|
||||
@@ -459,6 +460,8 @@ _meson_option_parse() {
|
||||
--disable-qpl) printf "%s" -Dqpl=disabled ;;
|
||||
--enable-rbd) printf "%s" -Drbd=enabled ;;
|
||||
--disable-rbd) printf "%s" -Drbd=disabled ;;
|
||||
+ --enable-vitastor) printf "%s" -Dvitastor=enabled ;;
|
||||
+ --disable-vitastor) printf "%s" -Dvitastor=disabled ;;
|
||||
--enable-rdma) printf "%s" -Drdma=enabled ;;
|
||||
--disable-rdma) printf "%s" -Drdma=disabled ;;
|
||||
--enable-relocatable) printf "%s" -Drelocatable=true ;;
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
diff --git a/block/meson.build b/block/meson.build
|
||||
index 34b1b2a306..24ca0f1e52 100644
|
||||
--- a/block/meson.build
|
||||
+++ b/block/meson.build
|
||||
@@ -114,6 +114,7 @@ foreach m : [
|
||||
[libnfs, 'nfs', files('nfs.c')],
|
||||
[libssh, 'ssh', files('ssh.c')],
|
||||
[rbd, 'rbd', files('rbd.c')],
|
||||
+ [vitastor, 'vitastor', files('vitastor.c')],
|
||||
]
|
||||
if m[0].found()
|
||||
module_ss = ss.source_set()
|
||||
diff --git a/meson.build b/meson.build
|
||||
index d9293294d8..776a5becc6 100644
|
||||
--- a/meson.build
|
||||
+++ b/meson.build
|
||||
@@ -1665,6 +1665,26 @@ if not get_option('rbd').auto() or have_block
|
||||
endif
|
||||
endif
|
||||
|
||||
+vitastor = not_found
|
||||
+if not get_option('vitastor').auto() or have_block
|
||||
+ libvitastor_client = cc.find_library('vitastor_client', has_headers: ['vitastor_c.h'],
|
||||
+ required: get_option('vitastor'))
|
||||
+ if libvitastor_client.found()
|
||||
+ if cc.links('''
|
||||
+ #include <vitastor_c.h>
|
||||
+ int main(void) {
|
||||
+ vitastor_c_create_qemu(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
+ return 0;
|
||||
+ }''', dependencies: libvitastor_client)
|
||||
+ vitastor = declare_dependency(dependencies: libvitastor_client)
|
||||
+ elif get_option('vitastor').enabled()
|
||||
+ error('could not link libvitastor_client')
|
||||
+ else
|
||||
+ warning('could not link libvitastor_client, disabling')
|
||||
+ endif
|
||||
+ endif
|
||||
+endif
|
||||
+
|
||||
glusterfs = not_found
|
||||
glusterfs_ftruncate_has_stat = false
|
||||
glusterfs_iocb_has_stat = false
|
||||
@@ -2509,6 +2529,7 @@ endif
|
||||
config_host_data.set('CONFIG_OPENGL', opengl.found())
|
||||
config_host_data.set('CONFIG_PLUGIN', get_option('plugins'))
|
||||
config_host_data.set('CONFIG_RBD', rbd.found())
|
||||
+config_host_data.set('CONFIG_VITASTOR', vitastor.found())
|
||||
config_host_data.set('CONFIG_RDMA', rdma.found())
|
||||
config_host_data.set('CONFIG_RELOCATABLE', get_option('relocatable'))
|
||||
config_host_data.set('CONFIG_SAFESTACK', get_option('safe_stack'))
|
||||
@@ -4948,6 +4969,7 @@ summary_info += {'fdt support': fdt_opt == 'internal' ? 'internal' : fdt}
|
||||
summary_info += {'libcap-ng support': libcap_ng}
|
||||
summary_info += {'bpf support': libbpf}
|
||||
summary_info += {'rbd support': rbd}
|
||||
+summary_info += {'vitastor support': vitastor}
|
||||
summary_info += {'smartcard support': cacard}
|
||||
summary_info += {'U2F support': u2f}
|
||||
summary_info += {'libusb': libusb}
|
||||
diff --git a/meson_options.txt b/meson_options.txt
|
||||
index 2836156257..148086cc6f 100644
|
||||
--- a/meson_options.txt
|
||||
+++ b/meson_options.txt
|
||||
@@ -206,6 +206,8 @@ option('pvg', type: 'feature', value: 'auto',
|
||||
description: 'macOS paravirtualized graphics support')
|
||||
option('rbd', type : 'feature', value : 'auto',
|
||||
description: 'Ceph block device driver')
|
||||
+option('vitastor', type : 'feature', value : 'auto',
|
||||
+ description: 'Vitastor block device driver')
|
||||
option('opengl', type : 'feature', value : 'auto',
|
||||
description: 'OpenGL support')
|
||||
option('rdma', type : 'feature', value : 'auto',
|
||||
diff --git a/qapi/block-core.json b/qapi/block-core.json
|
||||
index b82af74256..f25a6f5ce8 100644
|
||||
--- a/qapi/block-core.json
|
||||
+++ b/qapi/block-core.json
|
||||
@@ -3351,7 +3351,7 @@
|
||||
'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum',
|
||||
'raw', 'rbd',
|
||||
{ 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
|
||||
- 'ssh', 'throttle', 'vdi', 'vhdx',
|
||||
+ 'ssh', 'throttle', 'vdi', 'vhdx', 'vitastor',
|
||||
{ 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' },
|
||||
{ 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' },
|
||||
{ 'name': 'virtio-blk-vhost-vdpa', 'if': 'CONFIG_BLKIO' },
|
||||
@@ -4434,6 +4434,28 @@
|
||||
'*key-secret': 'str',
|
||||
'*server': ['InetSocketAddressBase'] } }
|
||||
|
||||
+##
|
||||
+# @BlockdevOptionsVitastor:
|
||||
+#
|
||||
+# Driver specific block device options for vitastor
|
||||
+#
|
||||
+# @image: Image name
|
||||
+# @inode: Inode number
|
||||
+# @pool: Pool ID
|
||||
+# @size: Desired image size in bytes
|
||||
+# @config-path: Path to Vitastor configuration
|
||||
+# @etcd-host: etcd connection address(es)
|
||||
+# @etcd-prefix: etcd key/value prefix
|
||||
+##
|
||||
+{ 'struct': 'BlockdevOptionsVitastor',
|
||||
+ 'data': { '*inode': 'uint64',
|
||||
+ '*pool': 'uint64',
|
||||
+ '*size': 'uint64',
|
||||
+ '*image': 'str',
|
||||
+ '*config-path': 'str',
|
||||
+ '*etcd-host': 'str',
|
||||
+ '*etcd-prefix': 'str' } }
|
||||
+
|
||||
##
|
||||
# @ReplicationMode:
|
||||
#
|
||||
@@ -4902,6 +4924,7 @@
|
||||
'throttle': 'BlockdevOptionsThrottle',
|
||||
'vdi': 'BlockdevOptionsGenericFormat',
|
||||
'vhdx': 'BlockdevOptionsGenericFormat',
|
||||
+ 'vitastor': 'BlockdevOptionsVitastor',
|
||||
'virtio-blk-vfio-pci':
|
||||
{ 'type': 'BlockdevOptionsVirtioBlkVfioPci',
|
||||
'if': 'CONFIG_BLKIO' },
|
||||
@@ -5376,6 +5399,20 @@
|
||||
'*cluster-size' : 'size',
|
||||
'*encrypt' : 'RbdEncryptionCreateOptions' } }
|
||||
|
||||
+##
|
||||
+# @BlockdevCreateOptionsVitastor:
|
||||
+#
|
||||
+# Driver specific image creation options for Vitastor.
|
||||
+#
|
||||
+# @location: Where to store the new image file. This location cannot
|
||||
+# point to a snapshot.
|
||||
+#
|
||||
+# @size: Size of the virtual disk in bytes
|
||||
+##
|
||||
+{ 'struct': 'BlockdevCreateOptionsVitastor',
|
||||
+ 'data': { 'location': 'BlockdevOptionsVitastor',
|
||||
+ 'size': 'size' } }
|
||||
+
|
||||
##
|
||||
# @BlockdevVmdkSubformat:
|
||||
#
|
||||
@@ -5598,6 +5635,7 @@
|
||||
'ssh': 'BlockdevCreateOptionsSsh',
|
||||
'vdi': 'BlockdevCreateOptionsVdi',
|
||||
'vhdx': 'BlockdevCreateOptionsVhdx',
|
||||
+ 'vitastor': 'BlockdevCreateOptionsVitastor',
|
||||
'vmdk': 'BlockdevCreateOptionsVmdk',
|
||||
'vpc': 'BlockdevCreateOptionsVpc'
|
||||
} }
|
||||
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
|
||||
index 3d0d132344..65ee8c855e 100644
|
||||
--- a/scripts/meson-buildoptions.sh
|
||||
+++ b/scripts/meson-buildoptions.sh
|
||||
@@ -177,6 +177,7 @@ meson_options_help() {
|
||||
printf "%s\n" ' qga-vss build QGA VSS support (broken with MinGW)'
|
||||
printf "%s\n" ' qpl Query Processing Library support'
|
||||
printf "%s\n" ' rbd Ceph block device driver'
|
||||
+ printf "%s\n" ' vitastor Vitastor block device driver'
|
||||
printf "%s\n" ' rdma Enable RDMA-based migration'
|
||||
printf "%s\n" ' replication replication support'
|
||||
printf "%s\n" ' rust Rust support'
|
||||
@@ -464,6 +465,8 @@ _meson_option_parse() {
|
||||
--disable-qpl) printf "%s" -Dqpl=disabled ;;
|
||||
--enable-rbd) printf "%s" -Drbd=enabled ;;
|
||||
--disable-rbd) printf "%s" -Drbd=disabled ;;
|
||||
+ --enable-vitastor) printf "%s" -Dvitastor=enabled ;;
|
||||
+ --disable-vitastor) printf "%s" -Dvitastor=disabled ;;
|
||||
--enable-rdma) printf "%s" -Drdma=enabled ;;
|
||||
--disable-rdma) printf "%s" -Drdma=disabled ;;
|
||||
--enable-relocatable) printf "%s" -Drelocatable=true ;;
|
||||
@@ -11,7 +11,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.6
|
||||
Version: 3.0.12
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.6.el10.tar.gz
|
||||
Source0: vitastor-3.0.12.el10.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
|
||||
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
|
||||
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
|
||||
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.6
|
||||
Version: 3.0.12
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.6.el7.tar.gz
|
||||
Source0: vitastor-3.0.12.el7.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: devtoolset-9-gcc-c++
|
||||
@@ -17,6 +17,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake3
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
|
||||
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
|
||||
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.6
|
||||
Version: 3.0.12
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.6.el8.tar.gz
|
||||
Source0: vitastor-3.0.12.el8.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-toolset-9-gcc-c++
|
||||
@@ -16,6 +16,7 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.6
|
||||
Version: 3.0.12
|
||||
Release: 1%{?dist}
|
||||
Summary: Vitastor, a fast software-defined clustered block storage
|
||||
|
||||
License: Vitastor Network Public License 1.1
|
||||
URL: https://vitastor.io/
|
||||
Source0: vitastor-3.0.6.el9.tar.gz
|
||||
Source0: vitastor-3.0.12.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}
|
||||
|
||||
+12
-3
@@ -1,9 +1,8 @@
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CTest)
|
||||
include(CheckIncludeFile)
|
||||
|
||||
find_package(PkgConfig)
|
||||
@@ -21,7 +20,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
|
||||
endif()
|
||||
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
|
||||
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.6")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.12")
|
||||
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
|
||||
add_link_options(-fno-omit-frame-pointer)
|
||||
if (${WITH_ASAN})
|
||||
@@ -70,11 +69,21 @@ pkg_check_modules(ISAL libisal)
|
||||
if (ISAL_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL)
|
||||
endif (ISAL_LIBRARIES)
|
||||
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
|
||||
if (ISAL_CRYPTO_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL_CRYPTO)
|
||||
endif (ISAL_CRYPTO_LIBRARIES)
|
||||
pkg_check_modules(RDMACM librdmacm)
|
||||
if (RDMACM_LIBRARIES)
|
||||
add_definitions(-DWITH_RDMACM)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(-DWITH_OPENSSL)
|
||||
|
||||
pkg_check_modules(CARES REQUIRED libcares)
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
|
||||
if (${WITH_SYSTEM_LIBURING})
|
||||
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
|
||||
include_directories(${LIBURING_INCLUDE_DIRS})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
|
||||
@@ -228,4 +228,9 @@ public:
|
||||
virtual uint64_t get_journal_size() = 0;
|
||||
|
||||
virtual uint32_t get_bitmap_granularity() = 0;
|
||||
|
||||
virtual uint64_t get_live_entries() = 0;
|
||||
virtual uint64_t get_live_memory() = 0;
|
||||
virtual uint64_t get_garbage_entries() = 0;
|
||||
virtual uint64_t get_garbage_memory() = 0;
|
||||
};
|
||||
|
||||
@@ -83,17 +83,24 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
|
||||
}
|
||||
else if (config["data_csum_type"] == "xxh3_32")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
|
||||
}
|
||||
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
|
||||
}
|
||||
csum_block_size = parse_size(config["csum_block_size"]);
|
||||
discard_on_start = config.find("discard_on_start") != config.end() &&
|
||||
(config["discard_on_start"] == "true" || config["discard_on_start"] == "1" || config["discard_on_start"] == "yes");
|
||||
gc_on_start = config.find("gc_on_start") == config.end() ||
|
||||
(config["gc_on_start"] == "true" || config["gc_on_start"] == "1" || config["gc_on_start"] == "yes");
|
||||
skip_double_claim = (config["skip_double_claim"] == "true" || config["skip_double_claim"] == "1" || config["skip_double_claim"] == "yes");
|
||||
min_discard_size = parse_size(config["min_discard_size"]);
|
||||
if (!min_discard_size)
|
||||
min_discard_size = 1024*1024;
|
||||
|
||||
@@ -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
|
||||
@@ -57,6 +58,10 @@ struct blockstore_disk_t
|
||||
bool inmemory_journal = true;
|
||||
// Data discard granularity and minimum size (for the sake of performance)
|
||||
bool discard_on_start = false;
|
||||
// GC on start (new store)
|
||||
bool gc_on_start = true;
|
||||
// Skip double claim conflicts on start (new store, temporary until the bug is found)
|
||||
bool skip_double_claim = false;
|
||||
uint64_t min_discard_size = 1024*1024;
|
||||
uint64_t discard_granularity = 0;
|
||||
|
||||
|
||||
@@ -174,14 +174,18 @@ bool journal_flusher_co::loop()
|
||||
else if (wait_state == 19) goto resume_19;
|
||||
else if (wait_state == 20) goto resume_20;
|
||||
else if (wait_state == 21) goto resume_21;
|
||||
else if (wait_state == 22) goto resume_22;
|
||||
else if (wait_state == 23) goto resume_23;
|
||||
else if (wait_state == 24) goto resume_24;
|
||||
else if (wait_state == 25) goto resume_25;
|
||||
resume_0:
|
||||
wait_state = 0;
|
||||
wait_count = 0;
|
||||
cur_oid = {};
|
||||
res = bs->heap->get_next_compact(cur_oid);
|
||||
// Advance fsynced_lsn every <journal_trim_interval> intent writes
|
||||
if ((bs->intent_write_counter >= bs->journal_trim_interval) && co_id == 0)
|
||||
{
|
||||
// Advance fsynced_lsn every <journal_trim_interval> intent writes
|
||||
bs->intent_write_counter = 0;
|
||||
resume_17:
|
||||
resume_18:
|
||||
@@ -196,6 +200,7 @@ resume_21:
|
||||
if (res == ENOENT && flusher->force_start > 0 && co_id == 0 &&
|
||||
(!bs->dsk.disable_journal_fsync || !bs->dsk.disable_meta_fsync || !bs->dsk.disable_data_fsync))
|
||||
{
|
||||
// When under pressure, do an additional fsync to force entries to be marked compactable
|
||||
flusher->active_flushers++;
|
||||
resume_14:
|
||||
resume_15:
|
||||
@@ -259,11 +264,9 @@ resume_1:
|
||||
if (wr->type() == BS_HEAP_SMALL_WRITE ||
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity)
|
||||
{
|
||||
auto res = bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
bs->prepare_read(read_vec, cur_obj, wr, 0, bs->dsk.data_block_size,
|
||||
wr->type() == BS_HEAP_INTENT_WRITE && bs->dsk.csum_block_size > bs->dsk.bitmap_granularity && !bs->perfect_csum_update
|
||||
? COPY_BUF_SKIP_CSUM : 0);
|
||||
if (res > 0)
|
||||
copy_count++;
|
||||
}
|
||||
});
|
||||
if (!compact_info.compact_lsn)
|
||||
@@ -273,6 +276,25 @@ resume_1:
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
flusher->active_flushers++;
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
{
|
||||
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
|
||||
{
|
||||
copy_count++;
|
||||
}
|
||||
}
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
init_fsync_data();
|
||||
}
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
@@ -291,13 +313,6 @@ resume_1:
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
flusher->active_flushers++;
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
overwrite_start = overwrite_end = 0;
|
||||
if (read_vec.size() > 0)
|
||||
{
|
||||
@@ -336,6 +351,13 @@ resume_3:
|
||||
if (res == ENOENT || res == EDOM)
|
||||
{
|
||||
// Abort compaction
|
||||
abort_compact:
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
cur_sync->member_count--;
|
||||
if (cur_sync->member_count > 0)
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
@@ -349,10 +371,7 @@ resume_4:
|
||||
if (res == ENOENT)
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->flushing.erase(cur_oid);
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
flusher->active_flushers--;
|
||||
goto resume_0;
|
||||
goto abort_compact;
|
||||
}
|
||||
if (res == EAGAIN)
|
||||
{
|
||||
@@ -381,14 +400,14 @@ resume_9:
|
||||
for (i = 0; i < read_vec.size(); i++)
|
||||
{
|
||||
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED) ||
|
||||
(read_vec[i].copy_flags & COPY_BUF_PADDED)) // FIXME Shit, simplify these flags
|
||||
!(read_vec[i].copy_flags & COPY_BUF_COALESCED))
|
||||
{
|
||||
assert(read_vec[i].buf);
|
||||
await_sqe(10);
|
||||
data->iov = (struct iovec){ read_vec[i].buf + (read_vec[i].copy_flags & COPY_BUF_PADDED
|
||||
? read_vec[i].offset - read_vec[i].disk_offset : 0), (size_t)read_vec[i].len };
|
||||
data->callback = simple_callback_w;
|
||||
assert(clean_loc + read_vec[i].offset + data->iov.iov_len <= bs->dsk.block_count*bs->dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + read_vec[i].offset);
|
||||
wait_count++;
|
||||
}
|
||||
@@ -399,6 +418,17 @@ resume_11:
|
||||
wait_state = 11;
|
||||
return false;
|
||||
}
|
||||
if (copy_count > 0 && !bs->dsk.disable_data_fsync)
|
||||
{
|
||||
resume_22:
|
||||
resume_23:
|
||||
resume_24:
|
||||
resume_25:
|
||||
if (!fsync_data(22))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Lock is only needed to prevent freeing the big_write because we overwrite it...
|
||||
bs->heap->unlock_entry(cur_oid);
|
||||
// Mark the object compacted, but don't free and remove small_writes
|
||||
@@ -408,12 +438,14 @@ resume_11:
|
||||
if (!cur_obj)
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
if (!calc_block_checksums())
|
||||
{
|
||||
// Abort compaction
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -422,6 +454,7 @@ resume_11:
|
||||
if (res == EBUSY)
|
||||
{
|
||||
// Abort compaction, object is already overwritten by something else
|
||||
flusher->active_flushers--;
|
||||
flusher->flushing.erase(cur_oid);
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -586,7 +619,7 @@ int journal_flusher_co::check_and_punch_checksums()
|
||||
bs->heap->calc_block_checksums((uint32_t*)(new_csums+csum_off), vec.buf, punch_bmp, vec.offset, vec.offset+vec.len, true, NULL);
|
||||
}
|
||||
}
|
||||
// Modified, we should add_punch_holes and then write the block to disk
|
||||
// Modified, we should punch_holes and then write the block to disk
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
@@ -699,6 +732,67 @@ resume_1:
|
||||
return true;
|
||||
}
|
||||
|
||||
void journal_flusher_co::init_fsync_data()
|
||||
{
|
||||
cur_sync = flusher->data_syncs.begin();
|
||||
if (cur_sync == flusher->data_syncs.end() || cur_sync->ready_count > 0)
|
||||
{
|
||||
cur_sync = flusher->data_syncs.emplace(cur_sync);
|
||||
}
|
||||
cur_sync->member_count++;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::fsync_data(int wait_base)
|
||||
{
|
||||
if (wait_state == wait_base)
|
||||
goto resume_0;
|
||||
else if (wait_state == wait_base+1)
|
||||
goto resume_1;
|
||||
else if (wait_state == wait_base+2)
|
||||
goto resume_2;
|
||||
else if (wait_state == wait_base+3)
|
||||
goto resume_3;
|
||||
cur_sync->ready_count++;
|
||||
resume_0:
|
||||
if (cur_sync->ready_count < cur_sync->member_count)
|
||||
{
|
||||
wait_state = wait_base;
|
||||
return false;
|
||||
}
|
||||
if (!cur_sync->sent)
|
||||
{
|
||||
// Sync batch is ready. Do it.
|
||||
await_sqe(1);
|
||||
data->iov = { 0 };
|
||||
data->callback = simple_callback_w;
|
||||
io_uring_prep_fsync(sqe, bs->dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
cur_sync->sent = true;
|
||||
wait_count++;
|
||||
resume_2:
|
||||
if (wait_count > 0)
|
||||
{
|
||||
wait_state = wait_base+2;
|
||||
return false;
|
||||
}
|
||||
cur_sync->done = true;
|
||||
// Wake up other flushers
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
resume_3:
|
||||
if (!cur_sync->done)
|
||||
{
|
||||
wait_state = wait_base+3;
|
||||
return false;
|
||||
}
|
||||
cur_sync->done_count++;
|
||||
if (cur_sync->done_count >= cur_sync->member_count)
|
||||
{
|
||||
flusher->data_syncs.erase(cur_sync);
|
||||
cur_sync = flusher->data_syncs.end();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool journal_flusher_co::fsync_meta(int wait_base)
|
||||
{
|
||||
if (wait_state == wait_base) goto resume_0;
|
||||
|
||||
@@ -25,6 +25,15 @@ struct flusher_meta_write_t
|
||||
std::map<uint64_t, meta_sector_t>::iterator it;
|
||||
};
|
||||
|
||||
struct flusher_data_sync_t
|
||||
{
|
||||
int member_count = 0;
|
||||
int ready_count = 0;
|
||||
int done_count = 0;
|
||||
bool sent = false;
|
||||
bool done = false;
|
||||
};
|
||||
|
||||
class journal_flusher_t;
|
||||
|
||||
// Journal flusher coroutine
|
||||
@@ -58,6 +67,7 @@ class journal_flusher_co
|
||||
int i, res;
|
||||
bool read_to_fill_incomplete;
|
||||
int copy_count;
|
||||
std::list<flusher_data_sync_t>::iterator cur_sync;
|
||||
|
||||
friend class journal_flusher_t;
|
||||
|
||||
@@ -68,6 +78,8 @@ class journal_flusher_co
|
||||
bool calc_block_checksums();
|
||||
bool write_meta_block(int wait_base);
|
||||
bool read_buffered(int wait_base);
|
||||
void init_fsync_data();
|
||||
bool fsync_data(int wait_base);
|
||||
bool fsync_meta(int wait_base);
|
||||
bool fsync_buffer(int wait_base);
|
||||
bool trim_lsn(int wait_base);
|
||||
@@ -88,6 +100,7 @@ class journal_flusher_t
|
||||
|
||||
robin_hood::unordered_flat_set<object_id> flushing;
|
||||
int active_flushers = 0;
|
||||
std::list<flusher_data_sync_t> data_syncs;
|
||||
int wanting_meta_fsync = 0;
|
||||
bool fsyncing_meta = false;
|
||||
int syncing_buffer = 0;
|
||||
|
||||
+603
-257
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t entry_type;
|
||||
uint32_t crc32c;
|
||||
uint32_t checksum;
|
||||
uint64_t lsn;
|
||||
uint64_t inode;
|
||||
uint64_t stripe;
|
||||
@@ -57,11 +57,11 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
inline heap_small_write_t& small() { return *(heap_small_write_t*)this; }
|
||||
inline heap_big_write_t& big() { return *(heap_big_write_t*)this; }
|
||||
inline heap_big_intent_t& big_intent() { return *(heap_big_intent_t*)this; }
|
||||
bool is_garbage();
|
||||
bool is_garbage() const;
|
||||
void set_garbage();
|
||||
bool is_overwrite();
|
||||
bool is_compactable();
|
||||
bool is_before(heap_entry_t *other);
|
||||
bool is_overwrite() const;
|
||||
bool is_compactable() const;
|
||||
bool is_before(const heap_entry_t *other) const;
|
||||
uint32_t get_size(blockstore_heap_t *heap);
|
||||
uint8_t *get_ext_bitmap(blockstore_heap_t *heap);
|
||||
uint8_t *get_int_bitmap(blockstore_heap_t *heap);
|
||||
@@ -69,7 +69,8 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
uint32_t *get_checksum(blockstore_heap_t *heap);
|
||||
uint64_t big_location(blockstore_heap_t *heap);
|
||||
void set_big_location(blockstore_heap_t *heap, uint64_t location);
|
||||
uint32_t calc_crc32c();
|
||||
uint32_t calc_checksum(blockstore_heap_t *heap);
|
||||
uint32_t calc_checksum(blockstore_disk_t *dsk);
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_small_write_t
|
||||
@@ -80,7 +81,7 @@ struct __attribute__((__packed__)) heap_small_write_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
|
||||
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_big_write_t
|
||||
@@ -98,7 +99,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
|
||||
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_list_item_t
|
||||
@@ -117,10 +118,13 @@ struct heap_object_mvcc_t
|
||||
|
||||
struct heap_block_info_t
|
||||
{
|
||||
uint32_t used_space = 0;
|
||||
struct __attribute__((__packed__))
|
||||
{
|
||||
uint32_t used_space = 0;
|
||||
uint32_t garbage_space = 0;
|
||||
};
|
||||
uint64_t mod_lsn = 0, mod_lsn_to = 0; // only 1 block write of LSN sequence is allowed at a moment
|
||||
bool is_writing: 1;
|
||||
bool has_garbage: 1;
|
||||
bool is_writing = false;
|
||||
std::vector<heap_list_item_t*> entries;
|
||||
};
|
||||
|
||||
@@ -155,6 +159,16 @@ struct heap_li_equal
|
||||
}
|
||||
};
|
||||
|
||||
struct heap_recheck_state_t
|
||||
{
|
||||
heap_entry_t *obj = NULL;
|
||||
heap_entry_t *next_wr = NULL;
|
||||
size_t total_reads = 0;
|
||||
size_t sent_reads = 0;
|
||||
size_t checked_reads = 0;
|
||||
heap_entry_t *bad_wr = NULL;
|
||||
};
|
||||
|
||||
using i64hash_t = robin_hood::hash<uint64_t>;
|
||||
using heap_inode_map_t = robin_hood::unordered_flat_set<heap_list_item_t*, heap_li_hash, heap_li_equal, 88>;
|
||||
using heap_block_index_t = robin_hood::unordered_flat_map<uint64_t,
|
||||
@@ -184,6 +198,11 @@ class blockstore_heap_t
|
||||
uint64_t buffer_area_used_space = 0;
|
||||
uint64_t data_used_space = 0;
|
||||
|
||||
uint64_t live_entries = 0;
|
||||
uint64_t live_memory = 0;
|
||||
uint64_t garbage_entries = 0;
|
||||
uint64_t garbage_memory = 0;
|
||||
|
||||
uint64_t next_lsn = 0;
|
||||
uint32_t last_allocated_block = UINT32_MAX;
|
||||
heap_mvcc_map_t object_mvcc;
|
||||
@@ -200,9 +219,11 @@ class blockstore_heap_t
|
||||
|
||||
bool marked_used_blocks = false;
|
||||
bool recheck_queue_filled = false;
|
||||
std::vector<heap_list_item_t*> loaded_list_items;
|
||||
std::vector<heap_list_item_t*> postponed_items;
|
||||
std::set<uint32_t> recheck_modified_blocks;
|
||||
std::deque<heap_entry_t*> recheck_queue;
|
||||
std::map<heap_entry_t*, heap_recheck_state_t> recheck_states;
|
||||
size_t recheck_pending_reads = 0;
|
||||
int recheck_in_progress = 0;
|
||||
bool in_recheck = false;
|
||||
std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> recheck_cb;
|
||||
@@ -211,14 +232,22 @@ class blockstore_heap_t
|
||||
uint64_t get_pg_id(inode_t inode, uint64_t stripe);
|
||||
bool validate_object(heap_entry_t *obj);
|
||||
void fill_recheck_queue();
|
||||
void recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr);
|
||||
void recheck_start_reads(heap_recheck_state_t *st);
|
||||
int mark_used_blocks();
|
||||
void init_free_bad_entry(heap_entry_t *wr);
|
||||
void init_erase_bad_entry(heap_list_item_t *li);
|
||||
bool init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li);
|
||||
void recheck_full_gc();
|
||||
void recheck_buffer(heap_entry_t *cwr, uint8_t *buf);
|
||||
void defragment_block(uint32_t block_num);
|
||||
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);
|
||||
void insert_list_items(heap_list_item_t** v, size_t count, bool postpone);
|
||||
void remove_list_item(heap_list_item_t *li);
|
||||
void unlink_list_item(heap_list_item_t *li);
|
||||
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
|
||||
bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry);
|
||||
int add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type);
|
||||
@@ -345,6 +374,10 @@ public:
|
||||
uint32_t get_compact_queue_size();
|
||||
uint32_t get_to_compact_count();
|
||||
uint64_t get_compacted_count();
|
||||
uint64_t get_live_entries();
|
||||
uint64_t get_live_memory();
|
||||
uint64_t get_garbage_entries();
|
||||
uint64_t get_garbage_memory();
|
||||
|
||||
uint64_t entry_pos(uint32_t block_num, uint32_t offset);
|
||||
heap_entry_t *entry_from_pos(uint64_t entry_pos, bool allow_unallocated = false);
|
||||
|
||||
@@ -101,6 +101,7 @@ void blockstore_impl_t::loop()
|
||||
unsigned initial_ring_space = ringloop->space_left();
|
||||
int op_idx = 0, new_idx = 0;
|
||||
bool has_unfinished_writes = false;
|
||||
bool has_unfinished_sync = false;
|
||||
for (; op_idx < submit_queue.size(); op_idx++, new_idx++)
|
||||
{
|
||||
auto op = submit_queue[op_idx];
|
||||
@@ -138,7 +139,13 @@ void blockstore_impl_t::loop()
|
||||
else if (op->opcode == BS_OP_SYNC)
|
||||
{
|
||||
// syncs only completed writes, so doesn't have to be blocked by anything
|
||||
wr_st = continue_sync(op);
|
||||
if (!has_unfinished_sync)
|
||||
{
|
||||
wr_st = continue_sync(op);
|
||||
has_unfinished_sync = (wr_st != 2);
|
||||
}
|
||||
else
|
||||
wr_st = 0;
|
||||
}
|
||||
else if (op->opcode == BS_OP_STABLE || op->opcode == BS_OP_ROLLBACK)
|
||||
{
|
||||
@@ -154,9 +161,7 @@ void blockstore_impl_t::loop()
|
||||
wr_st = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
wr_st = 0;
|
||||
}
|
||||
}
|
||||
if (wr_st == 2)
|
||||
{
|
||||
|
||||
@@ -229,4 +229,9 @@ public:
|
||||
uint64_t get_free_block_count();
|
||||
inline uint32_t get_bitmap_granularity() { return dsk.bitmap_granularity; }
|
||||
inline uint64_t get_journal_size() { return dsk.journal_len; }
|
||||
|
||||
inline uint64_t get_live_entries() { return heap->get_live_entries(); }
|
||||
inline uint64_t get_live_memory() { return heap->get_live_memory(); }
|
||||
inline uint64_t get_garbage_entries() { return heap->get_garbage_entries(); }
|
||||
inline uint64_t get_garbage_memory() { return heap->get_garbage_memory(); }
|
||||
};
|
||||
|
||||
@@ -153,6 +153,14 @@ resume_1:
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
uint32_t csum = hdr->header_csum;
|
||||
hdr->header_csum = 0;
|
||||
if (crc32c(0, hdr, sizeof(*hdr)) != csum)
|
||||
{
|
||||
printf("Metadata header is corrupt (checksum mismatch).\n");
|
||||
exit(1);
|
||||
}
|
||||
hdr->header_csum = csum;
|
||||
}
|
||||
bs->heap->start_load(((blockstore_meta_header_v3_t *)bs->meta_superblock)->completed_lsn);
|
||||
if (bs->dsk.inmemory_journal)
|
||||
@@ -240,23 +248,7 @@ resume_4:
|
||||
}
|
||||
// metadata read finished
|
||||
bs->heap->finish_load();
|
||||
printf("Metadata entries loaded: %ju, used blocks: %ju / %ju\n", entries_loaded, bs->heap->get_data_used_space() / bs->dsk.data_block_size, bs->dsk.block_count);
|
||||
if (zero_on_init && !bs->dsk.disable_meta_fsync)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("Metadata entries loaded: %ju, rechecking unfinished writes and garbage entries\n", entries_loaded);
|
||||
// asynchronous recheck
|
||||
resume_6:
|
||||
wait_state = 6;
|
||||
@@ -293,6 +285,11 @@ resume_7:
|
||||
if (bs->readonly)
|
||||
{
|
||||
recheck_mod.clear();
|
||||
printf("Actual metadata entries: %ju\n", bs->heap->get_live_entries());
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Actual metadata entries: %ju, clearing garbage in %zu metadata blocks\n", bs->heap->get_live_entries(), recheck_mod.size());
|
||||
}
|
||||
for (i = 0; i < recheck_mod.size(); i++)
|
||||
{
|
||||
@@ -306,7 +303,7 @@ resume_8:
|
||||
uint32_t block_num = recheck_mod[i];
|
||||
uint64_t block_offset = bs->dsk.meta_offset + (uint64_t)(block_num+1) * bs->dsk.meta_block_size;
|
||||
data = ((ring_data_t*)sqe->user_data);
|
||||
uint8_t *buf = (uint8_t*)malloc_or_die(bs->dsk.meta_block_size);
|
||||
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, bs->dsk.meta_block_size);
|
||||
bs->heap->get_meta_block(block_num, buf);
|
||||
data->iov = { buf, bs->dsk.meta_block_size };
|
||||
data->callback = [this, buf, block_offset](ring_data_t *data)
|
||||
@@ -332,5 +329,25 @@ resume_9:
|
||||
}
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
if (!bs->dsk.disable_meta_fsync && !bs->readonly)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("Loading finished. Data used: %ju / %ju bytes (%s / %s)\n",
|
||||
bs->heap->get_data_used_space(), bs->dsk.block_count * bs->dsk.data_block_size,
|
||||
format_size(bs->heap->get_data_used_space()).c_str(),
|
||||
format_size(bs->dsk.block_count * bs->dsk.data_block_size).c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -462,6 +462,10 @@ int blockstore_impl_t::read_bitmap(object_id oid, uint64_t target_version, void
|
||||
{
|
||||
if (target_version >= wr->version)
|
||||
{
|
||||
if (wr->type() == BS_HEAP_DELETE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
found = true;
|
||||
if (result_version)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
else if (priv->op_state == 5) goto resume_5;
|
||||
assert(!priv->op_state);
|
||||
op->retval = 0;
|
||||
PRIV(op)->lsn = 0;
|
||||
priv->modified_block = priv->modified_block2 = UINT32_MAX;
|
||||
for (priv->stab_pos = 0; priv->stab_pos < op->len; priv->stab_pos++)
|
||||
{
|
||||
@@ -36,6 +37,12 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (res == ENOENT)
|
||||
{
|
||||
op->retval = -ENOENT;
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (res == ENOSPC)
|
||||
{
|
||||
if (!heap->get_to_compact_count())
|
||||
|
||||
@@ -9,6 +9,7 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
|
||||
if (!PRIV(op)->op_state)
|
||||
{
|
||||
op->retval = 0;
|
||||
PRIV(op)->lsn = 0;
|
||||
}
|
||||
int res = do_sync(op, 0);
|
||||
if (res == 2)
|
||||
@@ -104,7 +105,8 @@ int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
|
||||
unsynced_data_write_count = unsynced_buffer_write_count = unsynced_meta_write_count = 0;
|
||||
return 2;
|
||||
}
|
||||
PRIV(op)->modified_block = heap->get_completed_lsn();
|
||||
assert(!PRIV(op)->lsn);
|
||||
PRIV(op)->lsn = heap->get_completed_lsn();
|
||||
if (!submit_fsyncs(PRIV(op)->pending_ops))
|
||||
{
|
||||
PRIV(op)->wait_detail = 1;
|
||||
@@ -118,6 +120,6 @@ resume_1:
|
||||
return 1;
|
||||
}
|
||||
resume_2:
|
||||
heap->mark_lsn_fsynced(PRIV(op)->modified_block);
|
||||
heap->mark_lsn_fsynced(PRIV(op)->lsn);
|
||||
return 2;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
|
||||
heap->complete_block_write(modified_block);
|
||||
ringloop->wakeup();
|
||||
};
|
||||
assert(((uint64_t)modified_block+2)*dsk.meta_block_size <= dsk.meta_area_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
|
||||
);
|
||||
@@ -177,6 +178,7 @@ enospc:
|
||||
ring_data_t *data = ((ring_data_t*)sqe->user_data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->offset+op->len <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
|
||||
PRIV(op)->pending_ops++;
|
||||
write_iodepth++;
|
||||
@@ -264,6 +266,7 @@ enospc:
|
||||
BS_SUBMIT_GET_SQE(sqe2, data2);
|
||||
data2->iov = (struct iovec){ op->buf, op->len };
|
||||
data2->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->len <= dsk.journal_len);
|
||||
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, dsk.journal_offset + loc);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
@@ -453,6 +456,7 @@ resume_10:
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
data->iov = (struct iovec){ op->buf, op->len };
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(PRIV(op)->location + op->offset <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + PRIV(op)->location + op->offset);
|
||||
if (dsk.use_atomic_flag)
|
||||
sqe->rw_flags = RWF_ATOMIC;
|
||||
|
||||
@@ -141,7 +141,7 @@ struct __attribute__((__packed__)) journal_entry
|
||||
inline uint32_t je_crc32(journal_entry *je)
|
||||
{
|
||||
// 0x48674bc7 = crc32(4 zero bytes)
|
||||
return crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
return je->size < 4 ? 0 : crc32c(0x48674bc7, ((uint8_t*)je)+4, je->size-4);
|
||||
}
|
||||
|
||||
// "VITAstor"
|
||||
|
||||
@@ -520,6 +520,7 @@ resume_2:
|
||||
await_sqe(15);
|
||||
data->iov = (struct iovec){ it->buf, (size_t)it->len };
|
||||
data->callback = simple_callback_w;
|
||||
assert(clean_loc+it->offset+it->len <= bs->dsk.block_count*bs->dsk.data_block_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, bs->dsk.data_fd, &data->iov, 1, bs->dsk.data_offset + clean_loc + it->offset
|
||||
);
|
||||
@@ -749,6 +750,7 @@ bool journal_flusher_co::write_meta_block(flusher_meta_write_t & meta_block, int
|
||||
await_sqe(0);
|
||||
data->iov = (struct iovec){ meta_block.buf, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = simple_callback_w;
|
||||
assert(bs->dsk.meta_block_size + meta_block.sector + bs->dsk.meta_block_size <= bs->dsk.meta_area_size);
|
||||
io_uring_prep_writev(
|
||||
sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bs->dsk.meta_block_size + meta_block.sector
|
||||
);
|
||||
|
||||
@@ -855,4 +855,29 @@ std::string blockstore_impl_t::get_op_diag(blockstore_op_t *op)
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
uint64_t blockstore_impl_t::get_live_entries()
|
||||
{
|
||||
return used_blocks;
|
||||
}
|
||||
|
||||
uint64_t blockstore_impl_t::get_live_memory()
|
||||
{
|
||||
uint64_t used = 0;
|
||||
for (auto & kv: clean_db_shards)
|
||||
{
|
||||
used += kv.second.size() * sizeof(blockstore_clean_db_t::value_type);
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
uint64_t blockstore_impl_t::get_garbage_entries()
|
||||
{
|
||||
return dirty_db.size();
|
||||
}
|
||||
|
||||
uint64_t blockstore_impl_t::get_garbage_memory()
|
||||
{
|
||||
return (sizeof(obj_ver_id) + sizeof(dirty_entry) + 32) * dirty_db.size();
|
||||
}
|
||||
|
||||
} // namespace v1
|
||||
|
||||
@@ -332,6 +332,10 @@ public:
|
||||
inline uint64_t get_free_block_count() { return dsk.block_count - used_blocks; }
|
||||
inline uint32_t get_bitmap_granularity() { return dsk.disk_alignment; }
|
||||
inline uint64_t get_journal_size() { return dsk.journal_len; }
|
||||
uint64_t get_live_entries();
|
||||
uint64_t get_live_memory();
|
||||
uint64_t get_garbage_entries();
|
||||
uint64_t get_garbage_memory();
|
||||
};
|
||||
|
||||
} // namespace v1
|
||||
|
||||
@@ -193,6 +193,7 @@ void blockstore_impl_t::prepare_journal_sector_write(int cur_sector, blockstore_
|
||||
(size_t)journal.block_size
|
||||
};
|
||||
data->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
|
||||
assert(journal.sector_info[cur_sector].offset+journal.block_size <= dsk.journal_len);
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.journal_fd, &data->iov, 1, journal.offset + journal.sector_info[cur_sector].offset
|
||||
);
|
||||
|
||||
@@ -368,9 +368,9 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
}
|
||||
data->iov.iov_len = op->len + stripe_offset + stripe_end; // to check it in the callback
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
io_uring_prep_writev(
|
||||
sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + (loc * dsk.data_block_size) + op->offset - stripe_offset
|
||||
);
|
||||
const uint64_t write_offset = (loc * dsk.data_block_size) + op->offset - stripe_offset;
|
||||
assert(write_offset+op->len+stripe_offset+stripe_end <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, PRIV(op)->iov_zerofill, vcnt, dsk.data_offset + write_offset);
|
||||
PRIV(op)->pending_ops = 1;
|
||||
if (!(dirty_it->second.state & BS_ST_INSTANT))
|
||||
{
|
||||
@@ -495,9 +495,8 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
|
||||
.op = op,
|
||||
});
|
||||
data2->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
|
||||
io_uring_prep_writev(
|
||||
sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free
|
||||
);
|
||||
assert(journal.next_free+op->len <= dsk.journal_len);
|
||||
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free);
|
||||
PRIV(op)->pending_ops++;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
@@ -12,11 +12,11 @@ if (RDMACM_LIBRARIES)
|
||||
set(MSGR_RDMACM "msgr_rdmacm.cpp")
|
||||
endif (RDMACM_LIBRARIES)
|
||||
add_library(vitastor_common STATIC
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp
|
||||
msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp msgr_iothread.cpp ../util/addr_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp
|
||||
msgr_encrypt.cpp msgr_handshake.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
|
||||
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
|
||||
)
|
||||
target_link_libraries(vitastor_common pthread)
|
||||
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES} ${ISAL_CRYPTO_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,8 @@ target_link_libraries(vitastor_client
|
||||
${LIBURING_LIBRARIES}
|
||||
${IBVERBS_LIBRARIES}
|
||||
${RDMACM_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
${ISAL_CRYPTO_LIBRARIES}
|
||||
)
|
||||
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
|
||||
configure_file(vitastor.pc.in vitastor.pc @ONLY)
|
||||
@@ -52,9 +55,6 @@ if (${WITH_FIO})
|
||||
../util/rw_blocking.cpp
|
||||
../util/addr_util.cpp
|
||||
)
|
||||
target_link_libraries(fio_vitastor_sec
|
||||
tcmalloc_minimal
|
||||
)
|
||||
endif (${WITH_FIO})
|
||||
|
||||
# vitastor-nbd
|
||||
@@ -98,9 +98,10 @@ endif (${WITH_QEMU})
|
||||
add_executable(test_cluster_client
|
||||
EXCLUDE_FROM_ALL
|
||||
../test/test_cluster_client.cpp
|
||||
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp
|
||||
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp
|
||||
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp cluster_client_icache.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp
|
||||
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../util/xxh_x86dispatch.c ../util/openssl_util.cpp ../../json11/json11.cpp
|
||||
)
|
||||
target_link_libraries(test_cluster_client ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
|
||||
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
|
||||
add_dependencies(build_tests test_cluster_client)
|
||||
|
||||
+148
-102
@@ -27,7 +27,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
msgr.ringloop = ringloop;
|
||||
msgr.repeer_pgs = [this](osd_num_t peer_osd)
|
||||
{
|
||||
if (msgr.osd_peer_fds.find(peer_osd) != msgr.osd_peer_fds.end())
|
||||
if (msgr.osd_peers.find(peer_osd) != msgr.osd_peers.end())
|
||||
{
|
||||
// peer_osd just connected
|
||||
continue_ops();
|
||||
@@ -47,11 +47,11 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
msgr.exec_op = [this](osd_op_t *op)
|
||||
{
|
||||
// Garbage in
|
||||
fprintf(stderr, "Incoming garbage from peer %d\n", op->peer_fd);
|
||||
msgr.stop_client(op->peer_fd);
|
||||
fprintf(stderr, "Can't handle incoming operation from client %lu\n", op->client_id);
|
||||
msgr.stop_client(op->client_id);
|
||||
delete op;
|
||||
};
|
||||
msgr.parse_config(config);
|
||||
msgr.parse_config(config, true);
|
||||
|
||||
st_cli.tfd = tfd;
|
||||
st_cli.on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
@@ -62,6 +62,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli.on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
|
||||
st_cli.on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
|
||||
st_cli.on_reload_hook = [this]() { st_cli.load_global_config(); };
|
||||
st_cli.on_inode_change_hook = [this](uint64_t inode, bool removed) { on_change_inode_hook(inode, removed); };
|
||||
|
||||
st_cli.parse_config(config);
|
||||
st_cli.infinite_start = false;
|
||||
@@ -70,13 +71,11 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
st_cli.infinite_start = config["client_infinite_start"].bool_value();
|
||||
}
|
||||
st_cli.load_global_config();
|
||||
|
||||
scrap_buffer_size = SCRAP_BUFFER_SIZE;
|
||||
scrap_buffer = malloc_or_die(scrap_buffer_size);
|
||||
}
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
vault_destroy();
|
||||
if (retry_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(retry_timeout_id);
|
||||
@@ -94,7 +93,6 @@ cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
ringloop->unregister_consumer(&consumer);
|
||||
}
|
||||
free(scrap_buffer);
|
||||
delete wb;
|
||||
wb = NULL;
|
||||
}
|
||||
@@ -156,7 +154,7 @@ void cluster_client_t::continue_raw_ops(osd_num_t peer_osd)
|
||||
{
|
||||
auto op = it->second;
|
||||
op->op_type = OSD_OP_OUT;
|
||||
op->peer_fd = msgr.osd_peer_fds.at(peer_osd);
|
||||
op->client_id = msgr.osd_peers.at(peer_osd)->client_id;
|
||||
msgr.outbox_push(op);
|
||||
raw_ops.erase(it++);
|
||||
}
|
||||
@@ -481,7 +479,9 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
self_tree_metrics.clear();
|
||||
client_hostname = new_hostname;
|
||||
}
|
||||
msgr.parse_config(config);
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config, false);
|
||||
st_cli.parse_config(config);
|
||||
st_cli.load_pgs();
|
||||
}
|
||||
@@ -590,7 +590,7 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
{
|
||||
if (log_level > 2 && pg_counts[pool_item.first])
|
||||
{
|
||||
printf("Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
|
||||
fprintf(stderr, "Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
|
||||
pg_counts[pool_item.first], pool_item.second.real_pg_count);
|
||||
}
|
||||
// At this point, all pool operations should have been suspended
|
||||
@@ -607,6 +607,9 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
}
|
||||
inode_cache.clear();
|
||||
inode_cache_children.clear();
|
||||
vault_keys.clear();
|
||||
continue_ops();
|
||||
}
|
||||
|
||||
@@ -673,6 +676,10 @@ bool cluster_client_t::flush()
|
||||
{
|
||||
if (!ringloop)
|
||||
{
|
||||
if (vault_loading)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (wb->writeback_queue.size())
|
||||
{
|
||||
wb->start_writebacks(this, 0);
|
||||
@@ -695,7 +702,7 @@ bool cluster_client_t::flush()
|
||||
sync_done = true;
|
||||
};
|
||||
execute(sync);
|
||||
while (!sync_done)
|
||||
while (!sync_done || vault_loading)
|
||||
{
|
||||
ringloop->loop();
|
||||
if (!sync_done)
|
||||
@@ -871,13 +878,13 @@ void cluster_client_t::execute_cas(cluster_op_t *op)
|
||||
if (op->retval != expected && op->retval >= 0)
|
||||
op->retval = -EIO;
|
||||
op->retval = op->retval == -EPIPE ? -EINTR : op->retval;
|
||||
auto peer_it = msgr.osd_peer_fds.find(op->parts[0].osd_num);
|
||||
auto peer_it = msgr.osd_peers.find(op->parts[0].osd_num);
|
||||
if (op->retval != 0 || (op->flags & OP_IMMEDIATE_COMMIT))
|
||||
{
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
}
|
||||
else if (peer_it == msgr.osd_peer_fds.end())
|
||||
else if (peer_it == msgr.osd_peers.end())
|
||||
{
|
||||
// Care must be taken to make sure that the client doesn't reconnect to the OSD
|
||||
// before executing the previously completed operation callback (!)
|
||||
@@ -888,10 +895,10 @@ void cluster_client_t::execute_cas(cluster_op_t *op)
|
||||
else
|
||||
{
|
||||
// CAS writes have a built-in sync
|
||||
auto peer_fd = peer_it->second;
|
||||
osd_client_t *cl = peer_it->second;
|
||||
*part = (osd_op_t){
|
||||
.op_type = OSD_OP_OUT,
|
||||
.peer_fd = peer_fd,
|
||||
.client_id = cl->client_id,
|
||||
.req = {
|
||||
.hdr = {
|
||||
.magic = SECONDARY_OSD_OP_MAGIC,
|
||||
@@ -958,10 +965,40 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
{
|
||||
op->flags |= OP_IMMEDIATE_COMMIT;
|
||||
}
|
||||
bool searched = false;
|
||||
std::shared_ptr<inode_cache_t> icache;
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
|
||||
{
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (icache && icache->op_enc)
|
||||
{
|
||||
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
|
||||
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
|
||||
{
|
||||
auto ino_it = st_cli.inode_config.find(op->inode);
|
||||
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->readonly)
|
||||
{
|
||||
op->retval = -EROFS;
|
||||
auto cb = std::move(op->callback);
|
||||
@@ -972,43 +1009,49 @@ 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;
|
||||
}
|
||||
|
||||
void cluster_client_t::execute_raw(osd_num_t osd_num, osd_op_t *op)
|
||||
{
|
||||
auto fd_it = msgr.osd_peer_fds.find(osd_num);
|
||||
if (fd_it != msgr.osd_peer_fds.end())
|
||||
auto peer_it = msgr.osd_peers.find(osd_num);
|
||||
if (peer_it != msgr.osd_peers.end())
|
||||
{
|
||||
op->op_type = OSD_OP_OUT;
|
||||
op->peer_fd = fd_it->second;
|
||||
op->client_id = peer_it->second->client_id;
|
||||
msgr.outbox_push(op);
|
||||
}
|
||||
else
|
||||
@@ -1119,33 +1162,42 @@ resume_2:
|
||||
// Finished successfully
|
||||
// Even if the PG count has changed in meanwhile we treat it as success
|
||||
// because if some operations were invalid for the new PG count we'd get errors
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Copy part bitmaps only after finishing all part reads
|
||||
for (auto & part: op->parts)
|
||||
if ((part.flags & (PART_SENT|PART_DONE|PART_VALID)) == (PART_SENT|PART_DONE|PART_VALID))
|
||||
copy_part_bitmap(op, &part);
|
||||
}
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
// Check parent inode
|
||||
auto ino_it = st_cli.inode_config.find(op->cur_inode);
|
||||
// Skip parents from the same pool
|
||||
int skipped = 0;
|
||||
while (!op->deoptimise_snapshot &&
|
||||
ino_it != st_cli.inode_config.end() && ino_it->second.parent_id &&
|
||||
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
|
||||
uint64_t next_inode = 0;
|
||||
auto icache = inode_cache_get(op->cur_inode);
|
||||
if (icache)
|
||||
{
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
skipped > st_cli.inode_config.size())
|
||||
if (icache->has_parent_loop)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
skipped++;
|
||||
ino_it = st_cli.inode_config.find(ino_it->second.parent_id);
|
||||
if (op->deoptimise_snapshot)
|
||||
{
|
||||
if (icache->chain.size() > 1)
|
||||
next_inode = icache->chain[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (icache->other_pool_parent_id)
|
||||
next_inode = icache->other_pool_parent_id;
|
||||
}
|
||||
}
|
||||
if (ino_it != st_cli.inode_config.end() &&
|
||||
ino_it->second.parent_id &&
|
||||
ino_it->second.parent_id != op->inode)
|
||||
if (next_inode)
|
||||
{
|
||||
// Continue reading from the parent inode
|
||||
op->cur_inode = ino_it->second.parent_id;
|
||||
icache = inode_cache_get(next_inode);
|
||||
op->cur_inode = next_inode;
|
||||
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
|
||||
op->parts.clear();
|
||||
op->done_count = 0;
|
||||
goto resume_0;
|
||||
@@ -1164,7 +1216,7 @@ resume_2:
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
else if (op->retval != 0 && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
else if (op->retval != 0 && op->opcode != OSD_OP_SYNC && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||
op->retval != -EPIPE && (op->retval != -EIO || !client_eio_retry_interval) && (op->retval != -ENOSPC || !client_retry_enospc))
|
||||
{
|
||||
// Fatal error (neither -EPIPE, -EIO nor -ENOSPC)
|
||||
@@ -1196,7 +1248,7 @@ resume_2:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov, void *scrap, int scrap_len)
|
||||
static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov)
|
||||
{
|
||||
int left = size;
|
||||
while (left > 0 && iov_idx < op->iov.count)
|
||||
@@ -1204,7 +1256,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
int cur_left = op->iov.buf[iov_idx].iov_len - iov_pos;
|
||||
if (cur_left < left)
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, cur_left);
|
||||
}
|
||||
@@ -1214,7 +1266,7 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!skip)
|
||||
if (skip == 0)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, left);
|
||||
}
|
||||
@@ -1223,16 +1275,10 @@ static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t
|
||||
}
|
||||
}
|
||||
assert(left == 0);
|
||||
if (skip && scrap_len > 0)
|
||||
if (skip == 1)
|
||||
{
|
||||
// All skipped ranges are read into the same useless buffer
|
||||
left = size;
|
||||
while (left > 0)
|
||||
{
|
||||
int cur_left = scrap_len < left ? scrap_len : left;
|
||||
iov.push_back(scrap, cur_left);
|
||||
left -= cur_left;
|
||||
}
|
||||
// data read into a NULL buffer will be discarded by messenger
|
||||
iov.push_back(NULL, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1252,7 +1298,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
// Allocate memory for the bitmap
|
||||
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
|
||||
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
|
||||
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
|
||||
unsigned bitmap_mem = object_bitmap_size +
|
||||
op->parts.size() * pg_data_size *
|
||||
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->enc ? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*op->len/pool_cfg.bitmap_granularity : 0));
|
||||
if (!op->bitmap_buf || op->bitmap_buf_size < bitmap_mem)
|
||||
{
|
||||
op->bitmap_buf = realloc_or_die(op->bitmap_buf, bitmap_mem);
|
||||
@@ -1294,10 +1344,10 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
begin = cur;
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(cur-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(cur-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
skip_prev = skip;
|
||||
prev = cur;
|
||||
@@ -1308,11 +1358,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
if (skip_prev)
|
||||
{
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(end-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
end = prev;
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
if (end == begin)
|
||||
{
|
||||
op->done_count++;
|
||||
@@ -1321,7 +1371,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
}
|
||||
else if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_DELETE)
|
||||
{
|
||||
add_iov(end-begin, false, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
add_iov(end-begin, 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
}
|
||||
op->parts[i].parent = op;
|
||||
op->parts[i].offset = begin;
|
||||
@@ -1401,15 +1451,18 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
primary_osd = nearest_osd;
|
||||
}
|
||||
part->osd_num = primary_osd;
|
||||
auto peer_it = msgr.osd_peer_fds.find(primary_osd);
|
||||
if (peer_it != msgr.osd_peer_fds.end())
|
||||
auto peer_it = msgr.osd_peers.find(primary_osd);
|
||||
if (peer_it != msgr.osd_peers.end())
|
||||
{
|
||||
int peer_fd = peer_it->second;
|
||||
osd_client_t *cl = peer_it->second;
|
||||
part->flags |= PART_SENT|PART_VALID;
|
||||
op->inflight_count++;
|
||||
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->opcode == OSD_OP_READ && op->enc
|
||||
? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*pool_cfg.data_block_size/pool_cfg.bitmap_granularity
|
||||
: 0));
|
||||
uint64_t meta_rev = 0;
|
||||
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
|
||||
{
|
||||
@@ -1419,7 +1472,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
}
|
||||
part->op = (osd_op_t){
|
||||
.op_type = OSD_OP_OUT,
|
||||
.peer_fd = peer_fd,
|
||||
.client_id = cl->client_id,
|
||||
.req = { .rw = {
|
||||
.header = {
|
||||
.magic = SECONDARY_OSD_OP_MAGIC,
|
||||
@@ -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);
|
||||
@@ -1468,8 +1523,8 @@ int cluster_client_t::continue_sync(cluster_op_t *op)
|
||||
for (auto do_it = dirty_osds.begin(); do_it != dirty_osds.end(); )
|
||||
{
|
||||
osd_num_t sync_osd = *do_it;
|
||||
auto peer_it = msgr.osd_peer_fds.find(sync_osd);
|
||||
if (peer_it == msgr.osd_peer_fds.end())
|
||||
auto peer_it = msgr.osd_peers.find(sync_osd);
|
||||
if (peer_it == msgr.osd_peers.end())
|
||||
dirty_osds.erase(do_it++);
|
||||
else
|
||||
do_it++;
|
||||
@@ -1522,12 +1577,12 @@ resume_1:
|
||||
|
||||
void cluster_client_t::send_sync(cluster_op_t *op, cluster_op_part_t *part)
|
||||
{
|
||||
auto peer_fd = msgr.osd_peer_fds.at(part->osd_num);
|
||||
osd_client_t *cl = msgr.osd_peers.at(part->osd_num);
|
||||
part->flags |= PART_SENT;
|
||||
op->inflight_count++;
|
||||
part->op = (osd_op_t){
|
||||
.op_type = OSD_OP_OUT,
|
||||
.peer_fd = peer_fd,
|
||||
.client_id = cl->client_id,
|
||||
.req = {
|
||||
.hdr = {
|
||||
.magic = SECONDARY_OSD_OP_MAGIC,
|
||||
@@ -1552,6 +1607,9 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
|
||||
}
|
||||
}
|
||||
|
||||
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
|
||||
|
||||
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
cluster_op_t *op = part->parent;
|
||||
@@ -1560,17 +1618,12 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
// Operation failed, retry
|
||||
part->flags |= PART_ERROR;
|
||||
if (!op->retval || op->retval == -EPIPE ||
|
||||
part->op.reply.hdr.retval == -ENOSPC && op->retval == -ETIMEDOUT ||
|
||||
part->op.reply.hdr.retval == -EIO)
|
||||
{
|
||||
// Error priority: EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
|
||||
op->retval = part->op.reply.hdr.retval;
|
||||
}
|
||||
int stop_fd = -1;
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
|
||||
uint64_t stop_client_id = 0;
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
|
||||
{
|
||||
stop_fd = part->op.peer_fd;
|
||||
stop_client_id = part->op.client_id;
|
||||
if (op->retval != -EPIPE || log_level > 0)
|
||||
{
|
||||
fprintf(
|
||||
@@ -1597,9 +1650,9 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
op->retry_after = op->retval != -EPIPE ? client_eio_retry_interval : client_retry_interval;
|
||||
}
|
||||
reset_retry_timer(op->retry_after);
|
||||
if (stop_fd >= 0)
|
||||
if (stop_client_id)
|
||||
{
|
||||
msgr.stop_client(stop_fd);
|
||||
msgr.stop_client(stop_client_id);
|
||||
}
|
||||
op->inflight_count--;
|
||||
if (op->inflight_count == 0 && !op->retry_after)
|
||||
@@ -1630,13 +1683,6 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
}
|
||||
if (op->inflight_count == 0 && !op->retry_after)
|
||||
{
|
||||
// Copy part bitmaps only after finishing all part reads
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
for (auto & part: op->parts)
|
||||
if (part.flags == (PART_SENT|PART_VALID|PART_DONE))
|
||||
copy_part_bitmap(op, &part);
|
||||
}
|
||||
if (op->opcode == OSD_OP_SYNC)
|
||||
continue_sync(op);
|
||||
else
|
||||
|
||||
+59
-13
@@ -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,17 +82,33 @@ struct inode_list_osd_t;
|
||||
struct inode_list_pg_t;
|
||||
class writeback_cache_t;
|
||||
|
||||
struct inode_cache_t
|
||||
{
|
||||
std::vector<inode_t> chain; // only parents from the same pool
|
||||
uint8_t *key_data = NULL;
|
||||
osd_op_enc_t *op_enc = NULL;
|
||||
bool readonly = false;
|
||||
bool has_parent_loop = false;
|
||||
inode_t other_pool_parent_id = 0;
|
||||
int err_code = 0;
|
||||
|
||||
~inode_cache_t();
|
||||
};
|
||||
|
||||
struct vault_load_key_t
|
||||
{
|
||||
int key_state = 0;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
// FIXME: Split into public and private interfaces
|
||||
class __attribute__((visibility("default"))) cluster_client_t
|
||||
{
|
||||
#ifdef __MOCK__
|
||||
public:
|
||||
#endif
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
std::map<pool_pg_num_t, osd_num_t> pg_primary;
|
||||
// config:
|
||||
|
||||
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
|
||||
uint64_t client_max_dirty_bytes = 0;
|
||||
uint64_t client_max_dirty_ops = 0;
|
||||
@@ -102,12 +120,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,15 +144,28 @@ 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;
|
||||
uint64_t dirty_bytes = 0, dirty_ops = 0;
|
||||
|
||||
void *scrap_buffer = NULL;
|
||||
unsigned scrap_buffer_size = 0;
|
||||
// inodes require some extra state for read/write, it's stored here.
|
||||
// moreover, robin_hood access is slightly faster than std::map :)
|
||||
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
|
||||
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
|
||||
|
||||
http_context_t *vault_http_ctx = NULL;
|
||||
http_co_t *vault_http_cli = NULL;
|
||||
bool vault_loading = false;
|
||||
std::string vault_token;
|
||||
bool vault_auth_error = false;
|
||||
timespec vault_token_expire = {};
|
||||
std::vector<std::string> vault_key_load_queue;
|
||||
std::map<std::string, vault_load_key_t> vault_keys;
|
||||
|
||||
bool pgs_loaded = false;
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
ring_consumer_t consumer;
|
||||
std::vector<std::function<void(void)>> on_ready_hooks;
|
||||
int list_retry_timeout_id = -1;
|
||||
@@ -155,15 +197,16 @@ public:
|
||||
void list_inode(inode_t inode, uint64_t min_offset, uint64_t max_offset, int max_parallel_pgs, std::function<void(
|
||||
int status, int pgs_left, pg_num_t pg_num, std::set<object_id>&& objects)> pg_callback);
|
||||
|
||||
//inline uint32_t get_bs_bitmap_granularity() { return st_cli.global_bitmap_granularity; }
|
||||
//inline uint64_t get_bs_block_size() { return st_cli.global_block_size; }
|
||||
|
||||
#ifndef __MOCK__
|
||||
protected:
|
||||
#endif
|
||||
void continue_ops(int time_passed = 0);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
|
||||
void vault_parse_config();
|
||||
bool vault_check_token();
|
||||
void vault_load_keys();
|
||||
void vault_destroy();
|
||||
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
|
||||
|
||||
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
|
||||
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
|
||||
|
||||
@@ -173,6 +216,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 +233,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);
|
||||
@@ -204,4 +249,5 @@ protected:
|
||||
osd_num_t select_nearest_osd(const std::vector<osd_num_t> & osds);
|
||||
|
||||
friend class writeback_cache_t;
|
||||
friend class cluster_client_test_t;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
#include "cluster_client_impl.h"
|
||||
#include "http_client.h"
|
||||
#include "str_util.h"
|
||||
|
||||
#define VAULT_KEY_NOT_LOADED 0
|
||||
#define VAULT_KEY_LOADING 1
|
||||
#define VAULT_KEY_LOADED 2
|
||||
#define VAULT_KEY_ERROR 3
|
||||
|
||||
inode_cache_t::~inode_cache_t()
|
||||
{
|
||||
if (key_data)
|
||||
{
|
||||
free(key_data);
|
||||
key_data = NULL;
|
||||
op_enc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_destroy()
|
||||
{
|
||||
if (vault_http_ctx)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
http_destroy(vault_http_cli);
|
||||
http_context_destroy(vault_http_ctx);
|
||||
vault_http_cli = NULL;
|
||||
vault_http_ctx = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_config()
|
||||
{
|
||||
vault_url = config["vault_url"].string_value();
|
||||
vault_client_cert = config["vault_client_cert"].string_value();
|
||||
vault_client_key = config["vault_client_key"].string_value();
|
||||
vault_ca = config["vault_ca"].string_value();
|
||||
vault_secret_api_path = "/v1/secret/";
|
||||
if (config["vault_secret_api_path"].is_string())
|
||||
vault_secret_api_path = config["vault_secret_api_path"].string_value();
|
||||
vault_timeout_ms = config["vault_timeout_ms"].uint64_value();
|
||||
if (!vault_timeout_ms)
|
||||
vault_timeout_ms = 5000;
|
||||
vault_error_timeout_sec = config["vault_error_timeout_sec"].uint64_value();
|
||||
if (!vault_error_timeout_sec)
|
||||
vault_error_timeout_sec = 60;
|
||||
vault_refresh_leeway_sec = config["vault_refresh_leeway_sec"].uint64_value();
|
||||
if (!vault_refresh_leeway_sec)
|
||||
vault_refresh_leeway_sec = 60;
|
||||
}
|
||||
|
||||
// FIXME: Rework client API by adding open/close and cache inode information in the "FD" (maybe)
|
||||
void cluster_client_t::on_change_inode_hook(uint64_t inode, bool removed)
|
||||
{
|
||||
std::vector<inode_t> children = { inode };
|
||||
for (size_t i = 0; i < children.size(); i++)
|
||||
{
|
||||
auto it = inode_cache_children.lower_bound(std::make_pair(children[i], (inode_t)0));
|
||||
while (it != inode_cache_children.end() && it->first == children[i])
|
||||
{
|
||||
children.push_back(it->second);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
for (auto & inode: children)
|
||||
{
|
||||
auto it = inode_cache.find(inode);
|
||||
if (it != inode_cache.end())
|
||||
{
|
||||
auto icache = it->second;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
inode_cache_children.erase(std::make_pair(parent, inode));
|
||||
}
|
||||
inode_cache.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
|
||||
{
|
||||
auto icache_it = inode_cache.find(ino);
|
||||
if (icache_it != inode_cache.end())
|
||||
{
|
||||
return icache_it->second;
|
||||
}
|
||||
// Fill inode cache
|
||||
auto ino_it = st_cli.inode_config.find(ino);
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto pool_it = st_cli.pool_config.find(INODE_POOL(ino));
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
{
|
||||
inode_cache[ino] = NULL;
|
||||
return NULL;
|
||||
}
|
||||
auto & inode_cfg = ino_it->second;
|
||||
auto & pool_cfg = pool_it->second;
|
||||
std::shared_ptr<inode_cache_t> icache = std::make_shared<inode_cache_t>();
|
||||
icache->readonly = inode_cfg.readonly;
|
||||
icache->chain.push_back(ino);
|
||||
std::vector<inode_config_t*> chain_cfg;
|
||||
// FIXME: Allow unencrypted read & write when all chain is encrypted with the same key
|
||||
int enc_key_count = !inode_cfg.enc_key.empty() ? 1 : 0;
|
||||
if (inode_cfg.parent_id)
|
||||
{
|
||||
// Check for loops and cache the chain
|
||||
robin_hood::unordered_flat_set<inode_t> seen;
|
||||
seen.insert(ino);
|
||||
uint64_t parent_id = inode_cfg.parent_id;
|
||||
while (parent_id)
|
||||
{
|
||||
if (seen.find(parent_id) != seen.end())
|
||||
{
|
||||
icache->has_parent_loop = true;
|
||||
break;
|
||||
}
|
||||
seen.insert(parent_id);
|
||||
ino_it = st_cli.inode_config.find(parent_id);
|
||||
if (INODE_POOL(parent_id) == INODE_POOL(ino))
|
||||
{
|
||||
icache->chain.push_back(parent_id);
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
chain_cfg.push_back(NULL);
|
||||
else
|
||||
{
|
||||
chain_cfg.push_back(&ino_it->second);
|
||||
if (!ino_it->second.enc_key.empty())
|
||||
enc_key_count++;
|
||||
}
|
||||
}
|
||||
else if (!icache->other_pool_parent_id)
|
||||
icache->other_pool_parent_id = parent_id;
|
||||
if (ino_it == st_cli.inode_config.end())
|
||||
break;
|
||||
parent_id = ino_it->second.parent_id;
|
||||
}
|
||||
}
|
||||
// Check external keys and wait for loading, if required
|
||||
if (enc_key_count)
|
||||
{
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
|
||||
{
|
||||
auto & ik = vault_keys[inode_cfg.enc_key];
|
||||
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
|
||||
{
|
||||
icache->err_code = EPERM;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
|
||||
{
|
||||
ik.key_state = VAULT_KEY_LOADING;
|
||||
vault_key_load_queue.push_back(inode_cfg.enc_key);
|
||||
vault_load_keys();
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else if (ik.key_state == VAULT_KEY_LOADING)
|
||||
{
|
||||
icache->err_code = EAGAIN;
|
||||
enc_key_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ik.key_state == VAULT_KEY_LOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate encryption key chain, if applicable
|
||||
if (enc_key_count)
|
||||
{
|
||||
uint8_t *key_data = (uint8_t*)malloc_or_die(
|
||||
AES_256_XTS_KEY_SIZE * enc_key_count +
|
||||
sizeof(uint8_t*) * icache->chain.size() +
|
||||
sizeof(osd_op_enc_t)
|
||||
);
|
||||
uint8_t **keys = (uint8_t**)(key_data + AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
osd_op_enc_t *enc = (osd_op_enc_t*)((uint8_t*)keys + sizeof(uint8_t*)*icache->chain.size());
|
||||
size_t key_pos = 0;
|
||||
for (size_t i = 0; i <= chain_cfg.size(); i++)
|
||||
{
|
||||
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
|
||||
if (cfg && !cfg->enc_key.empty())
|
||||
{
|
||||
const auto & key = cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX
|
||||
? vault_keys.at(cfg->enc_key).key
|
||||
: cfg->enc_key;
|
||||
assert(key_pos < AES_256_XTS_KEY_SIZE * enc_key_count);
|
||||
assert(key.size() == 2*AES_256_XTS_KEY_SIZE);
|
||||
keys[i] = key_data + key_pos;
|
||||
fromhexstr(key, AES_256_XTS_KEY_SIZE, key_data + key_pos);
|
||||
key_pos += AES_256_XTS_KEY_SIZE;
|
||||
}
|
||||
else
|
||||
keys[i] = NULL;
|
||||
}
|
||||
enc->key_chain = keys;
|
||||
enc->chain_size = icache->chain.size();
|
||||
enc->read_chain_bitmap_pos = pool_cfg.data_block_size/pool_cfg.bitmap_granularity/8;
|
||||
enc->bitmap_granularity = pool_cfg.bitmap_granularity;
|
||||
icache->key_data = key_data;
|
||||
icache->op_enc = enc;
|
||||
}
|
||||
inode_cache[ino] = icache;
|
||||
for (auto & parent: icache->chain)
|
||||
{
|
||||
if (parent != ino)
|
||||
inode_cache_children.insert(std::make_pair(parent, ino));
|
||||
}
|
||||
return icache;
|
||||
}
|
||||
|
||||
#ifndef __MOCK__
|
||||
bool cluster_client_t::vault_check_token()
|
||||
{
|
||||
timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
if (!vault_token_expire.tv_sec || vault_token_expire.tv_sec < now.tv_sec)
|
||||
{
|
||||
vault_loading = true;
|
||||
http_json_post(
|
||||
vault_http_cli, vault_url+"/v1/auth/cert/login", json11::Json::object{}, "",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this](http_message_t *response)
|
||||
{
|
||||
clock_gettime(CLOCK_REALTIME, &vault_token_expire);
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "Vault request failed: %s\n", err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t ttl = data["auth"]["lease_duration"].uint64_value();
|
||||
vault_token = data["auth"]["client_token"].string_value();
|
||||
if (vault_token.empty() || !ttl)
|
||||
{
|
||||
vault_token_expire.tv_sec += vault_error_timeout_sec;
|
||||
fprintf(stderr, "No token or lease_duration in Vault response: %s\n", data.dump().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ttl < vault_refresh_leeway_sec)
|
||||
vault_token_expire.tv_sec += ttl/2;
|
||||
else
|
||||
vault_token_expire.tv_sec += ttl - vault_refresh_leeway_sec;
|
||||
}
|
||||
}
|
||||
vault_load_keys();
|
||||
}
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (vault_token.empty())
|
||||
{
|
||||
// Auth error happened, mark all loads as failed
|
||||
for (auto & key_id: vault_key_load_queue)
|
||||
{
|
||||
auto & k = vault_keys[key_id];
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
}
|
||||
vault_key_load_queue.clear();
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void cluster_client_t::vault_load_keys()
|
||||
{
|
||||
if (vault_loading || !vault_key_load_queue.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
#ifdef __MOCK__
|
||||
vault_loading = true;
|
||||
#else
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
vault_http_ctx = http_context_init(tfd, vault_client_cert, vault_client_key, vault_ca, true, error);
|
||||
if (!vault_http_ctx)
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context for Vault: %s\n", error.c_str());
|
||||
exit(1);
|
||||
}
|
||||
vault_http_cli = http_init(vault_http_ctx);
|
||||
}
|
||||
if (!vault_check_token())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::string key_id = vault_key_load_queue[0];
|
||||
vault_key_load_queue.erase(vault_key_load_queue.begin());
|
||||
vault_loading = true;
|
||||
http_get(
|
||||
vault_http_cli, vault_url+vault_secret_api_path+key_id.substr(strlen(VAULT_KEY_PREFIX)), "X-Vault-Token: "+vault_token+"\r\n",
|
||||
(http_options_t){ .timeout = (int)vault_timeout_ms, .keepalive = true },
|
||||
[this, key_id](http_message_t *response)
|
||||
{
|
||||
vault_loading = false;
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
vault_parse_secret(key_id, err, data);
|
||||
}
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cluster_client_t::vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data)
|
||||
{
|
||||
vault_loading = false;
|
||||
auto & k = vault_keys[key_id];
|
||||
if (err != "")
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault %s%s%s request failed: %s\n", vault_url.c_str(),
|
||||
vault_secret_api_path.c_str(), key_id.c_str()+strlen(VAULT_KEY_PREFIX), err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hexkey = data["data"]["key"].string_value();
|
||||
if (hexkey.empty() || !ishexstr(hexkey) || hexkey.size() != 2*AES_256_XTS_KEY_SIZE)
|
||||
{
|
||||
k.key_state = VAULT_KEY_ERROR;
|
||||
fprintf(stderr, "Vault /v1/secret/%s request failed: 'key' is empty or has invalid format\n", key_id.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
k.key_state = VAULT_KEY_LOADED;
|
||||
k.key = hexkey;
|
||||
}
|
||||
}
|
||||
if (vault_key_load_queue.empty())
|
||||
{
|
||||
auto ops = std::move(key_wait_ops);
|
||||
for (cluster_op_t *op: ops)
|
||||
inode_cache.erase(op->inode);
|
||||
for (cluster_op_t *op: ops)
|
||||
execute_internal(op);
|
||||
}
|
||||
else
|
||||
vault_load_keys();
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "cluster_client.h"
|
||||
|
||||
#define SCRAP_BUFFER_SIZE 4*1024*1024
|
||||
#define PART_SENT 1
|
||||
#define PART_DONE 2
|
||||
#define PART_ERROR 4
|
||||
|
||||
@@ -295,7 +295,7 @@ int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
bool conn = true;
|
||||
for (osd_num_t peer_osd: all_peers)
|
||||
{
|
||||
if (msgr.osd_peer_fds.find(peer_osd) == msgr.osd_peer_fds.end())
|
||||
if (msgr.osd_peers.find(peer_osd) == msgr.osd_peers.end())
|
||||
{
|
||||
// Initiate connection
|
||||
if (st_cli.peer_states[peer_osd].is_null())
|
||||
@@ -340,7 +340,7 @@ void cluster_client_t::send_list(inode_list_osd_t *cur_list)
|
||||
osd_op_t *op = new osd_op_t();
|
||||
op->op_type = OSD_OP_OUT;
|
||||
// Already checked that it exists above, but anyway
|
||||
op->peer_fd = msgr.osd_peer_fds.at(cur_list->osd_num);
|
||||
op->client_id = msgr.osd_peers.at(cur_list->osd_num)->client_id;
|
||||
op->req = (osd_any_op_t){
|
||||
.sec_list = {
|
||||
.header = {
|
||||
|
||||
@@ -88,6 +88,11 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
// ...or just save it for writeback if write buffering is enabled
|
||||
if (op->len == 0)
|
||||
{
|
||||
// FIXME: OSD_OP_DELETEs are currently only sent by vitastor-cli rm/rm-data and
|
||||
// actually have len=0, because delete is actually a delete of the full object
|
||||
// containing the requested offset, not a "punch hole" operation. But here, writeback
|
||||
// cache assumes it IS a "punch hole" operation. I should select one of these
|
||||
// approaches and fix everything accordingly when I decide to implement TRIM.
|
||||
return;
|
||||
}
|
||||
auto dirty_it = find_dirty(op->inode, op->offset);
|
||||
@@ -244,12 +249,13 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
writeback_queue_size--;
|
||||
}
|
||||
}
|
||||
if (!is_del)
|
||||
if (!is_del && op->len > 0)
|
||||
{
|
||||
uint64_t pos = 0, len = op->len, iov_idx = 0;
|
||||
while (len > 0 && iov_idx < op->iov.count)
|
||||
while (iov_idx < op->iov.count)
|
||||
{
|
||||
auto & iov = op->iov.buf[iov_idx];
|
||||
assert(pos + iov.iov_len <= len);
|
||||
memcpy(buf + pos, iov.iov_base, iov.iov_len);
|
||||
pos += iov.iov_len;
|
||||
iov_idx++;
|
||||
@@ -443,7 +449,7 @@ void writeback_cache_t::start_writebacks(cluster_client_t *cli, int count)
|
||||
started++;
|
||||
assert(writeback_queue_size > 0);
|
||||
writeback_queue_size--;
|
||||
writeback_bytes -= off - from_it->first.stripe;
|
||||
writeback_bytes -= (is_del ? 0 : off - from_it->first.stripe);
|
||||
assert(writeback_queue_size > 0 || !writeback_bytes);
|
||||
flush_buffers(cli, from_it, to_it);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user