Compare commits
114
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c02a6bd6 | ||
|
|
253b7b3de5 | ||
|
|
dbe87937a0 | ||
|
|
322f06d77e | ||
|
|
5dbc679e16 | ||
|
|
d48864a6be | ||
|
|
462482d319 | ||
|
|
5ef9d78461 | ||
|
|
1b20010a69 | ||
|
|
d750d00c6f | ||
|
|
66cb564e2c | ||
|
|
f782decbd6 | ||
|
|
717582c4ba | ||
|
|
cd62c0755f | ||
|
|
c6f5ab7d79 | ||
|
|
09607ddcbe | ||
|
|
278852b4d5 | ||
|
|
7b11c6e90d | ||
|
|
6251ce8b9a | ||
|
|
d4a42f61cf | ||
|
|
6b003bcc34 | ||
|
|
1c945bcb41 | ||
|
|
716527b184 | ||
|
|
8c0486bd76 | ||
|
|
d2cf271f64 | ||
|
|
d93b488e32 | ||
|
|
fbffec5abb | ||
|
|
b6eb8f2055 | ||
|
|
e373ea2163 | ||
|
|
5e12b4a1a5 | ||
|
|
78b067566f | ||
|
|
cf3abdb9e3 | ||
|
|
826b35b369 | ||
|
|
cdc730314b | ||
|
|
5248d7f324 | ||
|
|
4161f0bd01 | ||
|
|
5627977a9b | ||
|
|
ec9cfa76c1 | ||
|
|
0c88884576 | ||
|
|
ba7637d9ad | ||
|
|
ac1025c7a5 | ||
|
|
2a81cef78a | ||
|
|
bcf6a7c7d1 | ||
|
|
85c4be3957 | ||
|
|
cf2ba05e4b | ||
|
|
04c2f8d408 | ||
|
|
0e528ca8f3 | ||
|
|
c6f733b96a | ||
|
|
938ac09248 | ||
|
|
3becdbf5b9 | ||
|
|
9c49315fdf | ||
|
|
430d3cfb6f | ||
|
|
c491db699c | ||
|
|
e9d053e30f | ||
|
|
1be51f903c | ||
|
|
cd51f14a90 | ||
|
|
9b8107875f | ||
|
|
ca606570f7 | ||
|
|
22d094ccc6 | ||
|
|
fd84d84279 | ||
|
|
af2b1e28e3 | ||
|
|
9dda449f48 | ||
|
|
e6d4b32629 | ||
|
|
4de22a08e2 | ||
|
|
a403de46b3 | ||
|
|
ac20f605f6 | ||
|
|
51ecbadb12 | ||
|
|
6a4627b625 | ||
|
|
fd2b8b8792 | ||
|
|
f3d662bac7 | ||
|
|
553cc8ef87 | ||
|
|
67fdf1142b | ||
|
|
02f6e564a6 | ||
|
|
33d14061d6 | ||
|
|
677e755e4e | ||
|
|
11a972cbfb | ||
|
|
1834743a0e | ||
|
|
213f76c66c | ||
|
|
91698404a7 | ||
|
|
27bd38d95e | ||
|
|
ef0e61be1b | ||
|
|
26fb08d7da | ||
|
|
80fa3094b3 | ||
|
|
df931b1e17 | ||
|
|
906294ae9a | ||
|
|
15f69719e4 | ||
|
|
43aa4cfff6 | ||
|
|
6901227390 | ||
|
|
7f718feaf6 | ||
|
|
236ffbb24e | ||
|
|
0e300f4c50 | ||
|
|
6d82a3daa3 | ||
|
|
c0c01a8e57 | ||
|
|
334755e912 | ||
|
|
c16f955a51 | ||
|
|
c1dc14f5ee | ||
|
|
ec6a70bbd3 | ||
|
|
f236ed895a | ||
|
|
7d70c90196 | ||
|
|
0a04490043 | ||
|
|
dce7ffde6f | ||
|
|
f6bd1ff0e5 | ||
|
|
ac00a06757 | ||
|
|
155cfb3c73 | ||
|
|
126891126a | ||
|
|
547a394be6 | ||
|
|
1a511acead | ||
|
|
5576a0d9ff | ||
|
|
879e9a32d1 | ||
|
|
747fd5c121 | ||
|
|
b4aab7a78e | ||
|
|
de26a995fc | ||
|
|
8418a9ad7b | ||
|
|
27be4ee2fa |
+108
-162
@@ -234,60 +234,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_etcd_fail_https_antietcd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_https:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_interrupted_rebalance:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -360,6 +306,78 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_dump_load:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_dump_load.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_dump_load_32k:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=32k OSD_ARGS="--data_csum_type crc32c --csum_block_size 32k" OFFSET_ARGS="$OSD_ARGS" /root/vitastor/tests/test_dump_load.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_dump_load:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: OLD=1 /root/vitastor/tests/test_dump_load.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_dump_load_old_32k:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=old_32k OLD=1 OSD_ARGS="--data_csum_type crc32c --csum_block_size 32k" OFFSET_ARGS="$OSD_ARGS" /root/vitastor/tests/test_dump_load.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_interrupted_rebalance:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -702,42 +720,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: ENCRYPTED=1 /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_snapshot_chain_enc_gcm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=enc_gcm ENCRYPTED=1 VITASTOR_CFG=',"proto_checksums":"gcm"' /root/vitastor/tests/test_snapshot_chain.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_snapshot_chain:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1296,24 +1278,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_heal_ec_rdma:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 10
|
||||
run: TEST_NAME=ec_rdma RDMA=1 SCHEME=ec /root/vitastor/tests/test_heal.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1332,24 +1296,6 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_checksum_xxhash:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_checksum:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1584,6 +1530,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_resize_last:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_resize_last.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_resize_auto:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -1620,6 +1584,24 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_resize_last:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: OLD=1 /root/vitastor/tests/test_resize_last.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_old_resize_auto:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
@@ -2178,39 +2160,3 @@ jobs:
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
test_write_encrypted_ec:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container: ${{env.TEST_IMAGE}}:${{github.sha}}
|
||||
steps:
|
||||
- name: Run test
|
||||
id: test
|
||||
timeout-minutes: 3
|
||||
run: SCHEME=ec /root/vitastor/tests/test_write_encrypted.sh
|
||||
- name: Print logs
|
||||
if: always() && steps.test.outcome == 'failure'
|
||||
run: |
|
||||
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
|
||||
echo "-------- $i --------"
|
||||
cat $i
|
||||
echo ""
|
||||
done
|
||||
|
||||
|
||||
@@ -38,14 +38,6 @@ for my $line (<>)
|
||||
{
|
||||
$test_name .= '_antietcd';
|
||||
}
|
||||
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
|
||||
{
|
||||
$test_name .= '_https';
|
||||
}
|
||||
elsif ($1 eq 'ENCRYPTED')
|
||||
{
|
||||
$test_name .= '_encrypted';
|
||||
}
|
||||
elsif ($1 eq 'OLD')
|
||||
{
|
||||
$test_name =~ s/^test_/test_old_/s;
|
||||
|
||||
@@ -3,4 +3,3 @@
|
||||
package-lock.json
|
||||
fio
|
||||
qemu
|
||||
node_modules
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.30)
|
||||
|
||||
project(vitastor)
|
||||
|
||||
set(VITASTOR_VERSION "3.0.12")
|
||||
set(VITASTOR_VERSION "3.0.15")
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ Vitastor поддерживает QEMU-драйвер, протоколы UBLK,
|
||||
- [Дисковые параметры OSD](docs/config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](docs/config/osd.ru.md)
|
||||
- [Параметры мониторов](docs/config/monitor.ru.md)
|
||||
- [Безопасность](docs/config/security.ru.md)
|
||||
- [Настройки пулов](docs/config/pool.ru.md)
|
||||
- [Метаданные образов в etcd](docs/config/inode.ru.md)
|
||||
- Использование
|
||||
|
||||
@@ -1,113 +1,183 @@
|
||||
# Vitastor
|
||||
# tromcho.net
|
||||
|
||||
[Читать на русском](README-ru.md)
|
||||
Repository này chứa toàn bộ source code của website **tromcho.net**.
|
||||
|
||||
## The Idea
|
||||
## Giới thiệu
|
||||
|
||||
Make Clustered Block Storage Fast Again.
|
||||
`tromcho.net` là mã nguồn website được tổ chức để phục vụ phát triển, triển khai và vận hành theo quy trình chuẩn trên GitHub.
|
||||
README này đóng vai trò tài liệu khởi đầu cho lập trình viên, DevOps engineer và cộng tác viên khi tiếp cận repository.
|
||||
|
||||
Vitastor is a distributed block, file and object SDS, direct replacement of Ceph RBD, CephFS and RGW,
|
||||
and also internal SDS's of public clouds. However, in contrast to them, Vitastor is fast
|
||||
and simple at the same time. The only thing is it's slightly young :-).
|
||||
## Mục tiêu repository
|
||||
|
||||
Vitastor is architecturally similar to Ceph which means strong consistency,
|
||||
primary-replication, symmetric clustering and automatic data distribution over any
|
||||
number of drives of any size with configurable redundancy (replication or erasure codes/XOR).
|
||||
- Quản lý tập trung toàn bộ source code của website.
|
||||
- Chuẩn hóa quy trình phát triển, review và triển khai.
|
||||
- Tạo nền tảng rõ ràng cho việc CI/CD, kiểm thử và vận hành production.
|
||||
- Hỗ trợ onboarding nhanh cho thành viên mới.
|
||||
|
||||
Vitastor targets primarily SSD and SSD+HDD clusters with at least 10 Gbit/s network,
|
||||
supports TCP and RDMA and may achieve 4 KB read and write latency as low as ~0.1 ms
|
||||
with proper hardware which is ~10 times faster than other popular SDS's like Ceph
|
||||
or internal systems of public clouds.
|
||||
## Cấu trúc thư mục đề xuất
|
||||
|
||||
Vitastor supports QEMU, UBLK, NBD, NFS protocols, OpenStack, OpenNebula, Proxmox, Kubernetes drivers.
|
||||
More drivers may be created easily.
|
||||
```text
|
||||
.
|
||||
├── app/ # Source code ứng dụng chính
|
||||
├── public/ # Static files, images, favicon, robots.txt
|
||||
├── config/ # Cấu hình môi trường, app, service integration
|
||||
├── database/ # Migration, seed, schema
|
||||
├── tests/ # Unit test, integration test, e2e test
|
||||
├── scripts/ # Script hỗ trợ build, deploy, backup, maintenance
|
||||
├── docs/ # Tài liệu kỹ thuật, kiến trúc, quy trình
|
||||
├── .github/ # GitHub Actions, issue template, PR template
|
||||
├── Dockerfile # Build image ứng dụng
|
||||
├── docker-compose.yml # Chạy local/dev bằng container
|
||||
├── .env.example # Biến môi trường mẫu
|
||||
└── README.md
|
||||
```
|
||||
|
||||
Read more details in the documentation. You can start from here: [Quick Start](docs/intro/quickstart.en.md).
|
||||
> Cấu trúc thực tế có thể thay đổi theo framework đang sử dụng.
|
||||
|
||||
## Talks and presentations
|
||||
## Yêu cầu môi trường
|
||||
|
||||
- KuberConf'2025: [video](https://vitastor.io/presentation/kuberconf.webm)
|
||||
- Highload'2025: [video](https://vitastor.io/presentation/hl2025/hl2025.webm),
|
||||
[youtube](https://www.youtube.com/watch?v=0R8MLjFtz7g), presentation
|
||||
([in Russian](https://vitastor.io/presentation/hl2025/), [in English](https://vitastor.io/presentation/hl2025/en.html))
|
||||
- Highload'2022: presentation ([in Russian](https://vitastor.io/presentation/highload/highload.html)),
|
||||
[video](https://vitastor.io/presentation/highload/talk.webm)
|
||||
- DevOpsConf'2021: presentation ([in Russian](https://vitastor.io/presentation/devopsconf/devopsconf.html),
|
||||
[in English](https://vitastor.io/presentation/devopsconf/devopsconf_en.html)),
|
||||
[video](https://vitastor.io/presentation/devopsconf/talk.webm)
|
||||
Tùy theo stack công nghệ của website, môi trường phát triển nên có:
|
||||
|
||||
## Documentation
|
||||
- Git
|
||||
- Docker và Docker Compose
|
||||
- Node.js / PHP / Python / runtime phù hợp với dự án
|
||||
- Make (khuyến nghị)
|
||||
- Truy cập vào file cấu hình môi trường `.env`
|
||||
|
||||
- Introduction
|
||||
- [Quick Start](docs/intro/quickstart.en.md)
|
||||
- [Features](docs/intro/features.en.md)
|
||||
- [Architecture](docs/intro/architecture.en.md)
|
||||
- [Author and license](docs/intro/author.en.md)
|
||||
- Installation
|
||||
- [Packages](docs/installation/packages.en.md)
|
||||
- [Docker](docs/installation/docker.en.md)
|
||||
- [Proxmox](docs/installation/proxmox.en.md)
|
||||
- [OpenNebula](docs/installation/opennebula.en.md)
|
||||
- [OpenStack](docs/installation/openstack.en.md)
|
||||
- [Kubernetes CSI](docs/installation/kubernetes.en.md)
|
||||
- [S3](docs/installation/s3.en.md)
|
||||
- [Building from Source](docs/installation/source.en.md)
|
||||
- Configuration
|
||||
- [Overview](docs/config.en.md)
|
||||
- Parameter Reference
|
||||
- [Common](docs/config/common.en.md)
|
||||
- [Network](docs/config/network.en.md)
|
||||
- [Client](docs/config/client.en.md)
|
||||
- [Global Disk Layout](docs/config/layout-cluster.en.md)
|
||||
- [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
|
||||
- [vitastor-cli](docs/usage/cli.en.md) (command-line interface)
|
||||
- [vitastor-disk](docs/usage/disk.en.md) (disk management tool)
|
||||
- [fio](docs/usage/fio.en.md) for benchmarks
|
||||
- [UBLK](docs/usage/ublk.en.md) for kernel mounts
|
||||
- [NBD](docs/usage/nbd.en.md) - old interface for kernel mounts
|
||||
- [QEMU, qemu-img and VDUSE](docs/usage/qemu.en.md)
|
||||
- [NFS](docs/usage/nfs.en.md) clustered file system and pseudo-FS proxy
|
||||
- [Administration](docs/usage/admin.en.md)
|
||||
- Performance
|
||||
- [Understanding storage performance](docs/performance/understanding.en.md)
|
||||
- [Theoretical performance](docs/performance/theoretical.en.md)
|
||||
- [Example comparison with Ceph](docs/performance/comparison1.en.md)
|
||||
- [Newer benchmark of Vitastor 1.3.1](docs/performance/bench2.en.md)
|
||||
## Bắt đầu nhanh
|
||||
|
||||
## Author and License
|
||||
### 1. Clone repository
|
||||
|
||||
Copyright (c) Vitaliy Filippov (vitalif [at] yourcmc.ru), 2019+
|
||||
```bash
|
||||
git clone https://github.com/<your-org>/tromcho.net.git
|
||||
cd tromcho.net
|
||||
```
|
||||
|
||||
Join Vitastor Telegram Chat: https://t.me/vitastor
|
||||
### 2. Tạo file môi trường
|
||||
|
||||
All server-side code (OSD, Monitor and so on) is licensed under the terms of
|
||||
Vitastor Network Public License 1.1 (VNPL 1.1), a copyleft license based on
|
||||
GNU GPLv3.0 with the additional "Network Interaction" clause which requires
|
||||
opensourcing all programs directly or indirectly interacting with Vitastor
|
||||
through a computer network and expressly designed to be used in conjunction
|
||||
with it ("Proxy Programs"). Proxy Programs may be made public not only under
|
||||
the terms of the same license, but also under the terms of any GPL-Compatible
|
||||
Free Software License, as listed by the Free Software Foundation.
|
||||
This is a stricter copyleft license than the Affero GPL.
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Please note that VNPL doesn't require you to open the code of proprietary
|
||||
software running inside a VM if it's not specially designed to be used with
|
||||
Vitastor.
|
||||
Sau đó cập nhật các biến cấu hình cần thiết trong file `.env`.
|
||||
|
||||
Basically, you can't use the software in a proprietary environment to provide
|
||||
its functionality to users without opensourcing all intermediary components
|
||||
standing between the user and Vitastor or purchasing a commercial license
|
||||
from the author 😀.
|
||||
### 3. Chạy môi trường local
|
||||
|
||||
Client libraries (cluster_client and so on) are dual-licensed under the same
|
||||
VNPL 1.1 and also GNU GPL 2.0 or later to allow for compatibility with GPLed
|
||||
software like QEMU and fio.
|
||||
Nếu dự án dùng Docker:
|
||||
|
||||
You can find the full text of VNPL-1.1 in the file [VNPL-1.1.txt](VNPL-1.1.txt).
|
||||
GPL 2.0 is also included in this repository as [GPL-2.0.txt](GPL-2.0.txt).
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Nếu dự án chạy trực tiếp theo framework, sử dụng lệnh tương ứng của stack hiện tại.
|
||||
|
||||
## Quy trình phát triển
|
||||
|
||||
- Tạo branch mới từ `main` hoặc `develop`.
|
||||
- Đặt tên branch rõ ràng, ví dụ: `feature/homepage-banner`, `fix/login-timeout`.
|
||||
- Commit ngắn gọn, đúng ngữ cảnh thay đổi.
|
||||
- Tạo Pull Request để review trước khi merge.
|
||||
- Không commit file bí mật như `.env`, private key hoặc credential.
|
||||
|
||||
## Quy ước commit
|
||||
|
||||
Khuyến nghị dùng convention sau:
|
||||
|
||||
```text
|
||||
feat: thêm chức năng mới
|
||||
fix: sửa lỗi
|
||||
refactor: tái cấu trúc mã nguồn
|
||||
chore: cập nhật tác vụ phụ trợ
|
||||
ci: thay đổi pipeline CI/CD
|
||||
docs: cập nhật tài liệu
|
||||
test: bổ sung hoặc cập nhật kiểm thử
|
||||
```
|
||||
|
||||
## CI/CD
|
||||
|
||||
Repository nên tích hợp các bước tự động sau:
|
||||
|
||||
- Lint source code
|
||||
- Chạy unit test / integration test
|
||||
- Build artifact hoặc Docker image
|
||||
- Scan bảo mật dependency/container
|
||||
- Deploy tới staging hoặc production theo rule xác định
|
||||
|
||||
Ví dụ vị trí cấu hình pipeline:
|
||||
|
||||
```text
|
||||
.github/workflows/
|
||||
```
|
||||
|
||||
## Biến môi trường
|
||||
|
||||
Không commit file `.env` thật lên GitHub.
|
||||
Nên cung cấp `.env.example` với:
|
||||
|
||||
- Danh sách biến bắt buộc
|
||||
- Giá trị mẫu an toàn
|
||||
- Ghi chú ngắn cho từng biến quan trọng
|
||||
|
||||
Ví dụ:
|
||||
|
||||
```env
|
||||
APP_ENV=local
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_NAME=tromcho
|
||||
DB_USER=user
|
||||
DB_PASSWORD=change_me
|
||||
```
|
||||
|
||||
## Triển khai
|
||||
|
||||
Khuyến nghị tách rõ các môi trường:
|
||||
|
||||
- local
|
||||
- development
|
||||
- staging
|
||||
- production
|
||||
|
||||
Các thành phần nên được chuẩn hóa khi triển khai:
|
||||
|
||||
- Biến môi trường
|
||||
- Reverse proxy / web server
|
||||
- TLS certificate
|
||||
- Database migration
|
||||
- Backup strategy
|
||||
- Log rotation và monitoring
|
||||
|
||||
## Bảo mật
|
||||
|
||||
- Không đưa secrets vào source code.
|
||||
- Bật branch protection cho nhánh quan trọng.
|
||||
- Review dependency định kỳ.
|
||||
- Áp dụng nguyên tắc least privilege cho tài khoản deploy.
|
||||
- Theo dõi log, audit và cảnh báo bất thường.
|
||||
|
||||
## Đóng góp
|
||||
|
||||
Khi đóng góp vào repository:
|
||||
|
||||
1. Fork hoặc tạo branch làm việc.
|
||||
2. Cập nhật mã nguồn theo phạm vi thay đổi.
|
||||
3. Kiểm tra local trước khi tạo Pull Request.
|
||||
4. Viết mô tả PR rõ ràng: mục tiêu, phạm vi ảnh hưởng, cách kiểm thử.
|
||||
|
||||
## Tài liệu nên bổ sung
|
||||
|
||||
Repository này nên có thêm các tài liệu sau trong thư mục `docs/`:
|
||||
|
||||
- Kiến trúc hệ thống
|
||||
- Sơ đồ database
|
||||
- Luồng deploy
|
||||
- Quy trình backup/restore
|
||||
- Hướng dẫn xử lý sự cố
|
||||
- Checklist release
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2026 Trộm Chó chấm Nét
|
||||
---
|
||||
|
||||
+1
-1
Submodule cpp-btree updated: ebe44c9b66...431d2e1d35
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.12
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ spec:
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
allowPrivilegeEscalation: true
|
||||
image: vitalif/vitastor-csi:v3.0.12
|
||||
image: vitalif/vitastor-csi:v3.0.15
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
@@ -121,7 +121,7 @@ spec:
|
||||
privileged: true
|
||||
capabilities:
|
||||
add: ["SYS_ADMIN"]
|
||||
image: vitalif/vitastor-csi:v3.0.12
|
||||
image: vitalif/vitastor-csi:v3.0.15
|
||||
args:
|
||||
- "--node=$(NODE_ID)"
|
||||
- "--endpoint=$(CSI_ENDPOINT)"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ package vitastor
|
||||
|
||||
const (
|
||||
vitastorCSIDriverName = "csi.vitastor.io"
|
||||
vitastorCSIDriverVersion = "3.0.12"
|
||||
vitastorCSIDriverVersion = "3.0.15"
|
||||
)
|
||||
|
||||
// Config struct fills the parameters of request or user input
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
vitastor (3.0.12-1) unstable; urgency=medium
|
||||
vitastor (3.0.15-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, libc-ares-dev,
|
||||
linux-libc-dev, libgoogle-perftools-dev, libjerasure-dev, libgf-complete-dev,
|
||||
libibverbs-dev, librdmacm-dev, libisal-dev, cmake, pkg-config, libnl-3-dev, libnl-genl-3-dev,
|
||||
node-bindings <!nocheck>, node-gyp, node-nan
|
||||
Standards-Version: 4.5.0
|
||||
|
||||
Vendored
+1
@@ -11,6 +11,7 @@ override_dh_install:
|
||||
cp -v node-binding/package.json node-binding/index.js node-binding/addon.cc node-binding/addon.h node-binding/client.cc node-binding/client.h debian/tmp/usr/lib/x86_64-linux-gnu/nodejs/vitastor
|
||||
cp -v node-binding/build/Release/addon.node debian/tmp/usr/lib/x86_64-linux-gnu/nodejs/vitastor/build/Release
|
||||
dh_install
|
||||
cd debian/vitastor-mon/usr/lib/vitastor/mon && npm install --production
|
||||
|
||||
override_dh_installdeb:
|
||||
cat debian/fio_version >> debian/vitastor-fio.substvars
|
||||
|
||||
Vendored
-6
@@ -37,12 +37,6 @@ rm -rf a b
|
||||
|
||||
echo "dep:fio=$FIO" > debian/fio_version
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL/vitastor-$VER
|
||||
mkdir mon/node_modules
|
||||
cd mon/node_modules
|
||||
curl -s https://git.yourcmc.ru/vitalif/antietcd/archive/master.tar.gz | tar -zx
|
||||
curl -s https://git.yourcmc.ru/vitalif/tinyraft/archive/master.tar.gz | tar -zx
|
||||
|
||||
cd /root/vitastor/packages/vitastor-$REL
|
||||
if [[ ( "$REL" = "trixie" || "$REL" = "resolute" ) && -e ../vitastor-bookworm/vitastor_$VER.orig.tar.xz ]]; then
|
||||
# Fucking shit, archives differ between bookworm (xz 5.4.1) and trixie (xz 5.8.1)
|
||||
|
||||
Vendored
+8
-6
@@ -12,18 +12,20 @@ ARG REL=
|
||||
WORKDIR /root
|
||||
|
||||
RUN set -e -x; \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
if [ "$REL" = "buster" ]; then \
|
||||
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
|
||||
apt-get update; \
|
||||
apt-get -y install wget; \
|
||||
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
|
||||
echo "deb https://vitastor.io/debian $REL main" >> /etc/apt/sources.list; \
|
||||
fi; \
|
||||
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
|
||||
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/*.sources || true; \
|
||||
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
|
||||
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
|
||||
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake \
|
||||
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
|
||||
apt-get -y build-dep fio && \
|
||||
apt-get --download-only source fio
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.0.12
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
SyslogIdentifier=etcd
|
||||
ExecStart=bash -c 'docker run --rm -i -v /var/lib/vitastor/etcd:/data \
|
||||
--log-driver none --network host $CONTAINER_OPTIONS --name vitastor-etcd \
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --data-dir /data
|
||||
$ETCD_IMAGE /usr/local/bin/etcd --name "$ETCD_NAME" --data-dir /data \
|
||||
--snapshot-count 10000 --advertise-client-urls http://$ETCD_IP:2379 --listen-client-urls http://$ETCD_IP:2379 \
|
||||
--initial-advertise-peer-urls http://$ETCD_IP:2380 --listen-peer-urls http://$ETCD_IP:2380 \
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster "$ETCD_INITIAL_CLUSTER" \
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision'
|
||||
ExecStop=docker stop vitastor-etcd
|
||||
Restart=always
|
||||
StartLimitInterval=0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Desired Vitastor version
|
||||
VITASTOR_VERSION=v3.0.12
|
||||
VITASTOR_VERSION=v3.0.15
|
||||
|
||||
# Additional arguments for all containers
|
||||
# For example, you may want to specify a custom logging driver here
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.18
|
||||
ETCD_NAME=""
|
||||
ETCD_IP=""
|
||||
ETCD_INITIAL_CLUSTER=""
|
||||
|
||||
@@ -38,4 +38,3 @@ In the future, additional configuration methods may be added:
|
||||
- [OSD Disk Layout](config/layout-osd.en.md)
|
||||
- [OSD Runtime Parameters](config/osd.en.md)
|
||||
- [Monitor](config/monitor.en.md)
|
||||
- [Security Parameters](config/security.en.md)
|
||||
|
||||
@@ -41,4 +41,3 @@
|
||||
- [Дисковые параметры OSD](config/layout-osd.ru.md)
|
||||
- [Прочие параметры OSD](config/osd.ru.md)
|
||||
- [Параметры мониторов](config/monitor.ru.md)
|
||||
- [Параметры безопасности](config/security.ru.md)
|
||||
|
||||
@@ -198,14 +198,8 @@ put a modified value into etcd key /vitastor/config/global.
|
||||
- Type: string
|
||||
- Default: none
|
||||
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
|
||||
## csum_block_size
|
||||
|
||||
|
||||
@@ -209,12 +209,8 @@ journal_block_size и meta_block_size. Однако на данный момен
|
||||
- Тип: строка
|
||||
- Значение по умолчанию: none
|
||||
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
[Documentation](../../README.md#documentation) → [Configuration](../config.en.md) → Security Parameters
|
||||
|
||||
-----
|
||||
|
||||
[Читать на русском](security.ru.md)
|
||||
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
|
||||
- [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.
|
||||
@@ -1,154 +0,0 @@
|
||||
[Документация](../../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,8 +44,6 @@
|
||||
|
||||
{{../../config/monitor.en.md|indent=2}}
|
||||
|
||||
{{../../config/security.en.md|indent=2}}
|
||||
|
||||
{{../../config/pool.en.md|indent=2}}
|
||||
|
||||
{{../../config/inode.en.md|indent=2}}
|
||||
|
||||
@@ -44,8 +44,6 @@
|
||||
|
||||
{{../../config/monitor.ru.md|indent=2}}
|
||||
|
||||
{{../../config/security.ru.md|indent=2}}
|
||||
|
||||
{{../../config/pool.ru.md|indent=2}}
|
||||
|
||||
{{../../config/inode.ru.md|indent=2}}
|
||||
|
||||
@@ -233,21 +233,11 @@
|
||||
type: string
|
||||
default: none
|
||||
info: |
|
||||
Data and metadata checksum type to use. May be "crc32c", "xxh3_32" or "none".
|
||||
Select crc32c or xxh3_32 and set csum_block_size to enable data checksums.
|
||||
|
||||
Both crc32c and xxh3_32 are almost equally fast, xxh3_32 is safer. xxh3_32 is
|
||||
the xxhash3 algorithm truncated from 64 to 32 bits (which is still a good hash).
|
||||
|
||||
Note that enabled data checksums either increase memory usage or reduce
|
||||
performance. Check details in [csum_block_size](#csum_block_size) description.
|
||||
Data checksum type to use. May be "crc32c" or "none". Set to "crc32c" to
|
||||
enable data checksums.
|
||||
info_ru: |
|
||||
Тип используемых OSD контрольных сумм данных и метаданных. Может быть "crc32c",
|
||||
"xxh3_32" или "none". Выберите crc32c или xxh3_32 и установите csum_block_size,
|
||||
чтобы включить контрольные суммы данных.
|
||||
|
||||
И crc32c, и xxh3_32 примерно одинаково быстры, xxh3_32 надёжней. xxh3_32 - это
|
||||
алгоритм xxhash3, обрезанный с 64 до 32 бит (это всё равно хороший хеш).
|
||||
Тип используемых OSD контрольных сумм данных. Может быть "crc32c" или "none".
|
||||
Установите в "crc32c", чтобы включить расчёт и проверку контрольных сумм данных.
|
||||
|
||||
Следует понимать, что контрольные суммы в зависимости от размера блока их
|
||||
расчёта либо увеличивают потребление памяти, либо снижают производительность.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Security Parameters
|
||||
|
||||
These parameters affect your Vitastor installation security and apply to OSDs, monitors and clients.
|
||||
|
||||
Most of them can be set in /etc/vitastor/vitastor.conf and in etcd, but don't support online modification.
|
||||
@@ -1,7 +0,0 @@
|
||||
# Параметры безопасности
|
||||
|
||||
Данные параметры затрагивают безопасность инсталляций Vitastor и используются
|
||||
OSD, мониторами и клиентами.
|
||||
|
||||
Большая их часть может задаваться в /etc/vitastor/vitastor.conf и в etcd, но не
|
||||
поддерживает онлайн-изменение.
|
||||
@@ -1,131 +0,0 @@
|
||||
- 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.12`
|
||||
`docker pull vitalif/vitastor:v3.0.15`
|
||||
2. Install scripts to the host system: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 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.12`
|
||||
`docker pull vitalif/vitastor:v3.0.15`
|
||||
2. Установите скрипты в хост-систему командой: \
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.12 install.sh`
|
||||
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
|
||||
3. Перезагрузите правила udev: \
|
||||
`udevadm control --reload-rules`
|
||||
4. Включите сервис vitastor-host: \
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- gcc and g++ 8 or newer, clang 10 or newer, or other compiler with C++11 plus
|
||||
designated initializers support from C++20
|
||||
- CMake
|
||||
- jerasure, c-ares headers and libraries
|
||||
- jerasure headers and libraries
|
||||
- ISA-L, libibverbs, librdmacm, libnl3 headers and libraries (optional)
|
||||
|
||||
## Basic instructions
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- gcc и g++ >= 8, либо clang >= 10, либо другой компилятор с поддержкой C++11 плюс
|
||||
назначенных инициализаторов (designated initializers) из C++20
|
||||
- CMake
|
||||
- Заголовки и библиотеки jerasure, c-ares
|
||||
- Заголовки и библиотеки jerasure
|
||||
- Опционально - заголовки и библиотеки ISA-L, libibverbs, librdmacm, libnl3
|
||||
|
||||
## Базовая инструкция
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
- [Built-in Prometheus metric exporter](../config/monitor.en.md#enable_prometheus)
|
||||
- [NFS RDMA support](../usage/nfs.en.md#rdma) (probably also usable for GPUDirect)
|
||||
- [S3](../installation/s3.en.md)
|
||||
- [TLS support for etcd connections](../config/security.en.md)
|
||||
- [AES-256-XTS image encryption](../usage/cli.en.md#create) and [Vault support](../config/security.en.md#vault_url) for key storage
|
||||
|
||||
## Plugins and tools
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
- [Встроенный Prometheus-экспортер метрик](../config/monitor.ru.md#enable_prometheus)
|
||||
- [Поддержка NFS RDMA](../usage/nfs.ru.md#rdma) (вероятно, также подходящая для GPUDirect)
|
||||
- [S3](../installation/s3.ru.md)
|
||||
- [Поддержка TLS-соединений с etcd](../config/security.ru.md)
|
||||
- [AES-256-XTS шифрование данных](../usage/cli.ru.md#create) и [поддержка Vault](../config/security.ru.md#vault_url) для хранения ключей
|
||||
|
||||
## Драйверы и инструменты
|
||||
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
[Документация](../../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
|
||||
+7
-21
@@ -125,31 +125,18 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size SIZE [OPTIONS] <name>`
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
|
||||
Create an image. Options:
|
||||
|
||||
* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix.
|
||||
* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool).
|
||||
* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).
|
||||
If parent is not a snapshot, it must be a read-only image.
|
||||
* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image.
|
||||
* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image.
|
||||
* `--enc-key vault:ID` - Use an encryption key from an external Vault secret with specified ID.
|
||||
Create an image. You may use K/M/G/T suffixes for `<size>`. If `--parent` is specified,
|
||||
a copy-on-write image clone is created. Parent must be a snapshot (readonly image).
|
||||
Pool must be specified if there is more than one pool.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [OPTIONS] <image>
|
||||
vitastor-cli snap-create [OPTIONS] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Create a snapshot of image `<image>`. May be used live if only a single writer is active.
|
||||
|
||||
Options:
|
||||
|
||||
* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool.
|
||||
* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key.
|
||||
* `--enc-key KEY` - Change image encryption key to a specified key, Vault key or to an empty key.
|
||||
By default, the image retains its old encryption key when taking a snapshot.
|
||||
Create a snapshot of image `<name>` (either form can be used). May be used live if only a single writer is active.
|
||||
|
||||
See also about [how to export snapshots](qemu.en.md#exporting-snapshots).
|
||||
|
||||
@@ -164,7 +151,6 @@ You should resize file system in the image, if present, before shrinking it.
|
||||
* `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes).
|
||||
* `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children.
|
||||
* `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs.
|
||||
* `--enc-key HEX` - Change image encryption key (allowed only with `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+8
-22
@@ -127,32 +127,19 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0
|
||||
|
||||
## create
|
||||
|
||||
`vitastor-cli create -s|--size SIZE [ОПЦИИ] <name>`
|
||||
`vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>`
|
||||
|
||||
Создать образ. Опции:
|
||||
|
||||
* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт).
|
||||
* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один).
|
||||
* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`.
|
||||
Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения.
|
||||
* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа.
|
||||
* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа.
|
||||
* `--enc-key vault:ID` - Использовать ключ из внешнего секрета с заданным ID из Vault.
|
||||
Создать образ. Для размера `<size>` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт).
|
||||
Если указана опция `--parent`, создаётся клон образа. Родитель `<parent_name>[@<snapshot>]` должен быть
|
||||
снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула.
|
||||
|
||||
```
|
||||
vitastor-cli create --snapshot <snapshot> [ОПЦИИ] <image>
|
||||
vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
|
||||
vitastor-cli create --snapshot <snapshot> [-p|--pool <id|name>] <image>
|
||||
vitastor-cli snap-create [-p|--pool <id|name>] <image>@<snapshot>
|
||||
```
|
||||
|
||||
Создать снимок образа `<image>` (можно использовать любую форму команды).
|
||||
Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного.
|
||||
|
||||
Опции:
|
||||
|
||||
* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле.
|
||||
* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS.
|
||||
* `--enc-key KEY` - Изменить ключ шифрования образа на заданный ключ, ключ из Vault или пустой ключ.
|
||||
По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка.
|
||||
Создать снимок образа `<name>` (можно использовать любую форму команды). Снимок можно создавать без остановки
|
||||
клиентов, если пишущий клиент максимум 1.
|
||||
|
||||
Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков).
|
||||
|
||||
@@ -169,7 +156,6 @@ vitastor-cli snap-create [ОПЦИИ] <image>@<snapshot>
|
||||
* `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении).
|
||||
* `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны.
|
||||
* `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD.
|
||||
* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`).
|
||||
|
||||
## dd
|
||||
|
||||
|
||||
+17
-50
@@ -3,7 +3,6 @@
|
||||
|
||||
const AntiEtcd = require('antietcd');
|
||||
|
||||
const vitastor_auth_filter = require('./vitastor_auth_filter.js');
|
||||
const vitastor_persist_filter = require('./vitastor_persist_filter.js');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -28,68 +27,36 @@ class AntiEtcdAdapter
|
||||
is_local['::'] = true;
|
||||
is_local[''] = true;
|
||||
// split :, 3 -> <schema>:<//ip>:<port>
|
||||
const cluster_local = cluster.map(s =>
|
||||
const selected = [];
|
||||
for (let i = 0; i < cluster.length; i++)
|
||||
{
|
||||
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));
|
||||
const m = /^(https?:\/\/)?(?:\[(.*)\]|([^\[\:]+))(?::(\d+))?$/.exec(cluster[i]);
|
||||
if (!m)
|
||||
continue;
|
||||
const ip = m[3] || m[2];
|
||||
const port = m[4] || 2379;
|
||||
if (is_local[ip] && (!cfg_port || port == cfg_port))
|
||||
selected.push({ idx: i, ip, port });
|
||||
}
|
||||
if (selected.length > 1)
|
||||
{
|
||||
console.error('More than 1 etcd_address matches local IPs: '+(selected.join(', '))+', please specify port');
|
||||
console.error('More than 1 etcd_address matches local IPs, please specify port');
|
||||
process.exit(1);
|
||||
}
|
||||
else if (selected.length == 1)
|
||||
{
|
||||
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'),
|
||||
ip: selected[0].ip,
|
||||
port: selected[0].port,
|
||||
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0].port+'.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
|
||||
node_id: cluster[selected[0].idx].replace(/^(https?:\/\/)/, ''), // same as in <cluster> below
|
||||
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
|
||||
cluster_key: (config.etcd_prefix || '/vitastor'),
|
||||
stale_read: 1,
|
||||
log_level: 1,
|
||||
logs: { cluster: true },
|
||||
};
|
||||
if (config.etcd_proxy)
|
||||
{
|
||||
// Monitor may use the builtin etcd_proxy mode
|
||||
if (!config.etcd_proxy.urls)
|
||||
{
|
||||
console.error('etcd_proxy.urls are empty');
|
||||
process.exit(1);
|
||||
}
|
||||
antietcd_config.etcd_proxy = config.etcd_proxy.urls;
|
||||
antietcd_config.etcd_cert = config.etcd_proxy.cert;
|
||||
antietcd_config.etcd_key = config.etcd_proxy.key;
|
||||
antietcd_config.etcd_ca = config.etcd_proxy.ca;
|
||||
delete antietcd_config.data;
|
||||
delete antietcd_config.persist_filter;
|
||||
delete antietcd_config.cluster;
|
||||
delete antietcd_config.cluster_key;
|
||||
}
|
||||
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_')
|
||||
@@ -214,7 +181,7 @@ class AntiEtcdAdapter
|
||||
await new Promise(ok => setTimeout(ok, timeout-(Date.now()-prev)));
|
||||
}
|
||||
prev = Date.now();
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body, { user_type: 'mon' });
|
||||
const res = await this.antietcd.api(path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/+/g, '_'), body);
|
||||
if (res.error)
|
||||
{
|
||||
console.error('Failed to query antietcd '+path+' (retry '+retry+'/'+retries+'): '+res.error);
|
||||
|
||||
+6
-27
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const WebSocket = require('ws');
|
||||
const { b64, local_ips } = require('./utils.js');
|
||||
|
||||
@@ -17,30 +15,11 @@ class EtcdAdapter
|
||||
this.ws = null;
|
||||
this.ws_alive = false;
|
||||
this.ws_keepalive_timer = null;
|
||||
this.opts = {};
|
||||
}
|
||||
|
||||
parse_config(config)
|
||||
{
|
||||
this.parse_etcd_addresses(config.etcd_address||config.etcd_url);
|
||||
if (config.mon_etcd_client_cert || config.etcd_client_cert)
|
||||
{
|
||||
this.opts.cert = config.mon_etcd_client_cert || config.etcd_client_cert;
|
||||
if (this.opts.cert.substr(0, 5) != '-----')
|
||||
this.opts.cert = fs.readFileSync(this.opts.cert, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.mon_etcd_client_key || config.etcd_client_key)
|
||||
{
|
||||
this.opts.key = config.mon_etcd_client_key || config.etcd_client_key;
|
||||
if (this.opts.key.substr(0, 5) != '-----')
|
||||
this.opts.key = fs.readFileSync(this.opts.key, { encoding: 'utf-8' });
|
||||
}
|
||||
if (config.etcd_ca)
|
||||
{
|
||||
this.opts.ca = config.etcd_ca;
|
||||
if (this.opts.ca.substr(0, 5) != '-----')
|
||||
this.opts.ca = fs.readFileSync(this.opts.ca, { encoding: 'utf-8' });
|
||||
}
|
||||
}
|
||||
|
||||
parse_etcd_addresses(addrs)
|
||||
@@ -60,7 +39,7 @@ class EtcdAdapter
|
||||
for (let url of addrs)
|
||||
{
|
||||
let scheme = 'http';
|
||||
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
|
||||
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
|
||||
const slash = url.indexOf('/');
|
||||
const colon = url.indexOf(':');
|
||||
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
|
||||
@@ -151,7 +130,7 @@ class EtcdAdapter
|
||||
}
|
||||
ok(false);
|
||||
}, this.mon.config.etcd_mon_timeout);
|
||||
this.ws = new WebSocket(base+'/watch', this.opts);
|
||||
this.ws = new WebSocket(base+'/watch');
|
||||
this.ws_used_url = cur_addr;
|
||||
const fail = () =>
|
||||
{
|
||||
@@ -293,7 +272,7 @@ class EtcdAdapter
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
}
|
||||
const res = await POST(base+path, body, timeout, this.opts);
|
||||
const res = await POST(base+path, body, timeout);
|
||||
if (this.mon.stopped)
|
||||
{
|
||||
throw new Error(MON_STOPPED);
|
||||
@@ -319,7 +298,7 @@ class EtcdAdapter
|
||||
}
|
||||
}
|
||||
|
||||
function POST(url, body, timeout, opts)
|
||||
function POST(url, body, timeout)
|
||||
{
|
||||
return new Promise(ok =>
|
||||
{
|
||||
@@ -331,10 +310,10 @@ function POST(url, body, timeout, opts)
|
||||
req = null;
|
||||
ok({ error: 'timeout' });
|
||||
}, timeout) : null;
|
||||
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
|
||||
let req = http.request(url, { method: 'POST', headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body_text.length,
|
||||
}, ...(opts||{}) }, (res) =>
|
||||
} }, (res) =>
|
||||
{
|
||||
if (!req)
|
||||
{
|
||||
|
||||
+1
-22
@@ -16,7 +16,6 @@ const etcd_allow = new RegExp('^'+[
|
||||
'config/pools',
|
||||
'config/osd/[1-9]\\d*',
|
||||
'config/pgs', // old name
|
||||
'config/user/.*',
|
||||
'pg/config',
|
||||
'config/inode/[1-9]\\d*/[1-9]\\d*',
|
||||
'osd/state/[1-9]\\d*',
|
||||
@@ -46,14 +45,7 @@ const etcd_tree = {
|
||||
config_path: "/etc/vitastor/vitastor.conf",
|
||||
etcd_prefix: "/vitastor",
|
||||
// etcd connection - configurable online
|
||||
etcd_address: "http://10.0.115.10:2379/v3",
|
||||
etcd_client_cert: "",
|
||||
etcd_client_key: "",
|
||||
osd_etcd_client_cert: "",
|
||||
osd_etcd_client_key: "",
|
||||
mon_etcd_client_cert: "",
|
||||
mon_etcd_client_key: "",
|
||||
etcd_ca: "",
|
||||
etcd_address: "10.0.115.10:2379/v3",
|
||||
// mon
|
||||
etcd_mon_ttl: 5, // min: 1
|
||||
etcd_mon_timeout: 1000, // ms. min: 0
|
||||
@@ -209,8 +201,6 @@ const etcd_tree = {
|
||||
primary_affinity_tags?: 'nvme' | [ 'nvme', ... ],
|
||||
// scrub interval
|
||||
scrub_interval?: '30d',
|
||||
// users allowed to create images in this pool
|
||||
creator_group?: '',
|
||||
},
|
||||
...
|
||||
}, */
|
||||
@@ -227,21 +217,10 @@ const etcd_tree = {
|
||||
parent_id?: <inode_t>,
|
||||
readonly?: boolean,
|
||||
deleted?: boolean,
|
||||
enc_key?: string,
|
||||
owner?: string,
|
||||
owner_group?: string,
|
||||
reader_group?: string,
|
||||
}
|
||||
}
|
||||
}, */
|
||||
inode: {},
|
||||
/* user: {
|
||||
<username>: {
|
||||
type: 'osd'|'mon'|'admin'|'client',
|
||||
groups: string[],
|
||||
},
|
||||
}, */
|
||||
user: {},
|
||||
},
|
||||
osd: {
|
||||
state: {
|
||||
|
||||
@@ -112,9 +112,10 @@ function make_cyclic(pgs, parity_space)
|
||||
{
|
||||
if (parity_space > 1)
|
||||
{
|
||||
for (const pg in pgs)
|
||||
for (const id in pgs)
|
||||
{
|
||||
for (let i = 1; i < pg.size; i++)
|
||||
const pg = pgs[id];
|
||||
for (let i = 1; i < pg.length; i++)
|
||||
{
|
||||
const cyclic = [ ...pg.slice(i), ...pg.slice(0, i) ];
|
||||
pgs['pg_'+cyclic.join('_')] = cyclic;
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vitastor-mon",
|
||||
"version": "3.0.12",
|
||||
"version": "3.0.15",
|
||||
"description": "Vitastor SDS monitor service",
|
||||
"main": "mon-main.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "Vitaliy Filippov",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"antietcd": "^1.2.4",
|
||||
"antietcd": "^1.3.1",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"ws": "^7.2.5"
|
||||
},
|
||||
|
||||
+44
-327
@@ -1,278 +1,38 @@
|
||||
#!/usr/bin/node
|
||||
// Simple Vitastor etcd / antietcd / TLS configurator
|
||||
// Simple systemd unit generator for etcd
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: MIT
|
||||
|
||||
// USAGE:
|
||||
// 1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes
|
||||
// 2) Run ./make-etcd.js. It will create the etcd service on one of specified IPs
|
||||
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const 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()
|
||||
{
|
||||
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++)
|
||||
const config_path = process.argv[2] || '/etc/vitastor/vitastor.conf';
|
||||
if (config_path == '-h' || config_path == '--help')
|
||||
{
|
||||
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;
|
||||
}
|
||||
console.log(
|
||||
'Initialize systemd etcd service for Vitastor\n'+
|
||||
'(c) Vitaliy Filippov, 2019+ (MIT)\n'+
|
||||
'\n'+
|
||||
'USAGE:\n'+
|
||||
'1) Put the same etcd_address into /etc/vitastor/vitastor.conf on all monitor nodes\n'+
|
||||
'2) Run '+process.argv[1]+' [config_path]\n'
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
if (!fs.existsSync(config_path))
|
||||
{
|
||||
console.log(config_path+' is missing');
|
||||
process.exit(1);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
{
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
}
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => /^(https?):\/\/(\[[^\]]+\]|[^\[\]\:\/]+)(?::(\d+))?/.exec(s.toLowerCase()))
|
||||
.filter(s => s)
|
||||
.map(s => ({
|
||||
scheme: s[1],
|
||||
addr: s[2].indexOf(':') && s[2][0] != '[' ? '['+s[2]+']' : s[2],
|
||||
ip: s[2][0] == '[' ? s[2].substr(1, s[2].length-2) : s[2],
|
||||
port: s[3],
|
||||
}));
|
||||
const tls = etcds.filter(e => e.scheme === 'https').length > 0;
|
||||
const use_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"))
|
||||
@@ -285,49 +45,30 @@ async configure_etcd()
|
||||
console.log("/etc/systemd/system/etcd.service already exists");
|
||||
process.exit(1);
|
||||
}
|
||||
const etcd_url = etcds[num].scheme + '://' + etcds[num].addr;
|
||||
const options = {
|
||||
name: 'etcd'+etcds[num].ip.replace(/[^0-9a-z_]/ig, '_'),
|
||||
advertise_client_urls: etcd_url+':'+(use_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)
|
||||
const config = JSON.parse(fs.readFileSync(config_path, { encoding: 'utf-8' }));
|
||||
if (!config.etcd_address)
|
||||
{
|
||||
options['cert_file'] = '/etc/vitastor/etcd.crt';
|
||||
options['key_file'] = '/etc/vitastor/etcd.key';
|
||||
if (use_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';
|
||||
}
|
||||
console.log("etcd_address is missing in "+config_path);
|
||||
process.exit(1);
|
||||
}
|
||||
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
|
||||
? fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' })
|
||||
: "";
|
||||
for (const k in options)
|
||||
const etcds = (config.etcd_address instanceof Array ? config.etcd_address : (''+config.etcd_address).split(/,/))
|
||||
.map(s => (''+s).replace(/^https?:\/\/|(:\d+)?(\/.*)?$/g, '').replace(/^\[(.*)\]$/, '$1').toLowerCase());
|
||||
const num = select_local_etcd(etcds);
|
||||
if (num < 0)
|
||||
{
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_'+k.toUpperCase().replace(/-/, '_'), options[k]);
|
||||
console.log('No matching IPs in etcd_address from '+config_path);
|
||||
process.exit(0);
|
||||
}
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
const etcd_url = 'http://' + (etcds[num].indexOf(':') >= 0 ? '['+etcds[num]+']' : etcds[num]);
|
||||
const etcd_name = 'etcd'+etcds[num].replace(/[^0-9a-z_]/ig, '_');
|
||||
const etcd_cluster = etcds.map(e => `etcd${e.replace(/[^0-9a-z_]/ig, '_')}=http://${e.indexOf(':') >= 0 ? '['+e+']' : e}:2380`).join(',');
|
||||
if (in_docker)
|
||||
{
|
||||
let etcd_conf = fs.readFileSync("/etc/vitastor/etcd.conf", { encoding: 'utf-8' });
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_NAME', etcd_name);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_IP', etcds[num]);
|
||||
etcd_conf = replace_env(etcd_conf, 'ETCD_INITIAL_CLUSTER', etcd_cluster);
|
||||
fs.writeFileSync("/etc/vitastor/etcd.conf", etcd_conf);
|
||||
console.log('etcd for Vitastor configured. Run `systemctl enable --now vitastor-etcd` to start etcd');
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -342,8 +83,12 @@ Wants=network-online.target local-fs.target time-sync.target
|
||||
[Service]
|
||||
Restart=always
|
||||
Environment=GOGC=50
|
||||
EnvironmentFile=/etc/vitastor/etcd.conf
|
||||
ExecStart=etcd --data-dir /var/lib/etcd/vitastor
|
||||
ExecStart=etcd --name ${etcd_name} --data-dir /var/lib/etcd/vitastor \\
|
||||
--snapshot-count 10000 --advertise-client-urls ${etcd_url}:2379 --listen-client-urls ${etcd_url}:2379 \\
|
||||
--initial-advertise-peer-urls ${etcd_url}:2380 --listen-peer-urls ${etcd_url}:2380 \\
|
||||
--initial-cluster-token vitastor-etcd-1 --initial-cluster ${etcd_cluster} \\
|
||||
--initial-cluster-state new --max-txn-ops=100000 --max-request-bytes=104857600 \\
|
||||
--auto-compaction-retention=10 --auto-compaction-mode=revision
|
||||
WorkingDirectory=/var/lib/etcd/vitastor
|
||||
ExecStartPre=+chown -R etcd /var/lib/etcd/vitastor
|
||||
User=etcd
|
||||
@@ -361,6 +106,7 @@ WantedBy=multi-user.target
|
||||
// Disable distribution etcd unit and enable our one
|
||||
await system(`systemctl disable --now etcd`);
|
||||
await system(`systemctl enable --now vitastor-etcd`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function replace_env(text, key, value)
|
||||
@@ -373,29 +119,16 @@ function replace_env(text, key, value)
|
||||
function select_local_etcd(etcds)
|
||||
{
|
||||
const ifaces = os.networkInterfaces();
|
||||
const local = {};
|
||||
for (const ifname in ifaces)
|
||||
{
|
||||
for (const iface of ifaces[ifname])
|
||||
{
|
||||
const addr = iface.address;
|
||||
if (iface.family == 'IPv6')
|
||||
local[addr.toLowerCase()] = local['['+addr.toLowerCase()+']'] = true;
|
||||
else
|
||||
local[addr] = true;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
{
|
||||
if (local[etcds[i].addr])
|
||||
return i;
|
||||
}
|
||||
for (let i = 0; i < etcds.length; i++)
|
||||
if (etcds[i] == iface.address.toLowerCase())
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
async function system(cmd)
|
||||
{
|
||||
console.log('Running '+cmd);
|
||||
const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
|
||||
let finish_cb;
|
||||
cp.on('exit', () => finish_cb && finish_cb());
|
||||
@@ -403,19 +136,3 @@ async function system(cmd)
|
||||
await new Promise(ok => finish_cb = ok);
|
||||
return cp.exitCode;
|
||||
}
|
||||
|
||||
async function make_ca(subj, filename)
|
||||
{
|
||||
if (await system("openssl req -days 3650 -x509 -subj '"+subj+"' -addext basicConstraints=critical,CA:TRUE,pathlen:1"+
|
||||
" -new -newkey rsa:4096 -nodes -keyout "+filename+".key -out "+filename+".crt"))
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function make_signed(subj, f, ca, san)
|
||||
{
|
||||
if (await system(`openssl req -subj '${subj}' ${san ? "-addext 'subjectAltName="+san+"'" : ""} -nodes -new -keyout ${f}.key -out ${f}.csr`))
|
||||
process.exit(1);
|
||||
if (await system(`openssl x509 -req -days 3650 -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
|
||||
process.exit(1);
|
||||
fs.unlinkSync(f+".csr");
|
||||
}
|
||||
|
||||
@@ -1,539 +0,0 @@
|
||||
// AntiEtcd authentication filter for Vitastor
|
||||
// (c) Vitaliy Filippov, 2026
|
||||
// License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
|
||||
|
||||
// Permissions are based on:
|
||||
// 1. Users.
|
||||
// Stored in /vitastor/config/user/<username>.
|
||||
// Has 2 properties:
|
||||
// - type, one of: admin, client.
|
||||
// admin has full access to all images and also to cluster config.
|
||||
// client has r/w access to owned images and r/o access to images with reader_group.
|
||||
// - groups, a list of group names the user is included in.
|
||||
// 2. Images.
|
||||
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
|
||||
// - owner (user name)
|
||||
// - owner_group (group name)
|
||||
// - reader_group
|
||||
// 3. Certificates.
|
||||
// - osd, mon use their own trusted certificates.
|
||||
|
||||
const { X509Certificate } = require('node:crypto');
|
||||
|
||||
const static_perms = {
|
||||
invalid: {
|
||||
keys: {},
|
||||
prefixes: {},
|
||||
},
|
||||
osd: {
|
||||
keys: { '/pg/config': false },
|
||||
prefixes: { '/config/': false, '/osd/': true, '/pg/state/': true, '/pg/history/': true, '/pgstats/': true },
|
||||
},
|
||||
mon: {
|
||||
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
|
||||
prefixes: {
|
||||
'/config/': false, '/osd/': false, '/mon/': true, '/pg/history/': true,
|
||||
'/pgstats/': false, '/inode/stats/': true, '/pool/stats/': true,
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
keys: { '/stats': false },
|
||||
prefixes: {
|
||||
'/config/': true, '/osd/': true, '/index/': true, '/pg/history/': true,
|
||||
'/mon/': false, '/pg/': false, '/pgstats/': false, '/inode/stats/': false, '/pool/stats/': false,
|
||||
},
|
||||
},
|
||||
client: {
|
||||
keys: { '/config/global': false, '/config/node_placement': false, '/config/pools': false, '/pg/config': false },
|
||||
prefixes: { '/osd/state/': false, '/pg/state/': false, '/index/maxid/': false },
|
||||
},
|
||||
};
|
||||
|
||||
const api_perms = {
|
||||
osd: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
mon: { lease_grant: true, lease_revoke: true, lease_keepalive: true, maintenance_status: true },
|
||||
admin: { maintenance_status: true },
|
||||
client: { maintenance_status: true },
|
||||
};
|
||||
|
||||
class VitastorAuthFilter
|
||||
{
|
||||
constructor(antietcd)
|
||||
{
|
||||
this.cfg = antietcd.cfg;
|
||||
this.antietcd = antietcd;
|
||||
this.prefix = this.cfg.vitastor_prefix || '/vitastor';
|
||||
this.prefix_parts = this.prefix.split('/');
|
||||
}
|
||||
|
||||
async init()
|
||||
{
|
||||
if (!this.cfg.cert || !this.cfg.key || !this.cfg.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.12",
|
||||
"version": "3.0.15",
|
||||
"description": "Low-level native bindings to Vitastor client library",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
+45
-10
@@ -366,15 +366,38 @@ sub map_volume
|
||||
my $prefix = defined $scfg->{vitastor_prefix} ? $scfg->{vitastor_prefix} : 'pve/';
|
||||
|
||||
my ($vtype, $img_name, $vmid) = $class->parse_volname($volname);
|
||||
my $name = $img_name;
|
||||
my $name = $prefix.$img_name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
|
||||
my $mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
my ($kerneldev) = grep { $mapped->{$_}->{image} eq $prefix.$name } keys %$mapped;
|
||||
return $kerneldev if $kerneldev && -b $kerneldev; # already mapped
|
||||
my ($kerneldev) = grep {
|
||||
$mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name
|
||||
} keys %$mapped;
|
||||
|
||||
$kerneldev = run_cli($scfg, [ 'map', '--image', $prefix.$name ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
return $kerneldev;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
my $size = `/usr/sbin/blockdev --getsize64 $kerneldev`;
|
||||
return $kerneldev if $size && $size > 0;
|
||||
}
|
||||
|
||||
my $map_out = run_cli($scfg, [ 'map', '--image', $name ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
$map_out =~ s/^\s+|\s+$//gso;
|
||||
|
||||
# Wait until the device is started
|
||||
for (my $i = 0; $i < 100; $i++)
|
||||
{
|
||||
$mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
($kerneldev) = grep { $mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name } keys %$mapped;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
{
|
||||
my $size = `/usr/sbin/blockdev --getsize64 $kerneldev`;
|
||||
return $kerneldev if $size && $size > 0;
|
||||
}
|
||||
select(undef, undef, undef, 0.1);
|
||||
}
|
||||
|
||||
die "Failed to map Vitastor image $name via NBD".
|
||||
($map_out ? ", vitastor-nbd map returned '$map_out'" : "")."\n";
|
||||
}
|
||||
|
||||
sub unmap_volume
|
||||
@@ -383,13 +406,19 @@ sub unmap_volume
|
||||
my $prefix = defined $scfg->{vitastor_prefix} ? $scfg->{vitastor_prefix} : 'pve/';
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
$name = $prefix.$name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
|
||||
my $mapped = run_cli($scfg, [ 'ls' ], binary => '/usr/bin/vitastor-nbd');
|
||||
my ($kerneldev) = grep { $mapped->{$_}->{image} eq $prefix.$name } keys %$mapped;
|
||||
if ($kerneldev && -b $kerneldev)
|
||||
|
||||
my @kerneldevs = grep {
|
||||
$mapped->{$_} && $mapped->{$_}->{image} && $mapped->{$_}->{image} eq $name
|
||||
} keys %$mapped;
|
||||
|
||||
for my $kerneldev (@kerneldevs)
|
||||
{
|
||||
run_cli($scfg, [ 'unmap', $kerneldev ], binary => '/usr/bin/vitastor-nbd', json => 0);
|
||||
next if !$kerneldev || !-b $kerneldev;
|
||||
eval { run_cli($scfg, [ 'unmap', $kerneldev ], binary => '/usr/bin/vitastor-nbd', json => 0); };
|
||||
warn "Failed to unmap Vitastor image $name from $kerneldev: $@" if $@;
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -405,7 +434,13 @@ sub activate_volume
|
||||
sub deactivate_volume
|
||||
{
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
$class->unmap_volume($storeid, $scfg, $volname, $snapname) if $scfg->{vitastor_nbd};
|
||||
|
||||
# Even with vitastor_nbd=0, Proxmox may call map_volume() for special
|
||||
# volumes like tpmstate0 because swtpm needs a local file/block path.
|
||||
# Therefore, always try to unmap an existing NBD mapping here.
|
||||
# unmap_volume() is a no-op if the volume is not currently mapped.
|
||||
$class->unmap_volume($storeid, $scfg, $volname, $snapname);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from cinder.volume import configuration
|
||||
from cinder.volume import driver
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
VITASTOR_VERSION = '3.0.12'
|
||||
VITASTOR_VERSION = '3.0.15'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.12
|
||||
Version: 3.0.15
|
||||
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.12.el10.tar.gz
|
||||
Source0: vitastor-3.0.15.el10.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,7 +16,6 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -15,7 +15,7 @@ RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils
|
||||
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
|
||||
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
|
||||
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel
|
||||
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.12
|
||||
Version: 3.0.15
|
||||
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.12.el7.tar.gz
|
||||
Source0: vitastor-3.0.15.el7.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: devtoolset-9-gcc-c++
|
||||
@@ -17,7 +17,6 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake3
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -13,7 +13,7 @@ RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugi
|
||||
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
|
||||
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
|
||||
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.12
|
||||
Version: 3.0.15
|
||||
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.12.el8.tar.gz
|
||||
Source0: vitastor-3.0.15.el8.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-toolset-9-gcc-c++
|
||||
@@ -16,7 +16,6 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
@@ -10,7 +10,7 @@ WORKDIR /root
|
||||
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
|
||||
RUN dnf -y install epel-release dnf-plugins-core
|
||||
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
|
||||
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel
|
||||
RUN dnf download --source fio
|
||||
RUN rpm --nomd5 -i fio*.src.rpm
|
||||
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: vitastor
|
||||
Version: 3.0.12
|
||||
Version: 3.0.15
|
||||
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.12.el9.tar.gz
|
||||
Source0: vitastor-3.0.15.el9.tar.gz
|
||||
|
||||
BuildRequires: gperftools-devel
|
||||
BuildRequires: gcc-c++
|
||||
@@ -16,7 +16,6 @@ BuildRequires: gf-complete-devel
|
||||
BuildRequires: rdma-core-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: libnl3-devel
|
||||
BuildRequires: c-ares-devel
|
||||
Requires: vitastor-osd = %{version}-%{release}
|
||||
Requires: vitastor-mon = %{version}-%{release}
|
||||
Requires: vitastor-client = %{version}-%{release}
|
||||
|
||||
+1
-11
@@ -20,7 +20,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
|
||||
endif()
|
||||
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
|
||||
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.12")
|
||||
add_definitions(-DVITASTOR_VERSION="3.0.15")
|
||||
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
|
||||
add_link_options(-fno-omit-frame-pointer)
|
||||
if (${WITH_ASAN})
|
||||
@@ -69,21 +69,11 @@ pkg_check_modules(ISAL libisal)
|
||||
if (ISAL_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL)
|
||||
endif (ISAL_LIBRARIES)
|
||||
pkg_check_modules(ISAL_CRYPTO libisal_crypto)
|
||||
if (ISAL_CRYPTO_LIBRARIES)
|
||||
add_definitions(-DWITH_ISAL_CRYPTO)
|
||||
endif (ISAL_CRYPTO_LIBRARIES)
|
||||
pkg_check_modules(RDMACM librdmacm)
|
||||
if (RDMACM_LIBRARIES)
|
||||
add_definitions(-DWITH_RDMACM)
|
||||
endif (RDMACM_LIBRARIES)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(-DWITH_OPENSSL)
|
||||
|
||||
pkg_check_modules(CARES REQUIRED libcares)
|
||||
include_directories(${CARES_INCLUDE_DIRS})
|
||||
|
||||
if (${WITH_SYSTEM_LIBURING})
|
||||
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
|
||||
include_directories(${LIBURING_INCLUDE_DIRS})
|
||||
|
||||
@@ -83,17 +83,13 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
|
||||
}
|
||||
else if (config["data_csum_type"] == "xxh3_32")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
|
||||
}
|
||||
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
|
||||
{
|
||||
data_csum_type = BLOCKSTORE_CSUM_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
|
||||
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
|
||||
}
|
||||
csum_block_size = parse_size(config["csum_block_size"]);
|
||||
discard_on_start = config.find("discard_on_start") != config.end() &&
|
||||
@@ -521,7 +517,7 @@ void blockstore_disk_t::close_all()
|
||||
|
||||
// Sadly DISCARD only works through ioctl(), but it seems to always block the device queue,
|
||||
// so it's not a big deal that we can only run it synchronously.
|
||||
int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_free)
|
||||
int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_used)
|
||||
{
|
||||
if (mock_mode)
|
||||
{
|
||||
@@ -532,7 +528,7 @@ int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_free)
|
||||
uint64_t discarded = 0;
|
||||
for (; i <= block_count; i++)
|
||||
{
|
||||
if (i >= block_count || is_free(i))
|
||||
if (i >= block_count || is_used(i))
|
||||
{
|
||||
if (i > j && (i-j)*data_block_size >= min_discard_size)
|
||||
{
|
||||
@@ -545,17 +541,21 @@ int blockstore_disk_t::trim_data(std::function<bool(uint64_t)> is_free)
|
||||
if (range[0] % discard_granularity)
|
||||
range[0] = range[0] + discard_granularity - (range[0] % discard_granularity);
|
||||
if (range[0] >= range[1])
|
||||
continue;
|
||||
range[1] -= range[0];
|
||||
range[1] = 0;
|
||||
else
|
||||
range[1] -= range[0];
|
||||
}
|
||||
r = ioctl(data_fd, BLKDISCARD, &range);
|
||||
if (r != 0)
|
||||
if (range[1] > 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to execute BLKDISCARD %ju+%ju on %s: %s (code %d)\n",
|
||||
range[0], range[1], data_device.c_str(), strerror(-r), r);
|
||||
return -errno;
|
||||
r = ioctl(data_fd, BLKDISCARD, &range);
|
||||
if (r != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to execute BLKDISCARD %ju+%ju on %s: %s (code %d)\n",
|
||||
range[0], range[1], data_device.c_str(), strerror(-r), r);
|
||||
return -errno;
|
||||
}
|
||||
discarded += range[1];
|
||||
}
|
||||
discarded += range[1];
|
||||
}
|
||||
j = i+1;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#define BLOCKSTORE_CSUM_NONE 0
|
||||
// Lower byte of checksum type is its length
|
||||
#define BLOCKSTORE_CSUM_CRC32C 0x104
|
||||
#define BLOCKSTORE_CSUM_XXH3_32 0x204
|
||||
|
||||
#define MOCK_DATA_FD 1000
|
||||
#define MOCK_META_FD 1001
|
||||
@@ -84,7 +83,7 @@ struct blockstore_disk_t
|
||||
void calc_lengths(bool skip_meta_check = false);
|
||||
void check_lengths();
|
||||
void close_all();
|
||||
int trim_data(std::function<bool(uint64_t)> is_free);
|
||||
int trim_data(std::function<bool(uint64_t)> is_used);
|
||||
|
||||
inline uint64_t dirty_dyn_size(uint64_t offset, uint64_t len)
|
||||
{
|
||||
|
||||
@@ -289,30 +289,41 @@ resume_1:
|
||||
{
|
||||
init_fsync_data();
|
||||
}
|
||||
if (bs->log_level > 10)
|
||||
if (compact_info.do_delete)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
memcpy(new_ext_bmp, compact_info.clean_wr->get_ext_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
bitmap_copied = true;
|
||||
}
|
||||
if (bs->dsk.csum_block_size && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
memcpy(new_csums, compact_info.clean_wr->get_checksums(bs->heap), bs->dsk.data_block_size/bs->dsk.csum_block_size * (bs->dsk.data_csum_type & 0xFF));
|
||||
for (size_t i = csum_copy.size(); i > 0; i--)
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
auto wr = csum_copy[i-1];
|
||||
memcpy(new_csums + wr->small().offset/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF),
|
||||
wr->get_checksums(bs->heap), wr->small().len/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF));
|
||||
printf("Compacting %jx:%jx up to l%ju (delete)\n", cur_oid.inode, cur_oid.stripe, compact_info.compact_lsn);
|
||||
}
|
||||
csum_copy.clear();
|
||||
clean_loc = UINT64_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bs->log_level > 10)
|
||||
{
|
||||
printf("Compacting %jx:%jx v%ju..v%ju / l%ju..l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe,
|
||||
compact_info.clean_wr->version, compact_info.compact_version,
|
||||
compact_info.clean_wr->lsn, compact_info.compact_lsn, copy_count);
|
||||
}
|
||||
mem_or(new_bmp, compact_info.clean_wr->get_int_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
if (!bitmap_copied)
|
||||
{
|
||||
memcpy(new_ext_bmp, compact_info.clean_wr->get_ext_bitmap(bs->heap), bs->dsk.clean_entry_bitmap_size);
|
||||
bitmap_copied = true;
|
||||
}
|
||||
if (bs->dsk.csum_block_size && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
{
|
||||
memcpy(new_csums, compact_info.clean_wr->get_checksums(bs->heap), bs->dsk.data_block_size/bs->dsk.csum_block_size * (bs->dsk.data_csum_type & 0xFF));
|
||||
for (size_t i = csum_copy.size(); i > 0; i--)
|
||||
{
|
||||
auto wr = csum_copy[i-1];
|
||||
memcpy(new_csums + wr->small().offset/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF),
|
||||
wr->get_checksums(bs->heap), wr->small().len/bs->dsk.csum_block_size*(bs->dsk.data_csum_type & 0xFF));
|
||||
}
|
||||
csum_copy.clear();
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
}
|
||||
clean_loc = compact_info.clean_wr->big_location(bs->heap);
|
||||
overwrite_start = overwrite_end = 0;
|
||||
if (read_vec.size() > 0)
|
||||
{
|
||||
@@ -625,7 +636,7 @@ int journal_flusher_co::check_and_punch_checksums()
|
||||
|
||||
bool journal_flusher_co::calc_block_checksums()
|
||||
{
|
||||
if (bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
|
||||
if (bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity || compact_info.do_delete)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "blockstore_heap.h"
|
||||
#include "../util/allocator.h"
|
||||
#include "../util/crc32c.h"
|
||||
#include "../util/xxh_x86dispatch.h"
|
||||
#include "../util/malloc_or_die.h"
|
||||
|
||||
#define BS_HEAP_FREE_MVCC 1
|
||||
@@ -23,7 +22,7 @@
|
||||
|
||||
#define HEAP_INFLIGHT_DONE 1
|
||||
#define HEAP_INFLIGHT_COMPACTABLE 2
|
||||
#define HEAP_INFLIGHT_COMPACTED 4
|
||||
#define HEAP_INFLIGHT_OVERWRITE 4
|
||||
#define HEAP_INFLIGHT_GC 8
|
||||
#define HEAP_INFLIGHT_EXPLICIT 16
|
||||
|
||||
@@ -66,19 +65,19 @@ uint32_t blockstore_heap_t::get_simple_entry_size()
|
||||
uint32_t blockstore_heap_t::get_big_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_write_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_big_intent_entry_size()
|
||||
{
|
||||
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
|
||||
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
|
||||
}
|
||||
|
||||
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
|
||||
{
|
||||
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
|
||||
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
|
||||
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
|
||||
}
|
||||
|
||||
@@ -93,7 +92,7 @@ uint32_t blockstore_heap_t::get_csum_size(heap_entry_t *wr)
|
||||
|
||||
uint32_t blockstore_heap_t::get_csum_size(uint32_t entry_type, uint32_t offset, uint32_t len)
|
||||
{
|
||||
if (!dsk->csum_block_size)
|
||||
if (!dsk->data_csum_type)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -123,6 +122,8 @@ uint32_t heap_entry_t::get_size(blockstore_heap_t *heap)
|
||||
}
|
||||
if (type() == BS_HEAP_SMALL_WRITE || type() == BS_HEAP_INTENT_WRITE)
|
||||
{
|
||||
if (size < sizeof(heap_small_write_t))
|
||||
return heap->get_small_entry_size(0, 0);
|
||||
return heap->get_small_entry_size(small().offset, small().len);
|
||||
}
|
||||
return heap->get_simple_entry_size();
|
||||
@@ -216,24 +217,15 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
|
||||
big().block_num = location / heap->dsk->data_block_size;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
|
||||
uint32_t heap_entry_t::calc_crc32c()
|
||||
{
|
||||
auto old_checksum = checksum;
|
||||
checksum = 0;
|
||||
uint32_t res = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
res = (uint32_t)XXH3_64bits(this, size);
|
||||
else
|
||||
res = ::crc32c(0, (uint8_t*)this, size);
|
||||
checksum = old_checksum;
|
||||
auto old_crc32c = crc32c;
|
||||
crc32c = 0;
|
||||
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
|
||||
crc32c = old_crc32c;
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
|
||||
{
|
||||
return calc_checksum(heap->dsk);
|
||||
}
|
||||
|
||||
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
|
||||
{
|
||||
uint64_t pg_num = 0;
|
||||
@@ -374,14 +366,11 @@ corrupted_object:
|
||||
return EDOM;
|
||||
}
|
||||
}
|
||||
if (((wr->entry_type & BS_HEAP_TYPE) == BS_HEAP_SMALL_WRITE ||
|
||||
(wr->entry_type & BS_HEAP_TYPE) == BS_HEAP_INTENT_WRITE) &&
|
||||
wr->size < sizeof(heap_small_write_t))
|
||||
if (wr->size != wr->get_size(this))
|
||||
{
|
||||
// Small writes require accessing offset & len to calculate correct length,
|
||||
// so require at least sizeof(heap_small_write_t) for them
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u < min %zu bytes)\n",
|
||||
wr->inode, wr->stripe, wr->version, block_num, block_offset, wr->size, sizeof(heap_small_write_t));
|
||||
// Check entry size
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju has invalid size in metadata block %u at %u (%u != %u bytes)\n",
|
||||
wr->inode, wr->stripe, wr->version, block_num, block_offset, wr->size, wr->get_size(this));
|
||||
goto corrupted_object;
|
||||
}
|
||||
if (wr->entry_type == BS_HEAP_COMMIT && !wr->version)
|
||||
@@ -391,12 +380,12 @@ corrupted_object:
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify crc
|
||||
uint32_t expected_checksum = wr->calc_checksum(this);
|
||||
if (wr->checksum != expected_checksum)
|
||||
uint32_t expected_crc32c = wr->calc_crc32c();
|
||||
if (wr->crc32c != expected_crc32c)
|
||||
{
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
|
||||
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). ",
|
||||
wr->inode, wr->stripe, wr->version, wr->lsn,
|
||||
block_num, block_offset, expected_checksum, wr->checksum);
|
||||
block_num, block_offset, expected_crc32c, wr->crc32c);
|
||||
goto corrupted_object;
|
||||
}
|
||||
// Verify offset & len
|
||||
@@ -570,7 +559,7 @@ void blockstore_heap_t::finish_load()
|
||||
size_t s = 0, e, n = postponed_items.size();
|
||||
for (e = 1; e <= n; e++)
|
||||
{
|
||||
if (e >= n || postponed_items[e]->entry.inode != postponed_items[s]->entry.inode &&
|
||||
if (e >= n || postponed_items[e]->entry.inode != postponed_items[s]->entry.inode ||
|
||||
postponed_items[e]->entry.stripe != postponed_items[s]->entry.stripe)
|
||||
{
|
||||
insert_list_items(postponed_items.data()+s, e-s, false);
|
||||
@@ -693,10 +682,14 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
}
|
||||
use_data(wr->inode, wr->big_location(this));
|
||||
}
|
||||
if (wr->is_compactable() && !added)
|
||||
if (wr->is_compactable())
|
||||
{
|
||||
compact_queue.push_back((object_id){ .inode = wr->inode, .stripe = wr->stripe });
|
||||
added = true;
|
||||
to_compact_count++;
|
||||
if (!added)
|
||||
{
|
||||
compact_queue.push_back((object_id){ .inode = wr->inode, .stripe = wr->stripe });
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if (wr->is_overwrite())
|
||||
{
|
||||
@@ -706,6 +699,11 @@ int blockstore_heap_t::mark_used_blocks()
|
||||
});
|
||||
}
|
||||
}
|
||||
for (auto li: init_erase_items)
|
||||
{
|
||||
unlink_list_item(li);
|
||||
}
|
||||
init_erase_items.clear();
|
||||
if (dsk->gc_on_start)
|
||||
{
|
||||
recheck_full_gc();
|
||||
@@ -741,7 +739,6 @@ void blockstore_heap_t::init_erase_bad_entry(heap_list_item_t *li)
|
||||
inf.garbage_space -= (li->entry.is_garbage() ? li->entry.size : 0);
|
||||
});
|
||||
recheck_modified_blocks.insert(li->block_num);
|
||||
unlink_list_item(li);
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_list_item_t *cur_li)
|
||||
@@ -796,6 +793,8 @@ bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_
|
||||
overwritten = erase_li->entry.is_overwrite();
|
||||
}
|
||||
init_erase_bad_entry(erase_li);
|
||||
// Can't erase (mutate map) while iterating, so postpone it
|
||||
init_erase_items.push_back(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
@@ -809,6 +808,8 @@ bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_
|
||||
auto next_erase_li = erase_li->next;
|
||||
init_free_bad_entry(&erase_li->entry);
|
||||
init_erase_bad_entry(erase_li);
|
||||
// Can't erase (mutate map) while iterating, so postpone it
|
||||
init_erase_items.push_back(erase_li);
|
||||
erase_li = next_erase_li;
|
||||
}
|
||||
erase_li = cur_li;
|
||||
@@ -817,6 +818,8 @@ bool blockstore_heap_t::init_erase_double_claim(heap_list_item_t *prev_li, heap_
|
||||
{
|
||||
auto prev_erase_li = erase_li->prev;
|
||||
init_erase_bad_entry(erase_li);
|
||||
// Can't erase (mutate map) while iterating, so postpone it
|
||||
init_erase_items.push_back(erase_li);
|
||||
erase_li = prev_erase_li;
|
||||
}
|
||||
}
|
||||
@@ -887,6 +890,7 @@ void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *ba
|
||||
auto prev = li->prev;
|
||||
assert(li->entry.type() == bad_wr->type());
|
||||
init_erase_bad_entry(li);
|
||||
unlink_list_item(li);
|
||||
li = prev;
|
||||
}
|
||||
}
|
||||
@@ -1079,11 +1083,7 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
|
||||
len = wr->big_intent().len;
|
||||
else
|
||||
assert(0);
|
||||
uint32_t real_csum = 0;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
real_csum = (uint32_t)XXH3_64bits(data, len);
|
||||
else
|
||||
real_csum = crc32c(0, data, len);
|
||||
uint32_t real_csum = crc32c(0, data, len);
|
||||
if (set)
|
||||
{
|
||||
*wr_csum = real_csum;
|
||||
@@ -1133,26 +1133,11 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
|
||||
return prev_crc;
|
||||
}
|
||||
|
||||
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
|
||||
{
|
||||
uint32_t cur_len = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
uint8_t *data = next(pos, cur_len);
|
||||
assert(data);
|
||||
cur_len = (cur_len < size ? cur_len : size);
|
||||
XXH3_64bits_update(xxh3_state, data, cur_len);
|
||||
pos += cur_len;
|
||||
size -= cur_len;
|
||||
}
|
||||
}
|
||||
|
||||
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
|
||||
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
|
||||
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
|
||||
{
|
||||
bool res = true;
|
||||
XXH3_state_t* xxh3_state = NULL;
|
||||
uint32_t pos = start;
|
||||
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
|
||||
uint32_t block_crc = 0;
|
||||
@@ -1168,90 +1153,43 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
|
||||
while (pos < end && pos < block_end && !(bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
// zero padding at the beginning or at the end of the block is not counted
|
||||
if (pos > prev && prev > 0 && pos < block_end)
|
||||
{
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
uint32_t zeropad = pos-prev;
|
||||
while (zeropad > 0)
|
||||
{
|
||||
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
|
||||
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
|
||||
zeropad -= zerolen;
|
||||
}
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
}
|
||||
if (pos > prev && prev > blk_start && pos < block_end)
|
||||
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
|
||||
prev = pos;
|
||||
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
|
||||
pos += dsk->bitmap_granularity;
|
||||
if (pos > prev)
|
||||
{
|
||||
isset = true;
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, prev, pos-prev);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
|
||||
}
|
||||
prev = pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
|
||||
{
|
||||
if (!xxh3_state)
|
||||
{
|
||||
xxh3_state = XXH3_createState();
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
}
|
||||
else
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
|
||||
pos = (end > block_end ? block_end : end);
|
||||
isset = true;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_64bits_reset(xxh3_state);
|
||||
}
|
||||
if (set)
|
||||
{
|
||||
*block_csums = block_crc;
|
||||
}
|
||||
else if (isset && block_crc != *block_csums)
|
||||
{
|
||||
res = false;
|
||||
if (bad_block_cb)
|
||||
{
|
||||
bad_block_cb(blk_start, *block_csums, block_crc);
|
||||
res = false;
|
||||
}
|
||||
else
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
block_end += dsk->csum_block_size;
|
||||
block_crc = 0;
|
||||
block_csums++;
|
||||
}
|
||||
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
|
||||
{
|
||||
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
|
||||
XXH3_freeState(xxh3_state);
|
||||
xxh3_state = NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1613,12 +1551,12 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
|
||||
// Remember the object as dirty and remove older entries when this block is written and fsynced
|
||||
push_inflight_lsn(next_lsn, new_wr,
|
||||
(explicit_complete ? HEAP_INFLIGHT_EXPLICIT : 0) |
|
||||
(new_wr->is_overwrite() ? HEAP_INFLIGHT_COMPACTED : 0) |
|
||||
(new_wr->is_overwrite() ? HEAP_INFLIGHT_OVERWRITE : 0) |
|
||||
(new_wr->is_compactable() ? HEAP_INFLIGHT_COMPACTABLE : 0));
|
||||
insert_list_items(&li, 1, false);
|
||||
li->block_num = block_num;
|
||||
new_wr->size = wr_size;
|
||||
new_wr->checksum = new_wr->calc_checksum(this);
|
||||
new_wr->crc32c = new_wr->calc_crc32c();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1647,10 +1585,22 @@ int blockstore_heap_t::add_small_write(object_id oid, heap_entry_t **obj_ptr, ui
|
||||
wr->small().location = location;
|
||||
if (bitmap)
|
||||
memcpy(wr->get_ext_bitmap(this), bitmap, dsk->clean_entry_bitmap_size);
|
||||
else if (obj)
|
||||
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
else
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
{
|
||||
bool found = false;
|
||||
iterate_with_stable(obj, UINT64_MAX, [&](heap_entry_t *old_wr, bool stable)
|
||||
{
|
||||
if (old_wr->get_ext_bitmap(this))
|
||||
{
|
||||
found = true;
|
||||
memcpy(wr->get_ext_bitmap(this), old_wr->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!found)
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
}
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
*obj_ptr = wr;
|
||||
});
|
||||
@@ -1678,7 +1628,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->csum_block_size)
|
||||
if (dsk->data_csum_type)
|
||||
{
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true, offset, len);
|
||||
@@ -1707,7 +1657,7 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
|
||||
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->csum_block_size)
|
||||
if (dsk->data_csum_type)
|
||||
memset(wr->get_checksums(this), 0, get_csum_size(wr));
|
||||
calc_checksums(wr, (uint8_t*)data, true);
|
||||
*obj_ptr = wr;
|
||||
@@ -1745,7 +1695,7 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
|
||||
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
|
||||
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
|
||||
if (dsk->csum_block_size)
|
||||
if (dsk->data_csum_type)
|
||||
{
|
||||
if (checksums)
|
||||
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
|
||||
@@ -1792,7 +1742,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
|
||||
new_wr->set_big_location(this, compact_location);
|
||||
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
|
||||
if (dsk->csum_block_size && new_csums)
|
||||
if (dsk->data_csum_type && new_csums)
|
||||
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
});
|
||||
}
|
||||
@@ -1811,7 +1761,7 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
|
||||
*modified_block = block_num;
|
||||
memcpy(wr->get_int_bitmap(this), new_bitmap, dsk->clean_entry_bitmap_size);
|
||||
memcpy(wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
|
||||
wr->checksum = wr->calc_checksum(dsk);
|
||||
wr->crc32c = wr->calc_crc32c();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2114,7 +2064,10 @@ void blockstore_heap_t::iterate_with_stable(heap_entry_t *obj, uint64_t max_lsn,
|
||||
{
|
||||
if (old_wr->type() == BS_HEAP_ROLLBACK)
|
||||
{
|
||||
rollback_version = old_wr->version;
|
||||
if (rollback_version > old_wr->version)
|
||||
{
|
||||
rollback_version = old_wr->version;
|
||||
}
|
||||
}
|
||||
else if (old_wr->type() == BS_HEAP_COMMIT)
|
||||
{
|
||||
@@ -2166,7 +2119,10 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
|
||||
res.compact_lsn = wr->lsn;
|
||||
res.compact_version = wr->version;
|
||||
}
|
||||
rollback_version = wr->version;
|
||||
if (rollback_version > wr->version)
|
||||
{
|
||||
rollback_version = wr->version;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (wr->type() == BS_HEAP_COMMIT && wr->lsn <= fsynced_lsn)
|
||||
@@ -2344,7 +2300,7 @@ void blockstore_heap_t::use_data(inode_t inode, uint64_t location)
|
||||
{
|
||||
auto sh_it = pool_shard_settings.find(INODE_POOL(inode));
|
||||
if (sh_it != pool_shard_settings.end() && sh_it->second.no_inode_stats)
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
inode = INODE_WITH_POOL(INODE_POOL(inode), 0);
|
||||
assert(!data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, true);
|
||||
inode_space_stats[inode] += dsk->data_block_size;
|
||||
@@ -2355,7 +2311,7 @@ void blockstore_heap_t::free_data(inode_t inode, uint64_t location)
|
||||
{
|
||||
auto sh_it = pool_shard_settings.find(INODE_POOL(inode));
|
||||
if (sh_it != pool_shard_settings.end() && sh_it->second.no_inode_stats)
|
||||
inode = (INODE_POOL(inode) << POOL_ID_BITS);
|
||||
inode = INODE_WITH_POOL(INODE_POOL(inode), 0);
|
||||
assert(data_alloc->get(location / dsk->data_block_size));
|
||||
data_alloc->set(location / dsk->data_block_size, false);
|
||||
auto sp_it = inode_space_stats.find(inode);
|
||||
@@ -2392,7 +2348,8 @@ void blockstore_heap_t::use_buffer_area(inode_t inode, uint64_t location, uint64
|
||||
return;
|
||||
}
|
||||
assert(!(size % dsk->bitmap_granularity));
|
||||
buffer_alloc->use(location / dsk->bitmap_granularity, size / dsk->bitmap_granularity);
|
||||
bool ok = buffer_alloc->use(location / dsk->bitmap_granularity, size / dsk->bitmap_granularity);
|
||||
assert(ok);
|
||||
buffer_area_used_space += size;
|
||||
}
|
||||
|
||||
@@ -2434,7 +2391,7 @@ void blockstore_heap_t::get_meta_block(uint32_t block_num, uint8_t *buffer)
|
||||
}
|
||||
}
|
||||
|
||||
void blockstore_heap_t::fill_block_empty_space(uint8_t *buffer, uint32_t pos)
|
||||
void blockstore_heap_t::fill_block_empty_space(uint8_t *buffer, uint64_t pos)
|
||||
{
|
||||
if (pos > dsk->meta_block_size)
|
||||
{
|
||||
@@ -2522,7 +2479,7 @@ uint64_t blockstore_heap_t::get_garbage_memory()
|
||||
void blockstore_heap_t::push_inflight_lsn(uint64_t lsn, heap_entry_t *wr, uint64_t flags)
|
||||
{
|
||||
uint64_t next_inf = first_inflight_lsn + inflight_lsn.size();
|
||||
if (flags & (HEAP_INFLIGHT_COMPACTABLE|HEAP_INFLIGHT_COMPACTED))
|
||||
if (flags & (HEAP_INFLIGHT_COMPACTABLE|HEAP_INFLIGHT_OVERWRITE))
|
||||
{
|
||||
to_compact_count++;
|
||||
}
|
||||
@@ -2589,7 +2546,7 @@ void blockstore_heap_t::mark_lsn_fsynced(uint64_t lsn)
|
||||
void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
|
||||
{
|
||||
auto wr = inflight.wr;
|
||||
if (inflight.flags & HEAP_INFLIGHT_COMPACTED)
|
||||
if (inflight.flags & HEAP_INFLIGHT_OVERWRITE)
|
||||
{
|
||||
// Mark previous entries as garbage, sequentially
|
||||
mark_garbage_up_to(wr);
|
||||
|
||||
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t entry_type;
|
||||
uint32_t checksum;
|
||||
uint32_t crc32c;
|
||||
uint64_t lsn;
|
||||
uint64_t inode;
|
||||
uint64_t stripe;
|
||||
@@ -69,8 +69,7 @@ struct __attribute__((__packed__)) heap_entry_t
|
||||
uint32_t *get_checksum(blockstore_heap_t *heap);
|
||||
uint64_t big_location(blockstore_heap_t *heap);
|
||||
void set_big_location(blockstore_heap_t *heap, uint64_t location);
|
||||
uint32_t calc_checksum(blockstore_heap_t *heap);
|
||||
uint32_t calc_checksum(blockstore_disk_t *dsk);
|
||||
uint32_t calc_crc32c();
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_small_write_t
|
||||
@@ -81,7 +80,7 @@ struct __attribute__((__packed__)) heap_small_write_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
|
||||
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_big_write_t
|
||||
@@ -99,7 +98,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
|
||||
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
|
||||
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) heap_list_item_t
|
||||
@@ -220,6 +219,7 @@ class blockstore_heap_t
|
||||
bool marked_used_blocks = false;
|
||||
bool recheck_queue_filled = false;
|
||||
std::vector<heap_list_item_t*> postponed_items;
|
||||
std::vector<heap_list_item_t*> init_erase_items;
|
||||
std::set<uint32_t> recheck_modified_blocks;
|
||||
std::deque<heap_entry_t*> recheck_queue;
|
||||
std::map<heap_entry_t*, heap_recheck_state_t> recheck_states;
|
||||
@@ -362,7 +362,7 @@ public:
|
||||
|
||||
// get metadata block data buffer and used space
|
||||
void get_meta_block(uint32_t block_num, uint8_t *buffer);
|
||||
void fill_block_empty_space(uint8_t *buffer, uint32_t pos);
|
||||
void fill_block_empty_space(uint8_t *buffer, uint64_t pos);
|
||||
uint32_t get_meta_block_used_space(uint32_t block_num);
|
||||
|
||||
// get space usage statistics
|
||||
|
||||
@@ -117,9 +117,12 @@ public:
|
||||
|
||||
journal_flusher_t *flusher;
|
||||
int write_iodepth = 0;
|
||||
int inflight_big = 0;
|
||||
int intent_write_counter = 0;
|
||||
bool fsyncing_data = false;
|
||||
uint64_t data_fsync_next = 0;
|
||||
uint64_t data_fsync_cur = 0;
|
||||
uint64_t data_fsync_sent = 0;
|
||||
uint64_t data_fsync_done = 0;
|
||||
std::deque<bool> data_fsyncs;
|
||||
|
||||
bool live = false, queue_stall = false;
|
||||
ring_loop_i *ringloop = NULL;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#define INIT_META_EMPTY 0
|
||||
#define INIT_META_READING 1
|
||||
#define INIT_META_READ_DONE 2
|
||||
#define INIT_META_WRITING 3
|
||||
|
||||
#define GET_SQE() \
|
||||
sqe = bs->get_sqe();\
|
||||
@@ -23,14 +22,15 @@ blockstore_init_meta::blockstore_init_meta(blockstore_impl_t *bs)
|
||||
this->bs = bs;
|
||||
}
|
||||
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num)
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num, const char *op)
|
||||
{
|
||||
if (data->res < 0)
|
||||
if (data->res != data->iov.iov_len)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string("read metadata failed at offset ") + std::to_string(buf_num >= 0 ? bufs[buf_num].offset : last_read_offset) +
|
||||
std::string(": ") + strerror(-data->res)
|
||||
);
|
||||
throw std::runtime_error(strprintf(
|
||||
"%s failed at offset %ju: got %s (code %d), but expected %zu",
|
||||
op, (buf_num >= 0 ? bufs[buf_num].offset : last_read_offset), strerror(-data->res),
|
||||
data->res, data->iov.iov_len
|
||||
));
|
||||
}
|
||||
if (buf_num >= 0)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ int blockstore_init_meta::loop()
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = { bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read metadata header"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -72,25 +72,19 @@ resume_1:
|
||||
}
|
||||
if (is_zero((uint64_t*)bs->meta_superblock, bs->dsk.meta_block_size))
|
||||
{
|
||||
{
|
||||
blockstore_meta_header_v3_t *hdr = (blockstore_meta_header_v3_t *)bs->meta_superblock;
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_V2)
|
||||
{
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
}
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_HEAP)
|
||||
{
|
||||
hdr->meta_area_size = bs->dsk.meta_area_size;
|
||||
}
|
||||
hdr->set_crc32c();
|
||||
}
|
||||
assert(bs->dsk.meta_format == BLOCKSTORE_META_FORMAT_HEAP);
|
||||
blockstore_meta_header_v3_t *hdr = (blockstore_meta_header_v3_t *)bs->meta_superblock;
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
hdr->completed_lsn = 0;
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
hdr->meta_area_size = bs->dsk.meta_area_size;
|
||||
hdr->set_crc32c();
|
||||
if (bs->readonly)
|
||||
{
|
||||
printf("Skipping metadata initialization because blockstore is readonly\n");
|
||||
@@ -98,21 +92,8 @@ resume_1:
|
||||
else
|
||||
{
|
||||
printf("Initializing metadata area\n");
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_2:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 2;
|
||||
return 1;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -163,7 +144,7 @@ resume_1:
|
||||
hdr->header_csum = csum;
|
||||
}
|
||||
bs->heap->start_load(((blockstore_meta_header_v3_t *)bs->meta_superblock)->completed_lsn);
|
||||
if (bs->dsk.inmemory_journal)
|
||||
if (bs->dsk.inmemory_journal && !zero_on_init)
|
||||
{
|
||||
// Read buffer area
|
||||
printf("Reading buffered data\n");
|
||||
@@ -175,7 +156,7 @@ resume_1:
|
||||
bs->buffer_area + md_offset,
|
||||
(size_t)(bs->dsk.journal_len - md_offset < bs->metadata_buf_size ? bs->dsk.journal_len - md_offset : bs->metadata_buf_size),
|
||||
};
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read buffer area"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.journal_fd, &data->iov, 1, bs->dsk.journal_offset + md_offset);
|
||||
md_offset += data->iov.iov_len;
|
||||
submitted++;
|
||||
@@ -194,7 +175,7 @@ resume_3:
|
||||
next_offset = md_offset;
|
||||
// Read the rest of the metadata
|
||||
resume_4:
|
||||
if (next_offset < bs->dsk.meta_area_size && submitted == 0)
|
||||
if (next_offset < bs->dsk.meta_area_size && submitted == 0 && (!zero_on_init || !bs->readonly))
|
||||
{
|
||||
// Submit one read
|
||||
for (int i = 0; i < 2; i++)
|
||||
@@ -211,12 +192,15 @@ resume_4:
|
||||
GET_SQE();
|
||||
assert(bufs[i].size <= 0x7fffffff);
|
||||
data->iov = { bufs[i].buf, (size_t)bufs[i].size };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i); };
|
||||
if (!zero_on_init)
|
||||
{
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "read metadata"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill metadata with empty block pattern
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "clear metadata"); };
|
||||
memset(bufs[i].buf, 0, bufs[i].size);
|
||||
for (uint64_t o = 0; o < bufs[i].size; o += bs->dsk.meta_block_size)
|
||||
bs->heap->fill_block_empty_space(bufs[i].buf + o, 0);
|
||||
@@ -232,11 +216,14 @@ resume_4:
|
||||
if (bufs[i].state == INIT_META_READ_DONE)
|
||||
{
|
||||
// Handle result
|
||||
uint64_t loaded = 0;
|
||||
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
|
||||
if (r != 0)
|
||||
exit(1);
|
||||
entries_loaded += loaded;
|
||||
if (!zero_on_init)
|
||||
{
|
||||
uint64_t loaded = 0;
|
||||
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
|
||||
if (r != 0)
|
||||
exit(1);
|
||||
entries_loaded += loaded;
|
||||
}
|
||||
bufs[i].state = 0;
|
||||
bs->ringloop->wakeup();
|
||||
}
|
||||
@@ -246,7 +233,7 @@ resume_4:
|
||||
wait_state = 4;
|
||||
return 1;
|
||||
}
|
||||
// metadata read finished
|
||||
// metadata read/clear finished
|
||||
bs->heap->finish_load();
|
||||
printf("Metadata entries loaded: %ju, rechecking unfinished writes and garbage entries\n", entries_loaded);
|
||||
// asynchronous recheck
|
||||
@@ -329,13 +316,14 @@ resume_9:
|
||||
}
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
do_fsync:
|
||||
if (!bs->dsk.disable_meta_fsync && !bs->readonly)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "fsync metadata"); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_5:
|
||||
@@ -345,6 +333,27 @@ resume_9:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (zero_on_init && !header_written && !bs->readonly)
|
||||
{
|
||||
GET_SQE();
|
||||
header_written = true;
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "write metadata header"); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_2:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 2;
|
||||
return 1;
|
||||
}
|
||||
if (!bs->dsk.disable_meta_fsync)
|
||||
{
|
||||
goto do_fsync;
|
||||
}
|
||||
}
|
||||
printf("Loading finished. Data used: %ju / %ju bytes (%s / %s)\n",
|
||||
bs->heap->get_data_used_space(), bs->dsk.block_count * bs->dsk.data_block_size,
|
||||
format_size(bs->heap->get_data_used_space()).c_str(),
|
||||
|
||||
@@ -17,6 +17,7 @@ class blockstore_init_meta
|
||||
int wait_state = 0;
|
||||
int wait_count = 0;
|
||||
bool zero_on_init = false;
|
||||
bool header_written = false;
|
||||
void *metadata_buffer = NULL;
|
||||
blockstore_init_meta_buf bufs[2] = {};
|
||||
int submitted = 0;
|
||||
@@ -29,7 +30,7 @@ class blockstore_init_meta
|
||||
std::vector<uint32_t> recheck_mod;
|
||||
int i = 0, j = 0;
|
||||
bool handle_meta_block(uint8_t *buf, uint64_t count, uint64_t done_cnt);
|
||||
void handle_event(ring_data_t *data, int buf_num);
|
||||
void handle_event(ring_data_t *data, int buf_num, const char *op);
|
||||
public:
|
||||
blockstore_init_meta(blockstore_impl_t *bs);
|
||||
int loop();
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#include "blockstore_mock.h"
|
||||
|
||||
blockstore_mock_t::blockstore_mock_t(const blockstore_config_t & config)
|
||||
{
|
||||
}
|
||||
|
||||
void blockstore_mock_t::parse_config(blockstore_config_t & config)
|
||||
{
|
||||
}
|
||||
|
||||
void* blockstore_mock_t::reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::reshard_continue(void *reshard_state, uint64_t chunk_limit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_mock_t::loop()
|
||||
{
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::is_started()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::is_stalled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool blockstore_mock_t::is_safe_to_stop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void blockstore_mock_t::enqueue_op(blockstore_op_t *op)
|
||||
{
|
||||
}
|
||||
|
||||
int blockstore_mock_t::read_bitmap(object_id oid, uint64_t target_version, void *bitmap, uint64_t *result_version)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
const std::map<uint64_t, uint64_t> & blockstore_mock_t::get_inode_space_stats()
|
||||
{
|
||||
return inode_space;
|
||||
}
|
||||
|
||||
void blockstore_mock_t::set_no_inode_stats(const std::vector<uint64_t> & pool_ids)
|
||||
{
|
||||
}
|
||||
|
||||
void blockstore_mock_t::dump_diagnostics()
|
||||
{
|
||||
}
|
||||
|
||||
std::string blockstore_mock_t::get_op_diag(blockstore_op_t *op)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32_t blockstore_mock_t::get_block_size()
|
||||
{
|
||||
return block_size;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_block_count()
|
||||
{
|
||||
return block_count;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_free_block_count()
|
||||
{
|
||||
return block_count;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_journal_size()
|
||||
{
|
||||
return 32*1024*1024;
|
||||
}
|
||||
|
||||
uint32_t blockstore_mock_t::get_bitmap_granularity()
|
||||
{
|
||||
return bitmap_granularity;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_live_entries()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_live_memory()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_garbage_entries()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t blockstore_mock_t::get_garbage_memory()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "blockstore.h"
|
||||
|
||||
class blockstore_mock_t: public blockstore_i
|
||||
{
|
||||
public:
|
||||
uint32_t block_size = 128*1024;
|
||||
uint32_t bitmap_granularity = 4096;
|
||||
uint64_t block_count = 100*1024*8;
|
||||
std::map<uint64_t, uint64_t> inode_space;
|
||||
|
||||
blockstore_mock_t(const blockstore_config_t & config);
|
||||
void parse_config(blockstore_config_t & config) override;
|
||||
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit) override;
|
||||
bool reshard_continue(void *reshard_state, uint64_t chunk_limit) override;
|
||||
void loop() override;
|
||||
bool is_started() override;
|
||||
bool is_stalled() override;
|
||||
bool is_safe_to_stop() override;
|
||||
void enqueue_op(blockstore_op_t *op) override;
|
||||
int read_bitmap(object_id oid, uint64_t target_version, void *bitmap, uint64_t *result_version = NULL) override;
|
||||
const std::map<uint64_t, uint64_t> & get_inode_space_stats() override;
|
||||
void set_no_inode_stats(const std::vector<uint64_t> & pool_ids) override;
|
||||
void dump_diagnostics() override;
|
||||
std::string get_op_diag(blockstore_op_t *op) override;
|
||||
uint32_t get_block_size() override;
|
||||
uint64_t get_block_count() override;
|
||||
uint64_t get_free_block_count() override;
|
||||
uint64_t get_journal_size() override;
|
||||
uint32_t get_bitmap_granularity() override;
|
||||
uint64_t get_live_entries() override;
|
||||
uint64_t get_live_memory() override;
|
||||
uint64_t get_garbage_entries() override;
|
||||
uint64_t get_garbage_memory() override;
|
||||
};
|
||||
@@ -28,6 +28,7 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
priv->modified_block2 = UINT32_MAX;
|
||||
int res = op->opcode == BS_OP_STABLE
|
||||
? heap->add_commit(obj, v[priv->stab_pos].version, &priv->modified_block2)
|
||||
: heap->add_rollback(obj, v[priv->stab_pos].version, &priv->modified_block2);
|
||||
@@ -52,11 +53,6 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
|
||||
FINISH_OP(op);
|
||||
return 2;
|
||||
}
|
||||
if (priv->modified_block2 != UINT32_MAX)
|
||||
{
|
||||
priv->stab_pos--;
|
||||
goto resume_1;
|
||||
}
|
||||
priv->wait_for = WAIT_COMPACTION;
|
||||
priv->wait_detail = heap->get_compacted_count();
|
||||
flusher->request_trim();
|
||||
|
||||
@@ -29,9 +29,12 @@ bool blockstore_impl_t::has_unsynced()
|
||||
|
||||
bool blockstore_impl_t::submit_fsyncs(int & wait_count)
|
||||
{
|
||||
int n = (unsynced_meta_write_count > 0 && !dsk.disable_meta_fsync) +
|
||||
(unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync && dsk.journal_fd != dsk.meta_fd) +
|
||||
(unsynced_data_write_count > 0 && !dsk.disable_data_fsync && dsk.data_fd != dsk.meta_fd && dsk.data_fd != dsk.journal_fd);
|
||||
int n = (unsynced_meta_write_count > 0 && !dsk.disable_meta_fsync ? 1 : 0) +
|
||||
(unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.journal_fd != dsk.meta_fd) ? 1 : 0) +
|
||||
(unsynced_data_write_count > 0 && !dsk.disable_data_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.data_fd != dsk.meta_fd) &&
|
||||
(!unsynced_buffer_write_count || dsk.data_fd != dsk.journal_fd) ? 1 : 0);
|
||||
if (ringloop->space_left() < n)
|
||||
{
|
||||
return false;
|
||||
@@ -60,7 +63,8 @@ bool blockstore_impl_t::submit_fsyncs(int & wait_count)
|
||||
data->callback = cb;
|
||||
wait_count++;
|
||||
}
|
||||
if (unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync && dsk.meta_fd != dsk.journal_fd)
|
||||
if (unsynced_buffer_write_count > 0 && !dsk.disable_journal_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.journal_fd != dsk.meta_fd))
|
||||
{
|
||||
// fsync buffer
|
||||
io_uring_sqe *sqe = get_sqe();
|
||||
@@ -71,7 +75,9 @@ bool blockstore_impl_t::submit_fsyncs(int & wait_count)
|
||||
data->callback = cb;
|
||||
wait_count++;
|
||||
}
|
||||
if (unsynced_data_write_count > 0 && !dsk.disable_data_fsync && dsk.data_fd != dsk.meta_fd && dsk.data_fd != dsk.journal_fd)
|
||||
if (unsynced_data_write_count > 0 && !dsk.disable_data_fsync &&
|
||||
(!unsynced_meta_write_count || dsk.data_fd != dsk.meta_fd) &&
|
||||
(!unsynced_buffer_write_count || dsk.data_fd != dsk.journal_fd))
|
||||
{
|
||||
// fsync data
|
||||
io_uring_sqe *sqe = get_sqe();
|
||||
@@ -109,6 +115,7 @@ int blockstore_impl_t::do_sync(blockstore_op_t *op, int base_state)
|
||||
PRIV(op)->lsn = heap->get_completed_lsn();
|
||||
if (!submit_fsyncs(PRIV(op)->pending_ops))
|
||||
{
|
||||
PRIV(op)->lsn = 0;
|
||||
PRIV(op)->wait_detail = 1;
|
||||
PRIV(op)->wait_for = WAIT_SQE;
|
||||
return 0;
|
||||
|
||||
@@ -180,13 +180,16 @@ enospc:
|
||||
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
|
||||
assert(loc+op->offset+op->len <= dsk.block_count*dsk.data_block_size);
|
||||
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
|
||||
if (!dsk.disable_data_fsync)
|
||||
{
|
||||
// use PRIV->lsn for fsync_data_id
|
||||
PRIV(op)->lsn = ++data_fsync_next;
|
||||
data_fsyncs.push_back(false);
|
||||
}
|
||||
PRIV(op)->pending_ops++;
|
||||
write_iodepth++;
|
||||
if (PRIV(op)->write_type == BS_HEAP_BIG_WRITE)
|
||||
{
|
||||
PRIV(op)->op_state = 1;
|
||||
inflight_big++;
|
||||
}
|
||||
else
|
||||
PRIV(op)->op_state = 3;
|
||||
}
|
||||
@@ -297,8 +300,6 @@ again:
|
||||
goto resume_10;
|
||||
else if (op_state == 11)
|
||||
goto resume_11;
|
||||
else if (op_state == 12)
|
||||
goto resume_12;
|
||||
else
|
||||
{
|
||||
// In progress
|
||||
@@ -317,38 +318,44 @@ again:
|
||||
resume_2:
|
||||
// We must fsync all big writes to avoid complex write workflows
|
||||
// It's OK for all HDDs and for server SSDs, but slightly worse for desktop SSDs
|
||||
inflight_big--;
|
||||
if (!dsk.disable_data_fsync)
|
||||
{
|
||||
// fsync data in a batch
|
||||
resume_11:
|
||||
if (inflight_big > 0)
|
||||
// Mark our data write as completed and advance data_fsync_cur
|
||||
data_fsyncs[PRIV(op)->lsn - data_fsync_cur - 1] = true;
|
||||
while (data_fsyncs.size() > 0 && data_fsyncs.front())
|
||||
{
|
||||
data_fsyncs.pop_front();
|
||||
data_fsync_cur++;
|
||||
}
|
||||
PRIV(op)->op_state = 11;
|
||||
// Then wait for all other data writes currently in progress to do less fsync calls
|
||||
// I.e. to fsync data in batches
|
||||
PRIV(op)->lsn = data_fsync_cur + data_fsyncs.size();
|
||||
resume_11:
|
||||
if (data_fsync_cur < PRIV(op)->lsn)
|
||||
{
|
||||
PRIV(op)->op_state = 11;
|
||||
return 1;
|
||||
}
|
||||
if (fsyncing_data)
|
||||
if (PRIV(op)->lsn > data_fsync_sent)
|
||||
{
|
||||
resume_12:
|
||||
if (fsyncing_data)
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
io_uring_prep_fsync(sqe, dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
data->iov = { 0 };
|
||||
data->callback = [this, op, fs = data_fsync_cur](ring_data_t *data)
|
||||
{
|
||||
PRIV(op)->op_state = 12;
|
||||
return 1;
|
||||
}
|
||||
goto resume_4;
|
||||
if (fs > data_fsync_done)
|
||||
{
|
||||
data_fsync_done = fs;
|
||||
ringloop->wakeup();
|
||||
}
|
||||
};
|
||||
data_fsync_sent = data_fsync_cur;
|
||||
}
|
||||
fsyncing_data = true;
|
||||
BS_SUBMIT_GET_SQE(sqe, data);
|
||||
io_uring_prep_fsync(sqe, dsk.data_fd, IORING_FSYNC_DATASYNC);
|
||||
data->iov = { 0 };
|
||||
data->callback = [this, op](ring_data_t *data)
|
||||
if (PRIV(op)->lsn > data_fsync_done)
|
||||
{
|
||||
fsyncing_data = false;
|
||||
handle_write_event(data, op);
|
||||
};
|
||||
PRIV(op)->pending_ops++;
|
||||
PRIV(op)->op_state = 3;
|
||||
return 1;
|
||||
return 1;
|
||||
}
|
||||
PRIV(op)->lsn = 0;
|
||||
}
|
||||
resume_4:
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ multilist_alloc_t::multilist_alloc_t(uint32_t count, uint32_t maxn):
|
||||
count(count), maxn(maxn)
|
||||
{
|
||||
// not-so-memory-efficient: 16 MB memory per 1 GB buffer space, but buffer spaces are small, so OK
|
||||
assert(count > 1 && count < 0x80000000);
|
||||
assert(count > 1 && count < 0x80000000 && count >= maxn);
|
||||
sizes.resize(count);
|
||||
nexts.resize(count); // nexts[i] = 0 -> area is used; nexts[i] = 1 -> no next; nexts[i] >= 2 -> next item
|
||||
prevs.resize(count);
|
||||
@@ -171,7 +171,7 @@ void multilist_alloc_t::print()
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
bool multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
{
|
||||
assert(pos+size <= count && size > 0);
|
||||
if (sizes[pos] <= 0)
|
||||
@@ -182,7 +182,8 @@ void multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
else
|
||||
while (start > 0 && !sizes[start])
|
||||
start--;
|
||||
assert(sizes[start] >= size);
|
||||
if (sizes[start] < size+(pos-start))
|
||||
return false;
|
||||
use_full(start);
|
||||
uint32_t full = sizes[start];
|
||||
sizes[pos-1] = -pos+start;
|
||||
@@ -199,7 +200,8 @@ void multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(sizes[pos] >= size);
|
||||
if (sizes[pos] < size)
|
||||
return false;
|
||||
use_full(pos);
|
||||
if (sizes[pos] > size)
|
||||
{
|
||||
@@ -214,12 +216,13 @@ void multilist_alloc_t::use(uint32_t pos, uint32_t size)
|
||||
#ifdef MULTILIST_TRACE
|
||||
print();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void multilist_alloc_t::use_full(uint32_t pos)
|
||||
{
|
||||
uint32_t prevsize = sizes[pos];
|
||||
assert(prevsize);
|
||||
assert(prevsize > 0);
|
||||
assert(nexts[pos]);
|
||||
uint32_t pi = (prevsize < maxn ? prevsize : maxn)-1;
|
||||
if (heads[pi] == pos+1)
|
||||
|
||||
@@ -17,7 +17,7 @@ struct multilist_alloc_t
|
||||
bool is_free(uint32_t pos);
|
||||
uint32_t find(uint32_t size);
|
||||
void use_full(uint32_t pos);
|
||||
void use(uint32_t pos, uint32_t size);
|
||||
bool use(uint32_t pos, uint32_t size);
|
||||
void do_free(uint32_t pos);
|
||||
void free(uint32_t pos);
|
||||
void verify();
|
||||
|
||||
+53
-22
@@ -71,6 +71,11 @@ bool journal_flusher_t::is_active()
|
||||
return active_flushers > 0 || dequeuing;
|
||||
}
|
||||
|
||||
size_t journal_flusher_t::get_queue_size()
|
||||
{
|
||||
return flush_queue.size();
|
||||
}
|
||||
|
||||
void journal_flusher_t::loop()
|
||||
{
|
||||
target_flusher_count = bs->write_iodepth*2;
|
||||
@@ -384,6 +389,7 @@ stop_flusher:
|
||||
wait_state = 0;
|
||||
return true;
|
||||
}
|
||||
copy_count = 0;
|
||||
try_trim = true;
|
||||
cur.oid = flusher->flush_queue.front();
|
||||
cur.version = flusher->flush_versions[cur.oid];
|
||||
@@ -511,6 +517,31 @@ resume_2:
|
||||
{
|
||||
uo_it->second.was_changed = true;
|
||||
}
|
||||
if (!bs->journal.inmemory)
|
||||
{
|
||||
// Verify journaled data checksums (but not COALESCED)
|
||||
for (it = v.begin(); it != v.end(); it++)
|
||||
{
|
||||
if (it->copy_flags == COPY_BUF_JOURNAL)
|
||||
{
|
||||
iovec iov = { .iov_base = it->buf, .iov_len = it->len };
|
||||
bs->verify_journal_checksums(
|
||||
it->csum_buf, it->offset, &iov, 1,
|
||||
[&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
|
||||
{
|
||||
printf(
|
||||
"Checksum mismatch in object %jx:%jx v%ju in journal at 0x%jx, checksum block #%u: got %08x, expected %08x\n",
|
||||
cur.oid.inode, cur.oid.stripe, cur.version, it->disk_offset,
|
||||
bad_block / bs->dsk.csum_block_size, calc_csum, stored_csum
|
||||
);
|
||||
bad_block += it->offset;
|
||||
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
|
||||
mangle_csum_blocks.insert(bad_block);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Submit data writes
|
||||
for (it = v.begin(); it != v.end(); it++)
|
||||
@@ -634,6 +665,7 @@ resume_2:
|
||||
}
|
||||
// All done
|
||||
flusher->active_flushers--;
|
||||
copy_count = 0; // used by is_mutated()...
|
||||
wait_state = 0;
|
||||
goto resume_0;
|
||||
}
|
||||
@@ -815,35 +847,21 @@ bool journal_flusher_co::clear_incomplete_csum_block_bits(int wait_base)
|
||||
bs->verify_padded_checksums(new_clean_bitmap, new_clean_bitmap + 2*bs->dsk.clean_entry_bitmap_size,
|
||||
v[i].offset, &iov, 1, [&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
|
||||
{
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in data area at offset 0x%jx+0x%x: got %08x, expected %08x\n",
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in data area at offset 0x%jx+0x%x during flush: got %08x, expected %08x\n",
|
||||
cur.oid.inode, cur.oid.stripe, old_clean_ver, old_clean_loc, bad_block, calc_csum, stored_csum);
|
||||
for (uint32_t j = 0; j < bs->dsk.csum_block_size; j += bs->dsk.bitmap_granularity)
|
||||
{
|
||||
// Simplest method of mangling: flip one byte in every sector
|
||||
((uint8_t*)v[i].buf)[j+bad_block-v[i].offset] ^= 0xff;
|
||||
}
|
||||
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
|
||||
mangle_csum_blocks.insert(bad_block);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
bs->verify_journal_checksums(v[i].csum_buf, v[i].offset, &iov, 1, [&](uint32_t bad_block, uint32_t calc_csum, uint32_t stored_csum)
|
||||
{
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in journal at offset 0x%jx+0x%x (block offset 0x%jx): got %08x, expected %08x\n",
|
||||
printf("Checksum mismatch in object %jx:%jx v%ju in journal at offset 0x%jx+0x%x (block offset 0x%jx) during flush: got %08x, expected %08x\n",
|
||||
cur.oid.inode, cur.oid.stripe, old_clean_ver,
|
||||
v[i].disk_offset, bad_block, v[i].offset, calc_csum, stored_csum);
|
||||
bad_block += (v[i].offset/bs->dsk.csum_block_size) * bs->dsk.csum_block_size;
|
||||
uint32_t bad_block_end = bad_block + bs->dsk.csum_block_size + (v[i].offset/bs->dsk.csum_block_size) * bs->dsk.csum_block_size;
|
||||
if (bad_block < v[i].offset)
|
||||
bad_block = v[i].offset;
|
||||
if (bad_block_end > v[i].offset+v[i].len)
|
||||
bad_block_end = v[i].offset+v[i].len;
|
||||
bad_block -= v[i].offset;
|
||||
bad_block_end -= v[i].offset;
|
||||
for (uint32_t j = bad_block; j < bad_block_end; j += bs->dsk.bitmap_granularity)
|
||||
{
|
||||
// Simplest method of mangling: flip one byte in every sector
|
||||
((uint8_t*)v[i].buf)[j] ^= 0xff;
|
||||
}
|
||||
assert(!(bad_block % bs->dsk.csum_block_size) && bad_block < bs->dsk.data_block_size);
|
||||
mangle_csum_blocks.insert(bad_block);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -952,6 +970,11 @@ void journal_flusher_co::calc_block_checksums(uint32_t *new_data_csums, bool ski
|
||||
}
|
||||
// `v` should contain aligned items, possibly split into pieces
|
||||
assert(!block_done);
|
||||
for (uint32_t mangle_block: mangle_csum_blocks)
|
||||
{
|
||||
// Flip 1 bit
|
||||
new_data_csums[mangle_block / bs->dsk.csum_block_size] ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void journal_flusher_co::scan_dirty()
|
||||
@@ -1088,7 +1111,8 @@ void journal_flusher_co::scan_dirty()
|
||||
last--;
|
||||
read_to_fill_incomplete = bs->fill_partial_checksum_blocks(
|
||||
v, fulfilled, bmp_ptr, NULL, false, NULL, v[0].offset/bs->dsk.csum_block_size * bs->dsk.csum_block_size,
|
||||
((v[last].offset+v[last].len-1) / bs->dsk.csum_block_size + 1) * bs->dsk.csum_block_size
|
||||
((v[last].offset+v[last].len-1) / bs->dsk.csum_block_size + 1) * bs->dsk.csum_block_size,
|
||||
0, bs->dsk.data_block_size
|
||||
);
|
||||
}
|
||||
else if (fill_incomplete && clean_init_bitmap)
|
||||
@@ -1118,6 +1142,7 @@ bool journal_flusher_co::read_dirty(int wait_base)
|
||||
if (wait_state == wait_base) goto resume_0;
|
||||
else if (wait_state == wait_base+1) goto resume_1;
|
||||
wait_count = wait_journal_count = 0;
|
||||
mangle_csum_blocks.clear();
|
||||
if (bs->journal.inmemory && !read_to_fill_incomplete)
|
||||
{
|
||||
// Happy path: nothing to read :)
|
||||
@@ -1349,7 +1374,7 @@ bool journal_flusher_co::fsync_batch(bool fsync_meta, int wait_base)
|
||||
cur_sync->ready_count++;
|
||||
flusher->syncing_flushers++;
|
||||
resume_1:
|
||||
if (!cur_sync->state)
|
||||
if (cur_sync->state == 0)
|
||||
{
|
||||
if (flusher->syncing_flushers >= flusher->active_flushers || !flusher->flush_queue.size())
|
||||
{
|
||||
@@ -1377,6 +1402,12 @@ bool journal_flusher_co::fsync_batch(bool fsync_meta, int wait_base)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (cur_sync->state == 1)
|
||||
{
|
||||
// Wait for fsync completion
|
||||
wait_state = wait_base+1;
|
||||
return false;
|
||||
}
|
||||
flusher->syncing_flushers--;
|
||||
cur_sync->ready_count--;
|
||||
if (cur_sync->ready_count == 0)
|
||||
|
||||
@@ -66,6 +66,7 @@ class journal_flusher_co
|
||||
uint64_t clean_bitmap_offset, clean_bitmap_len;
|
||||
uint8_t *clean_init_dyn_ptr;
|
||||
uint8_t *new_clean_bitmap;
|
||||
std::unordered_set<uint32_t> mangle_csum_blocks;
|
||||
|
||||
uint64_t new_trim_pos;
|
||||
|
||||
@@ -123,6 +124,7 @@ public:
|
||||
void loop();
|
||||
bool is_trim_wanted() { return trim_wanted; }
|
||||
bool is_active();
|
||||
size_t get_queue_size();
|
||||
void mark_trim_possible();
|
||||
void request_trim();
|
||||
void release_trim();
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
|
||||
namespace v1 {
|
||||
|
||||
blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd)
|
||||
blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd, bool mock_mode)
|
||||
{
|
||||
assert(sizeof(blockstore_op_private_t) <= BS_OP_PRIVATE_DATA_SIZE);
|
||||
this->tfd = tfd;
|
||||
this->ringloop = ringloop;
|
||||
dsk.mock_mode = mock_mode;
|
||||
ring_consumer.loop = [this]() { loop(); };
|
||||
ringloop->register_consumer(&ring_consumer);
|
||||
initialized = 0;
|
||||
@@ -35,6 +36,11 @@ blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *
|
||||
|
||||
blockstore_impl_t::~blockstore_impl_t()
|
||||
{
|
||||
for (auto& obj: dirty_db)
|
||||
{
|
||||
if (obj.second.dyn_data)
|
||||
free(obj.second.dyn_data);
|
||||
}
|
||||
delete data_alloc;
|
||||
delete flusher;
|
||||
if (zero_object)
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
//#define BLOCKSTORE_DEBUG
|
||||
|
||||
struct bs_test_t;
|
||||
|
||||
namespace v1 {
|
||||
|
||||
#include "journal.h"
|
||||
@@ -96,7 +98,7 @@ struct blockstore_op_private_t
|
||||
int op_state;
|
||||
|
||||
// Read
|
||||
uint64_t clean_block_used;
|
||||
uint64_t clean_loc_used;
|
||||
std::vector<copy_buffer_t> read_vec;
|
||||
|
||||
// Sync, write
|
||||
@@ -122,6 +124,7 @@ typedef uint64_t pool_pg_id_t;
|
||||
|
||||
class blockstore_impl_t: public blockstore_i
|
||||
{
|
||||
friend struct ::bs_test_t;
|
||||
blockstore_disk_t dsk;
|
||||
|
||||
/******* OPTIONS *******/
|
||||
@@ -220,6 +223,7 @@ class blockstore_impl_t: public blockstore_i
|
||||
|
||||
// Read
|
||||
int dequeue_read(blockstore_op_t *read_op);
|
||||
void release_clean(blockstore_op_t *op);
|
||||
void find_holes(std::vector<copy_buffer_t> & read_vec, uint32_t item_start, uint32_t item_end,
|
||||
std::function<int(int, bool, uint32_t, uint32_t)> callback);
|
||||
int fulfill_read(blockstore_op_t *read_op,
|
||||
@@ -230,7 +234,8 @@ class blockstore_impl_t: public blockstore_i
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data,
|
||||
uint32_t item_start, uint32_t item_end, uint64_t clean_loc, uint64_t clean_ver);
|
||||
int fill_partial_checksum_blocks(std::vector<copy_buffer_t> & rv, uint64_t & fulfilled,
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf, uint64_t read_offset, uint64_t read_end);
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf,
|
||||
uint32_t read_offset, uint32_t read_end, uint32_t item_start, uint32_t item_end);
|
||||
int pad_journal_read(std::vector<copy_buffer_t> & rv, copy_buffer_t & cp,
|
||||
uint64_t dirty_offset, uint64_t dirty_end, uint64_t dirty_loc, uint8_t *csum_ptr, int *dyn_data,
|
||||
uint64_t offset, uint64_t submit_len, uint64_t & blk_begin, uint64_t & blk_end, uint8_t* & blk_buf);
|
||||
@@ -281,7 +286,7 @@ class blockstore_impl_t: public blockstore_i
|
||||
|
||||
public:
|
||||
|
||||
blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd);
|
||||
blockstore_impl_t(blockstore_config_t & config, ring_loop_i *ringloop, timerfd_manager_t *tfd, bool mock_mode = false);
|
||||
~blockstore_impl_t();
|
||||
|
||||
void parse_config(blockstore_config_t & config);
|
||||
|
||||
+83
-68
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#include "str_util.h"
|
||||
#include "impl.h"
|
||||
#include "internal.h"
|
||||
|
||||
@@ -30,14 +31,15 @@ blockstore_init_meta::blockstore_init_meta(blockstore_impl_t *bs)
|
||||
this->bs = bs;
|
||||
}
|
||||
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num)
|
||||
void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num, const char *op)
|
||||
{
|
||||
if (data->res < 0)
|
||||
if (data->res != data->iov.iov_len)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string("read metadata failed at offset ") + std::to_string(buf_num >= 0 ? bufs[buf_num].offset : last_read_offset) +
|
||||
std::string(": ") + strerror(-data->res)
|
||||
);
|
||||
throw std::runtime_error(strprintf(
|
||||
"%s failed at offset %ju: got %s (code %d), but expected %zu",
|
||||
op, (buf_num >= 0 ? bufs[buf_num].offset : last_read_offset), strerror(-data->res),
|
||||
data->res, data->iov.iov_len
|
||||
));
|
||||
}
|
||||
if (buf_num >= 0)
|
||||
{
|
||||
@@ -65,10 +67,11 @@ int blockstore_init_meta::loop()
|
||||
if (!metadata_buffer)
|
||||
throw std::runtime_error("Failed to allocate metadata read buffer");
|
||||
// Read superblock
|
||||
hdr = (blockstore_meta_header_v2_t *)memalign_or_die(MEM_ALIGNMENT, bs->dsk.meta_block_size);
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
data->iov = { hdr, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read metadata header"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -78,24 +81,8 @@ resume_1:
|
||||
wait_state = 1;
|
||||
return 1;
|
||||
}
|
||||
if (iszero((uint64_t*)metadata_buffer, bs->dsk.meta_block_size / sizeof(uint64_t)))
|
||||
if (iszero((uint64_t*)hdr, bs->dsk.meta_block_size / sizeof(uint64_t)))
|
||||
{
|
||||
{
|
||||
blockstore_meta_header_v2_t *hdr = (blockstore_meta_header_v2_t *)metadata_buffer;
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_V2)
|
||||
{
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
hdr->header_csum = 0;
|
||||
hdr->header_csum = crc32c(0, hdr, sizeof(*hdr));
|
||||
}
|
||||
}
|
||||
if (bs->readonly)
|
||||
{
|
||||
printf("Skipping metadata initialization because blockstore is readonly\n");
|
||||
@@ -103,25 +90,11 @@ resume_1:
|
||||
else
|
||||
{
|
||||
printf("Initializing metadata area\n");
|
||||
GET_SQE();
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_3:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 3;
|
||||
return 1;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
zero_on_init = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
blockstore_meta_header_v2_t *hdr = (blockstore_meta_header_v2_t *)metadata_buffer;
|
||||
if (hdr->zero != 0 || hdr->magic != BLOCKSTORE_META_MAGIC_V1 || hdr->version < BLOCKSTORE_META_FORMAT_V1)
|
||||
{
|
||||
printf(
|
||||
@@ -223,12 +196,15 @@ resume_2:
|
||||
GET_SQE();
|
||||
assert(bufs[i].size <= 0x7fffffff);
|
||||
data->iov = { bufs[i].buf, (size_t)bufs[i].size };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i); };
|
||||
if (!zero_on_init)
|
||||
{
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "read metadata"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fill metadata with zeroes
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "clear metadata"); };
|
||||
memset(data->iov.iov_base, 0, data->iov.iov_len);
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
}
|
||||
@@ -256,7 +232,7 @@ resume_2:
|
||||
GET_SQE();
|
||||
assert(bufs[i].size <= 0x7fffffff);
|
||||
data->iov = { bufs[i].buf, (size_t)bufs[i].size };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i); };
|
||||
data->callback = [this, i](ring_data_t *data) { handle_event(data, i, "write metadata"); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + bufs[i].offset);
|
||||
bs->ringloop->submit();
|
||||
bufs[i].state = INIT_META_WRITING;
|
||||
@@ -285,7 +261,7 @@ resume_2:
|
||||
GET_SQE();
|
||||
last_read_offset = (1+next_offset)*bs->dsk.meta_block_size;
|
||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "read metadata"); };
|
||||
io_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -302,7 +278,7 @@ resume_5:
|
||||
}
|
||||
GET_SQE();
|
||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "write metadata"); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
@@ -317,27 +293,64 @@ resume_6:
|
||||
}
|
||||
// metadata read finished
|
||||
printf("Metadata entries loaded: %ju, free blocks: %ju / %ju\n", entries_loaded, bs->data_alloc->get_free_count(), bs->dsk.block_count);
|
||||
if (zero_on_init && !bs->readonly)
|
||||
{
|
||||
do_fsync:
|
||||
if (!bs->disable_meta_fsync)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "fsync metadata"); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_4:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 4;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!header_written)
|
||||
{
|
||||
GET_SQE();
|
||||
hdr->zero = 0;
|
||||
hdr->magic = BLOCKSTORE_META_MAGIC_V1;
|
||||
hdr->version = bs->dsk.meta_format;
|
||||
hdr->meta_block_size = bs->dsk.meta_block_size;
|
||||
hdr->data_block_size = bs->dsk.data_block_size;
|
||||
hdr->bitmap_granularity = bs->dsk.bitmap_granularity;
|
||||
if (bs->dsk.meta_format >= BLOCKSTORE_META_FORMAT_V2)
|
||||
{
|
||||
hdr->data_csum_type = bs->dsk.data_csum_type;
|
||||
hdr->csum_block_size = bs->dsk.csum_block_size;
|
||||
hdr->header_csum = 0;
|
||||
hdr->header_csum = crc32c(0, hdr, sizeof(*hdr));
|
||||
}
|
||||
header_written = true;
|
||||
last_read_offset = 0;
|
||||
data->iov = (struct iovec){ hdr, (size_t)bs->dsk.meta_block_size };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1, "write metadata header"); };
|
||||
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||
bs->ringloop->submit();
|
||||
submitted++;
|
||||
resume_3:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 3;
|
||||
return 1;
|
||||
}
|
||||
goto do_fsync;
|
||||
}
|
||||
}
|
||||
if (!bs->inmemory_meta)
|
||||
{
|
||||
free(metadata_buffer);
|
||||
metadata_buffer = NULL;
|
||||
}
|
||||
if (zero_on_init && !bs->disable_meta_fsync)
|
||||
{
|
||||
GET_SQE();
|
||||
io_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||
last_read_offset = 0;
|
||||
data->iov = { 0 };
|
||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||
submitted++;
|
||||
bs->ringloop->submit();
|
||||
resume_4:
|
||||
if (submitted > 0)
|
||||
{
|
||||
wait_state = 4;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
free(hdr);
|
||||
hdr = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -345,6 +358,8 @@ bool blockstore_init_meta::handle_meta_block(uint8_t *buf, uint64_t entries_per_
|
||||
{
|
||||
bool updated = false;
|
||||
uint64_t max_i = entries_per_block;
|
||||
if (done_cnt > bs->dsk.block_count)
|
||||
return false;
|
||||
if (max_i > bs->dsk.block_count-done_cnt)
|
||||
max_i = bs->dsk.block_count-done_cnt;
|
||||
for (uint64_t i = 0; i < max_i; i++)
|
||||
@@ -455,21 +470,21 @@ blockstore_init_journal::blockstore_init_journal(blockstore_impl_t *bs)
|
||||
};
|
||||
}
|
||||
|
||||
void blockstore_init_journal::handle_event(ring_data_t *data1)
|
||||
void blockstore_init_journal::handle_event(ring_data_t *data)
|
||||
{
|
||||
if (data1->res <= 0)
|
||||
if (data->res != data->iov.iov_len)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string("read journal failed at offset ") + std::to_string(journal_pos) +
|
||||
std::string(": ") + strerror(-data1->res)
|
||||
);
|
||||
throw std::runtime_error(strprintf(
|
||||
"read journal failed at offset %ju: got %s (code %d), but expected %zu",
|
||||
journal_pos, strerror(-data->res), data->res, data->iov.iov_len
|
||||
));
|
||||
}
|
||||
done.push_back({
|
||||
.buf = submitted_buf,
|
||||
.pos = journal_pos,
|
||||
.len = (uint64_t)data1->res,
|
||||
.len = (uint64_t)data->res,
|
||||
});
|
||||
journal_pos += data1->res;
|
||||
journal_pos += data->res;
|
||||
if (journal_pos >= bs->journal.len)
|
||||
{
|
||||
// Continue from the beginning
|
||||
|
||||
@@ -16,7 +16,9 @@ class blockstore_init_meta
|
||||
blockstore_impl_t *bs;
|
||||
int wait_state = 0;
|
||||
bool zero_on_init = false;
|
||||
bool header_written = false;
|
||||
void *metadata_buffer = NULL;
|
||||
blockstore_meta_header_v2_t *hdr = NULL;
|
||||
blockstore_init_meta_buf bufs[2] = {};
|
||||
int submitted = 0;
|
||||
struct io_uring_sqe *sqe;
|
||||
@@ -29,7 +31,7 @@ class blockstore_init_meta
|
||||
int i = 0, j = 0;
|
||||
std::vector<uint64_t> entries_to_zero;
|
||||
bool handle_meta_block(uint8_t *buf, uint64_t count, uint64_t done_cnt);
|
||||
void handle_event(ring_data_t *data, int buf_num);
|
||||
void handle_event(ring_data_t *data, int buf_num, const char *op);
|
||||
public:
|
||||
blockstore_init_meta(blockstore_impl_t *bs);
|
||||
int loop();
|
||||
|
||||
+145
-54
@@ -101,8 +101,8 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
|
||||
.copy_flags = COPY_BUF_JOURNAL|COPY_BUF_CSUM_FILL,
|
||||
.offset = blk_begin,
|
||||
.len = blk_end-blk_begin,
|
||||
.csum_buf = (csum + (blk_begin/dsk.csum_block_size -
|
||||
item_start/dsk.csum_block_size) * (dsk.data_csum_type & 0xFF)),
|
||||
.csum_buf = (!csum ? NULL : (csum + (blk_begin/dsk.csum_block_size -
|
||||
item_start/dsk.csum_block_size) * (dsk.data_csum_type & 0xFF))),
|
||||
.dyn_data = dyn_data,
|
||||
});
|
||||
if (dyn_data)
|
||||
@@ -134,7 +134,7 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
|
||||
// If we don't track it then we may IN THEORY read another object's data:
|
||||
// submit read -> remove the object -> flush remove -> overwrite with another object -> finish read
|
||||
// Very improbable, but possible
|
||||
PRIV(read_op)->clean_block_used = 1;
|
||||
PRIV(read_op)->clean_loc_used = UINT64_MAX;
|
||||
}
|
||||
rv.insert(rv.begin() + pos, el);
|
||||
fulfilled += el.len;
|
||||
@@ -167,7 +167,8 @@ uint8_t* blockstore_impl_t::get_clean_entry_bitmap(uint64_t block_loc, int offse
|
||||
}
|
||||
|
||||
int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> & rv, uint64_t & fulfilled,
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf, uint64_t read_offset, uint64_t read_end)
|
||||
uint8_t *clean_entry_bitmap, int *dyn_data, bool from_journal, uint8_t *read_buf,
|
||||
uint32_t read_offset, uint32_t read_end, uint32_t item_start, uint32_t item_end)
|
||||
{
|
||||
if (read_end == read_offset)
|
||||
return 0;
|
||||
@@ -175,10 +176,38 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
|
||||
read_buf -= read_offset;
|
||||
uint32_t last_block = (read_end-1)/dsk.csum_block_size;
|
||||
uint32_t start_block = read_offset/dsk.csum_block_size;
|
||||
uint32_t item_start_block = item_start/dsk.csum_block_size;
|
||||
uint32_t end_block = 0;
|
||||
auto zero_range = [&](int pos, bool alloc, uint32_t cur_start, uint32_t cur_end)
|
||||
{
|
||||
if (alloc)
|
||||
return 0;
|
||||
copy_buffer_t el = {
|
||||
.copy_flags = COPY_BUF_ZERO,
|
||||
.offset = cur_start,
|
||||
.len = cur_end-cur_start,
|
||||
};
|
||||
rv.insert(rv.begin() + pos, el);
|
||||
if (read_buf)
|
||||
memset(read_buf + el.offset - read_offset, 0, el.len);
|
||||
fulfilled += el.len;
|
||||
return 1;
|
||||
};
|
||||
if (read_offset < item_start)
|
||||
{
|
||||
// Zero-fill the beginning
|
||||
find_holes(rv, read_offset, item_start, zero_range);
|
||||
read_offset = item_start;
|
||||
}
|
||||
if (read_end > item_end)
|
||||
{
|
||||
// Zero-fill the end
|
||||
find_holes(rv, item_end, read_end, zero_range);
|
||||
read_end = item_end;
|
||||
}
|
||||
while (start_block <= last_block)
|
||||
{
|
||||
if (read_range_fulfilled(rv, fulfilled, read_buf, clean_entry_bitmap,
|
||||
if (read_range_fulfilled(rv, fulfilled, read_buf, from_journal ? NULL : clean_entry_bitmap,
|
||||
start_block*dsk.csum_block_size < read_offset ? read_offset : start_block*dsk.csum_block_size,
|
||||
(start_block+1)*dsk.csum_block_size > read_end ? read_end : (start_block+1)*dsk.csum_block_size))
|
||||
{
|
||||
@@ -190,7 +219,7 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
|
||||
// Find a sequence of checksum blocks required to be read
|
||||
end_block = start_block;
|
||||
while ((end_block+1)*dsk.csum_block_size < read_end &&
|
||||
!read_range_fulfilled(rv, fulfilled, read_buf, clean_entry_bitmap,
|
||||
!read_range_fulfilled(rv, fulfilled, read_buf, from_journal ? NULL : clean_entry_bitmap,
|
||||
(end_block+1)*dsk.csum_block_size < read_offset ? read_offset : (end_block+1)*dsk.csum_block_size,
|
||||
(end_block+2)*dsk.csum_block_size > read_end ? read_end : (end_block+2)*dsk.csum_block_size))
|
||||
{
|
||||
@@ -202,8 +231,10 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
|
||||
.copy_flags = COPY_BUF_CSUM_FILL | (from_journal ? COPY_BUF_JOURNALED_BIG : 0),
|
||||
.offset = start_block*dsk.csum_block_size,
|
||||
.len = (end_block-start_block)*dsk.csum_block_size,
|
||||
// save clean_entry_bitmap if we're reading clean data from the journal
|
||||
.csum_buf = from_journal ? clean_entry_bitmap : NULL,
|
||||
// save checksum reference if we're reading clean data from the journal
|
||||
.csum_buf = from_journal
|
||||
? clean_entry_bitmap + dsk.clean_entry_bitmap_size + (start_block-item_start_block)*(dsk.data_csum_type & 0xFF)
|
||||
: NULL,
|
||||
.dyn_data = dyn_data,
|
||||
});
|
||||
if (dyn_data)
|
||||
@@ -226,6 +257,11 @@ bool blockstore_impl_t::read_range_fulfilled(std::vector<copy_buffer_t> & rv, ui
|
||||
{
|
||||
if (alloc)
|
||||
return 0;
|
||||
if (!clean_entry_bitmap)
|
||||
{
|
||||
all_done = false;
|
||||
return 0;
|
||||
}
|
||||
int diff = 0;
|
||||
uint32_t bmp_start = cur_start/dsk.bitmap_granularity;
|
||||
uint32_t bmp_end = cur_end/dsk.bitmap_granularity;
|
||||
@@ -323,7 +359,7 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
|
||||
{
|
||||
iov[n_iov++] = (struct iovec){ (uint8_t*)op->buf+cur_start-op->offset, lim_end-cur_start };
|
||||
rv.insert(rv.begin() + pos, (copy_buffer_t){
|
||||
.copy_flags = COPY_BUF_DATA,
|
||||
.copy_flags = COPY_BUF_DATA|COPY_BUF_COALESCED,
|
||||
.offset = cur_start,
|
||||
.len = lim_end-cur_start,
|
||||
});
|
||||
@@ -361,10 +397,10 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
|
||||
PRIV(op)->pending_ops++;
|
||||
io_uring_prep_readv(sqe, submit_fd, iov + n_pos, n_cur, submit_offset + clean_loc + item_start + d_pos);
|
||||
data->callback = [this, op](ring_data_t *data) { handle_read_event(data, op); };
|
||||
if (n_pos > 0 || n_pos + IOV_MAX < n_iov)
|
||||
if (n_pos > 0 || n_iov > IOV_MAX)
|
||||
{
|
||||
uint32_t d_len = 0;
|
||||
for (int i = 0; i < IOV_MAX; i++)
|
||||
for (int i = 0; i < n_cur; i++)
|
||||
d_len += iov[n_pos+i].iov_len;
|
||||
data->iov.iov_len = d_len;
|
||||
d_pos += d_len;
|
||||
@@ -376,7 +412,7 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
|
||||
{
|
||||
// Reads running parallel to flushes of the same clean block may read
|
||||
// a mixture of old and new data. So we don't verify checksums for such blocks.
|
||||
PRIV(op)->clean_block_used = 1;
|
||||
PRIV(op)->clean_loc_used = UINT64_MAX;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -402,7 +438,7 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
|
||||
}
|
||||
uint64_t fulfilled = 0;
|
||||
PRIV(read_op)->pending_ops = 0;
|
||||
PRIV(read_op)->clean_block_used = 0;
|
||||
PRIV(read_op)->clean_loc_used = 0;
|
||||
auto & rv = PRIV(read_op)->read_vec;
|
||||
uint64_t result_version = 0;
|
||||
if (dirty_found)
|
||||
@@ -515,26 +551,50 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
|
||||
return 2;
|
||||
undo_read:
|
||||
// need to wait. undo added requests, don't dequeue op
|
||||
if (dsk.csum_block_size > dsk.bitmap_granularity)
|
||||
release_clean(read_op);
|
||||
for (auto & vec: rv)
|
||||
{
|
||||
for (auto & vec: rv)
|
||||
if ((vec.copy_flags & COPY_BUF_CSUM_FILL) && vec.buf)
|
||||
{
|
||||
if ((vec.copy_flags & COPY_BUF_CSUM_FILL) && vec.buf)
|
||||
{
|
||||
free(vec.buf);
|
||||
vec.buf = NULL;
|
||||
}
|
||||
if (vec.dyn_data && --(*vec.dyn_data) == 0) // refcount
|
||||
{
|
||||
free(vec.dyn_data);
|
||||
vec.dyn_data = NULL;
|
||||
}
|
||||
free(vec.buf);
|
||||
vec.buf = NULL;
|
||||
}
|
||||
if (vec.dyn_data && --(*vec.dyn_data) == 0) // refcount
|
||||
{
|
||||
free(vec.dyn_data);
|
||||
vec.dyn_data = NULL;
|
||||
}
|
||||
}
|
||||
rv.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void blockstore_impl_t::release_clean(blockstore_op_t *op)
|
||||
{
|
||||
if (PRIV(op)->clean_loc_used == UINT64_MAX)
|
||||
{
|
||||
PRIV(op)->clean_loc_used = 0;
|
||||
}
|
||||
if (PRIV(op)->clean_loc_used)
|
||||
{
|
||||
// Release clean data block
|
||||
auto uo_it = used_clean_objects.find(PRIV(op)->clean_loc_used - 1);
|
||||
if (uo_it != used_clean_objects.end())
|
||||
{
|
||||
uo_it->second.refs--;
|
||||
if (uo_it->second.refs <= 0)
|
||||
{
|
||||
if (uo_it->second.was_freed)
|
||||
{
|
||||
data_alloc->set((PRIV(op)->clean_loc_used - 1) / dsk.data_block_size, false);
|
||||
}
|
||||
used_clean_objects.erase(uo_it);
|
||||
}
|
||||
}
|
||||
PRIV(op)->clean_loc_used = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int blockstore_impl_t::pad_journal_read(std::vector<copy_buffer_t> & rv, copy_buffer_t & cp,
|
||||
// FIXME Passing dirty_entry& would be nicer
|
||||
uint64_t dirty_offset, uint64_t dirty_end, uint64_t dirty_loc, uint8_t *csum_ptr, int *dyn_data,
|
||||
@@ -598,11 +658,15 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
{
|
||||
auto & rv = PRIV(read_op)->read_vec;
|
||||
int req = fill_partial_checksum_blocks(rv, fulfilled, clean_entry_bitmap, dyn_data, from_journal,
|
||||
(uint8_t*)read_op->buf, read_op->offset, read_op->offset+read_op->len);
|
||||
(uint8_t*)read_op->buf, read_op->offset, read_op->offset+read_op->len, item_start, item_end);
|
||||
if (!inmemory_meta && !from_journal && req > 0)
|
||||
{
|
||||
// Read checksums from disk
|
||||
uint8_t *csum_buf = read_clean_meta_block(read_op, clean_loc, rv.size()-req);
|
||||
if (!csum_buf)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = req; i > 0; i--)
|
||||
{
|
||||
rv[rv.size()-i].csum_buf = csum_buf;
|
||||
@@ -615,7 +679,7 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PRIV(read_op)->clean_block_used = req > 0;
|
||||
PRIV(read_op)->clean_loc_used = req > 0 ? UINT64_MAX : 0;
|
||||
}
|
||||
else if (from_journal)
|
||||
{
|
||||
@@ -665,6 +729,10 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
{
|
||||
// Read checksums from disk
|
||||
csum_buf = read_clean_meta_block(read_op, clean_loc, PRIV(read_op)->read_vec.size());
|
||||
if (!csum_buf)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
csum_done = true;
|
||||
}
|
||||
uint8_t *csum = !dsk.csum_block_size ? 0 : (csum_buf + 2*dsk.clean_entry_bitmap_size + bmp_start*(dsk.data_csum_type & 0xFF));
|
||||
@@ -679,13 +747,13 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
|
||||
}
|
||||
}
|
||||
// Increment reference counter if clean data is being read from the disk
|
||||
if (PRIV(read_op)->clean_block_used)
|
||||
if (PRIV(read_op)->clean_loc_used == UINT64_MAX)
|
||||
{
|
||||
auto & uo = used_clean_objects[clean_loc];
|
||||
uo.refs++;
|
||||
if (dsk.csum_block_size && flusher->is_mutated(clean_loc))
|
||||
uo.was_changed = true;
|
||||
PRIV(read_op)->clean_block_used = clean_loc;
|
||||
PRIV(read_op)->clean_loc_used = clean_loc + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -725,12 +793,18 @@ bool blockstore_impl_t::verify_padded_checksums(uint8_t *clean_entry_bitmap, uin
|
||||
while (pos < iov[i].iov_len)
|
||||
{
|
||||
uint32_t start = pos;
|
||||
uint8_t bit = (clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1;
|
||||
while (pos < iov[i].iov_len && ((clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1) == bit)
|
||||
uint8_t bit = 1;
|
||||
if (clean_entry_bitmap)
|
||||
{
|
||||
pos += dsk.bitmap_granularity;
|
||||
bmp_pos++;
|
||||
bit = (clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1;
|
||||
while (pos < iov[i].iov_len && ((clean_entry_bitmap[bmp_pos >> 3] >> (bmp_pos & 0x7)) & 1) == bit)
|
||||
{
|
||||
pos += dsk.bitmap_granularity;
|
||||
bmp_pos++;
|
||||
}
|
||||
}
|
||||
else
|
||||
pos = iov[i].iov_len;
|
||||
uint32_t len = pos-start;
|
||||
auto buf = (uint8_t*)iov[i].iov_base+start;
|
||||
while (block_done+len >= dsk.csum_block_size)
|
||||
@@ -807,7 +881,7 @@ bool blockstore_impl_t::verify_clean_padded_checksums(blockstore_op_t *op, uint6
|
||||
{
|
||||
uint32_t offset = clean_loc % dsk.data_block_size;
|
||||
if (from_journal)
|
||||
return verify_padded_checksums(dyn_data, dyn_data + dsk.clean_entry_bitmap_size, offset, iov, n_iov, bad_block_cb);
|
||||
return verify_padded_checksums(NULL, dyn_data, offset, iov, n_iov, bad_block_cb);
|
||||
clean_loc = (clean_loc / dsk.data_block_size) * dsk.data_block_size;
|
||||
if (!dyn_data)
|
||||
{
|
||||
@@ -835,7 +909,7 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
|
||||
void *meta_block = NULL;
|
||||
if (dsk.csum_block_size > dsk.bitmap_granularity)
|
||||
{
|
||||
for (int i = rv.size()-1; i >= 0 && (rv[i].copy_flags & COPY_BUF_CSUM_FILL); i--)
|
||||
for (int i = 0; i < rv.size(); i++)
|
||||
{
|
||||
if (rv[i].copy_flags & COPY_BUF_META_BLOCK)
|
||||
{
|
||||
@@ -845,8 +919,41 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
|
||||
rv[i].buf = NULL;
|
||||
continue;
|
||||
}
|
||||
struct iovec *iov = (struct iovec*)((uint8_t*)rv[i].buf + (rv[i].len & 0xFFFFFFFF));
|
||||
int n_iov = rv[i].len >> 32;
|
||||
if (rv[i].copy_flags & COPY_BUF_ZERO)
|
||||
{
|
||||
// Zero read
|
||||
continue;
|
||||
}
|
||||
if (rv[i].copy_flags & COPY_BUF_COALESCED)
|
||||
{
|
||||
// Sub-block shared with another read. Skip
|
||||
continue;
|
||||
}
|
||||
if ((rv[i].copy_flags & COPY_BUF_JOURNAL) && journal.inmemory)
|
||||
{
|
||||
// Do not check journal checksums in-memory
|
||||
continue;
|
||||
}
|
||||
iovec single_iov = {};
|
||||
iovec *iov = NULL;
|
||||
int n_iov = 0;
|
||||
if (rv[i].copy_flags & COPY_BUF_CSUM_FILL)
|
||||
{
|
||||
// Padded, buffer list passed using a 'creepy way'
|
||||
iov = (struct iovec*)((uint8_t*)rv[i].buf + (rv[i].len & 0xFFFFFFFF));
|
||||
n_iov = rv[i].len >> 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not padded, buffer is fully within the input buffer
|
||||
assert(op->buf);
|
||||
assert(rv[i].csum_buf);
|
||||
iov = &single_iov;
|
||||
n_iov = 1;
|
||||
assert(rv[i].offset >= op->offset);
|
||||
assert(rv[i].offset + rv[i].len <= op->offset + op->len);
|
||||
single_iov = { .iov_base = op->buf + rv[i].offset - op->offset, .iov_len = rv[i].len };
|
||||
}
|
||||
bool ok = true;
|
||||
if (rv[i].copy_flags & COPY_BUF_JOURNAL)
|
||||
{
|
||||
@@ -944,23 +1051,7 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
|
||||
meta_block = NULL;
|
||||
}
|
||||
}
|
||||
if (PRIV(op)->clean_block_used)
|
||||
{
|
||||
// Release clean data block
|
||||
auto uo_it = used_clean_objects.find(PRIV(op)->clean_block_used);
|
||||
if (uo_it != used_clean_objects.end())
|
||||
{
|
||||
uo_it->second.refs--;
|
||||
if (uo_it->second.refs <= 0)
|
||||
{
|
||||
if (uo_it->second.was_freed)
|
||||
{
|
||||
data_alloc->set(PRIV(op)->clean_block_used, false);
|
||||
}
|
||||
used_clean_objects.erase(uo_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
release_clean(op);
|
||||
if (!journal.inmemory)
|
||||
{
|
||||
// Release journal sector usage
|
||||
|
||||
@@ -491,7 +491,7 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
|
||||
if (!exists)
|
||||
{
|
||||
uint64_t space_id = dirty_it->first.oid.inode;
|
||||
if (no_inode_stats[dirty_it->first.oid.inode >> (64-POOL_ID_BITS)])
|
||||
if (no_inode_stats.find(dirty_it->first.oid.inode >> (64-POOL_ID_BITS)) != no_inode_stats.end())
|
||||
space_id = space_id & ~(((uint64_t)1 << (64-POOL_ID_BITS)) - 1);
|
||||
inode_space_stats[space_id] += dsk.data_block_size;
|
||||
used_blocks++;
|
||||
@@ -501,7 +501,7 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
|
||||
else if (IS_DELETE(dirty_it->second.state))
|
||||
{
|
||||
uint64_t space_id = dirty_it->first.oid.inode;
|
||||
if (no_inode_stats[dirty_it->first.oid.inode >> (64-POOL_ID_BITS)])
|
||||
if (no_inode_stats.find(dirty_it->first.oid.inode >> (64-POOL_ID_BITS)) != no_inode_stats.end())
|
||||
space_id = space_id & ~(((uint64_t)1 << (64-POOL_ID_BITS)) - 1);
|
||||
auto & sp = inode_space_stats[space_id];
|
||||
if (sp > dsk.data_block_size)
|
||||
|
||||
+37
-14
@@ -3,6 +3,22 @@ cmake_minimum_required(VERSION 2.8...3.30)
|
||||
project(vitastor)
|
||||
|
||||
# libvitastor_common.a
|
||||
add_library(vitastor_common STATIC
|
||||
etcd_state_client.cpp
|
||||
msgr_stop.cpp
|
||||
msgr_op.cpp
|
||||
../../json11/json11.cpp
|
||||
osd_ops.cpp
|
||||
pg_states.cpp
|
||||
../util/allocator.cpp
|
||||
../util/addr_util.cpp
|
||||
../util/timerfd_manager.cpp
|
||||
../util/str_util.cpp
|
||||
../util/json_util.cpp
|
||||
)
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_net.a
|
||||
set(MSGR_RDMA "")
|
||||
if (IBVERBS_LIBRARIES)
|
||||
set(MSGR_RDMA "msgr_rdma.cpp")
|
||||
@@ -11,31 +27,36 @@ set(MSGR_RDMACM "")
|
||||
if (RDMACM_LIBRARIES)
|
||||
set(MSGR_RDMACM "msgr_rdmacm.cpp")
|
||||
endif (RDMACM_LIBRARIES)
|
||||
add_library(vitastor_common STATIC
|
||||
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp msgr_iothread.cpp ../util/addr_util.cpp ../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}
|
||||
add_library(vitastor_net STATIC
|
||||
../util/epoll_manager.cpp
|
||||
etcd_state_client_http.cpp
|
||||
messenger.cpp
|
||||
msgr_iothread.cpp
|
||||
msgr_send.cpp
|
||||
msgr_receive.cpp
|
||||
../util/ringloop.cpp
|
||||
http_client.cpp
|
||||
${MSGR_RDMA}
|
||||
${MSGR_RDMACM}
|
||||
)
|
||||
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES} ${CARES_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
target_compile_options(vitastor_common PUBLIC -fPIC)
|
||||
target_link_libraries(vitastor_net pthread vitastor_common)
|
||||
target_compile_options(vitastor_net PUBLIC -fPIC)
|
||||
|
||||
# libvitastor_client.so
|
||||
add_library(vitastor_client SHARED
|
||||
cluster_client.cpp
|
||||
cluster_client_real.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")
|
||||
target_link_libraries(vitastor_client
|
||||
vitastor_common
|
||||
vitastor_net
|
||||
vitastor_cli
|
||||
${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)
|
||||
@@ -98,11 +119,13 @@ 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 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
|
||||
cluster_client.cpp
|
||||
cluster_client_list.cpp
|
||||
cluster_client_wb.cpp
|
||||
../test/mock/messenger.cpp
|
||||
etcd_state_client_mock.cpp
|
||||
)
|
||||
target_link_libraries(test_cluster_client ${LIBURING_LIBRARIES} ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
|
||||
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
|
||||
target_link_libraries(test_cluster_client vitastor_common ${LIBURING_LIBRARIES})
|
||||
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
|
||||
add_dependencies(build_tests test_cluster_client)
|
||||
add_test(NAME test_cluster_client COMMAND test_cluster_client)
|
||||
|
||||
+109
-155
@@ -11,7 +11,7 @@
|
||||
#define TRY_SEND_CONNECTING 1
|
||||
#define TRY_SEND_OK 2
|
||||
|
||||
cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config)
|
||||
cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config, std::unique_ptr<etcd_state_client_t> st_cli_ptr)
|
||||
{
|
||||
wb = new writeback_cache_t();
|
||||
|
||||
@@ -51,31 +51,32 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
|
||||
msgr.stop_client(op->client_id);
|
||||
delete op;
|
||||
};
|
||||
msgr.parse_config(config, true);
|
||||
msgr.parse_config(config);
|
||||
|
||||
st_cli.tfd = tfd;
|
||||
st_cli.on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
st_cli.on_change_osd_state_hook = [this](uint64_t peer_osd) { on_change_osd_state_hook(peer_osd); };
|
||||
st_cli.on_change_pool_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli.on_change_pg_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli.on_change_pg_state_hook = [this](pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary) { on_change_pg_state_hook(pool_id, pg_num, prev_primary); };
|
||||
st_cli.on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
|
||||
st_cli.on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
|
||||
st_cli.on_reload_hook = [this]() { st_cli.load_global_config(); };
|
||||
st_cli.on_inode_change_hook = [this](uint64_t inode, bool removed) { on_change_inode_hook(inode, removed); };
|
||||
st_cli = std::move(st_cli_ptr);
|
||||
st_cli->on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
|
||||
st_cli->on_change_osd_state_hook = [this](uint64_t peer_osd) { on_change_osd_state_hook(peer_osd); };
|
||||
st_cli->on_change_pool_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli->on_change_pg_config_hook = [this]() { on_change_pool_config_hook(); };
|
||||
st_cli->on_change_pg_state_hook = [this](pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary) { on_change_pg_state_hook(pool_id, pg_num, prev_primary); };
|
||||
st_cli->on_change_node_placement_hook = [this]() { on_change_node_placement_hook(); };
|
||||
st_cli->on_load_pgs_hook = [this](bool success) { on_load_pgs_hook(success); };
|
||||
st_cli->on_reload_hook = [this]() { this->st_cli->load_global_config(); };
|
||||
|
||||
st_cli.parse_config(config);
|
||||
st_cli.infinite_start = false;
|
||||
st_cli->parse_config(config);
|
||||
st_cli->infinite_start = false;
|
||||
if (!config["client_infinite_start"].is_null())
|
||||
{
|
||||
st_cli.infinite_start = config["client_infinite_start"].bool_value();
|
||||
st_cli->infinite_start = config["client_infinite_start"].bool_value();
|
||||
}
|
||||
st_cli.load_global_config();
|
||||
st_cli->load_global_config();
|
||||
|
||||
scrap_buffer_size = SCRAP_BUFFER_SIZE;
|
||||
scrap_buffer = malloc_or_die(scrap_buffer_size);
|
||||
}
|
||||
|
||||
cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
vault_destroy();
|
||||
if (retry_timeout_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(retry_timeout_id);
|
||||
@@ -93,6 +94,7 @@ cluster_client_t::~cluster_client_t()
|
||||
{
|
||||
ringloop->unregister_consumer(&consumer);
|
||||
}
|
||||
free(scrap_buffer);
|
||||
delete wb;
|
||||
wb = NULL;
|
||||
}
|
||||
@@ -467,7 +469,7 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
auto etcd_report_interval = config["etcd_report_interval"].uint64_value();
|
||||
if (!etcd_report_interval)
|
||||
etcd_report_interval = 5;
|
||||
client_wait_up_timeout = 1+etcd_report_interval+(st_cli.max_etcd_attempts*(2*st_cli.etcd_quick_timeout)+999)/1000;
|
||||
client_wait_up_timeout = 1+etcd_report_interval+(st_cli->max_etcd_attempts*(2*st_cli->etcd_quick_timeout)+999)/1000;
|
||||
}
|
||||
// log_level
|
||||
log_level = config["log_level"].uint64_value();
|
||||
@@ -479,11 +481,9 @@ void cluster_client_t::on_load_config_hook(json11::Json::object & etcd_global_co
|
||||
self_tree_metrics.clear();
|
||||
client_hostname = new_hostname;
|
||||
}
|
||||
// vault
|
||||
vault_parse_config();
|
||||
msgr.parse_config(config, false);
|
||||
st_cli.parse_config(config);
|
||||
st_cli.load_pgs();
|
||||
msgr.parse_config(config);
|
||||
st_cli->parse_config(config);
|
||||
st_cli->load_pgs();
|
||||
}
|
||||
|
||||
osd_num_t cluster_client_t::select_random_osd(const std::vector<osd_num_t> & osds)
|
||||
@@ -492,7 +492,7 @@ osd_num_t cluster_client_t::select_random_osd(const std::vector<osd_num_t> & osd
|
||||
int alive_count = 0;
|
||||
for (auto & osd_num: osds)
|
||||
{
|
||||
if (!st_cli.peer_states[osd_num].is_null())
|
||||
if (!st_cli->peer_states[osd_num].is_null())
|
||||
alive_set[alive_count++] = osd_num;
|
||||
}
|
||||
if (!alive_count)
|
||||
@@ -509,7 +509,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
while (self_tree_metrics.find(cur_id) == self_tree_metrics.end())
|
||||
{
|
||||
self_tree_metrics[cur_id] = metric++;
|
||||
json11::Json cur_placement = st_cli.node_placement[cur_id];
|
||||
json11::Json cur_placement = st_cli->node_placement[cur_id];
|
||||
cur_id = cur_placement["parent"].string_value();
|
||||
}
|
||||
if (cur_id != "")
|
||||
@@ -529,7 +529,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
}
|
||||
else
|
||||
{
|
||||
auto & peer_state = st_cli.peer_states[osd_num];
|
||||
auto & peer_state = st_cli->peer_states[osd_num];
|
||||
if (!peer_state.is_null())
|
||||
{
|
||||
metric = self_tree_metrics[""];
|
||||
@@ -539,7 +539,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
while (seen.find(cur_id) == seen.end())
|
||||
{
|
||||
seen.insert(cur_id);
|
||||
json11::Json cur_placement = st_cli.node_placement[cur_id];
|
||||
json11::Json cur_placement = st_cli->node_placement[cur_id];
|
||||
std::string cur_parent = cur_placement["parent"].string_value();
|
||||
cur_id = (!first || cur_parent != "" ? cur_parent : peer_state["host"].string_value());
|
||||
first = false;
|
||||
@@ -564,7 +564,7 @@ osd_num_t cluster_client_t::select_nearest_osd(const std::vector<osd_num_t> & os
|
||||
|
||||
void cluster_client_t::on_load_pgs_hook(bool success)
|
||||
{
|
||||
for (auto & pool_item: st_cli.pool_config)
|
||||
for (auto & pool_item: st_cli->pool_config)
|
||||
{
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
@@ -584,7 +584,7 @@ void cluster_client_t::on_load_pgs_hook(bool success)
|
||||
|
||||
void cluster_client_t::on_change_pool_config_hook()
|
||||
{
|
||||
for (auto & pool_item: st_cli.pool_config)
|
||||
for (auto & pool_item: st_cli->pool_config)
|
||||
{
|
||||
if (pg_counts[pool_item.first] != pool_item.second.real_pg_count)
|
||||
{
|
||||
@@ -607,15 +607,12 @@ void cluster_client_t::on_change_pool_config_hook()
|
||||
pg_counts[pool_item.first] = pool_item.second.real_pg_count;
|
||||
}
|
||||
}
|
||||
inode_cache.clear();
|
||||
inode_cache_children.clear();
|
||||
vault_keys.clear();
|
||||
continue_ops();
|
||||
}
|
||||
|
||||
void cluster_client_t::on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary)
|
||||
{
|
||||
auto & pg_cfg = st_cli.pool_config[pool_id].pg_config[pg_num];
|
||||
auto & pg_cfg = st_cli->pool_config[pool_id].pg_config[pg_num];
|
||||
if (pg_cfg.cur_primary != prev_primary)
|
||||
{
|
||||
// Repeat this PG operations because an OSD which stopped being primary may not fsync operations
|
||||
@@ -633,8 +630,8 @@ bool cluster_client_t::get_immediate_commit(uint64_t inode)
|
||||
pool_id_t pool_id = INODE_POOL(inode);
|
||||
if (!pool_id)
|
||||
return true;
|
||||
auto pool_it = st_cli.pool_config.find(pool_id);
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
auto pool_it = st_cli->pool_config.find(pool_id);
|
||||
if (pool_it == st_cli->pool_config.end())
|
||||
return true;
|
||||
return pool_it->second.immediate_commit == IMMEDIATE_ALL;
|
||||
}
|
||||
@@ -644,7 +641,7 @@ void cluster_client_t::on_change_osd_state_hook(uint64_t peer_osd)
|
||||
osd_tree_metrics.erase(peer_osd);
|
||||
if (msgr.wanted_peers.find(peer_osd) != msgr.wanted_peers.end())
|
||||
{
|
||||
msgr.connect_peer(peer_osd, st_cli.peer_states[peer_osd]);
|
||||
msgr.connect_peer(peer_osd, st_cli->peer_states[peer_osd]);
|
||||
continue_lists();
|
||||
}
|
||||
}
|
||||
@@ -676,10 +673,6 @@ bool cluster_client_t::flush()
|
||||
{
|
||||
if (!ringloop)
|
||||
{
|
||||
if (vault_loading)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (wb->writeback_queue.size())
|
||||
{
|
||||
wb->start_writebacks(this, 0);
|
||||
@@ -702,7 +695,7 @@ bool cluster_client_t::flush()
|
||||
sync_done = true;
|
||||
};
|
||||
execute(sync);
|
||||
while (!sync_done || vault_loading)
|
||||
while (!sync_done)
|
||||
{
|
||||
ringloop->loop();
|
||||
if (!sync_done)
|
||||
@@ -943,8 +936,8 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
auto pool_it = st_cli.pool_config.find(pool_id);
|
||||
if (pool_it == st_cli.pool_config.end() || pool_it->second.real_pg_count == 0)
|
||||
auto pool_it = st_cli->pool_config.find(pool_id);
|
||||
if (pool_it == st_cli->pool_config.end() || pool_it->second.real_pg_count == 0)
|
||||
{
|
||||
// Pools are loaded, but this one is unknown
|
||||
op->retval = -EINVAL;
|
||||
@@ -965,40 +958,10 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
{
|
||||
op->flags |= OP_IMMEDIATE_COMMIT;
|
||||
}
|
||||
bool searched = false;
|
||||
std::shared_ptr<inode_cache_t> icache;
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_WRITE)
|
||||
{
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->has_parent_loop && op->opcode == OSD_OP_READ)
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (icache && icache->op_enc)
|
||||
{
|
||||
// Use shared_ptr aliasing to attach op_enc to the inode cache entry
|
||||
op->enc = std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc);
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
}
|
||||
else
|
||||
op->enc.reset();
|
||||
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
|
||||
{
|
||||
if (!searched)
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache && icache->readonly)
|
||||
auto ino_it = st_cli->inode_config.find(op->inode);
|
||||
if (ino_it != st_cli->inode_config.end() && ino_it->second.readonly)
|
||||
{
|
||||
op->retval = -EROFS;
|
||||
auto cb = std::move(op->callback);
|
||||
@@ -1009,39 +972,33 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||
op->deoptimise_snapshot = false;
|
||||
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
|
||||
{
|
||||
if (!searched)
|
||||
auto ino_it = st_cli->inode_config.find(op->inode);
|
||||
if (ino_it != st_cli->inode_config.end())
|
||||
{
|
||||
icache = inode_cache_get(op->inode);
|
||||
searched = true;
|
||||
}
|
||||
if (icache)
|
||||
{
|
||||
for (auto & parent: icache->chain)
|
||||
int chain_size = 0;
|
||||
while (ino_it != st_cli->inode_config.end() && ino_it->second.parent_id)
|
||||
{
|
||||
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
chain_size > st_cli->inode_config.size())
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
if (INODE_POOL(ino_it->second.parent_id) == INODE_POOL(ino_it->first) &&
|
||||
wb->has_inode(ino_it->second.parent_id))
|
||||
{
|
||||
// Deoptimise reads - we have dirty data for one of the parent layer(s).
|
||||
op->deoptimise_snapshot = true;
|
||||
break;
|
||||
}
|
||||
chain_size++;
|
||||
ino_it = st_cli->inode_config.find(ino_it->second.parent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (icache && icache->err_code)
|
||||
{
|
||||
if (icache->err_code == EPERM)
|
||||
{
|
||||
op->retval = -EPERM;
|
||||
auto cb = std::move(op->callback);
|
||||
cb(op);
|
||||
return false;
|
||||
}
|
||||
else if (icache->err_code == EAGAIN)
|
||||
{
|
||||
key_wait_ops.push_back(op);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1057,7 +1014,7 @@ void cluster_client_t::execute_raw(osd_num_t osd_num, osd_op_t *op)
|
||||
else
|
||||
{
|
||||
if (msgr.wanted_peers.find(osd_num) == msgr.wanted_peers.end())
|
||||
msgr.connect_peer(osd_num, st_cli.peer_states[osd_num]);
|
||||
msgr.connect_peer(osd_num, st_cli->peer_states[osd_num]);
|
||||
raw_ops.emplace(osd_num, op);
|
||||
}
|
||||
}
|
||||
@@ -1171,33 +1128,31 @@ resume_2:
|
||||
}
|
||||
if (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
uint64_t next_inode = 0;
|
||||
auto icache = inode_cache_get(op->cur_inode);
|
||||
if (icache)
|
||||
// Check parent inode
|
||||
auto ino_it = st_cli->inode_config.find(op->cur_inode);
|
||||
// Skip parents from the same pool
|
||||
int skipped = 0;
|
||||
while (!op->deoptimise_snapshot &&
|
||||
ino_it != st_cli->inode_config.end() && ino_it->second.parent_id &&
|
||||
INODE_POOL(ino_it->second.parent_id) == INODE_POOL(op->cur_inode))
|
||||
{
|
||||
if (icache->has_parent_loop)
|
||||
// Check for loops - FIXME check it in etcd_state_client
|
||||
if (ino_it->second.parent_id == op->inode ||
|
||||
skipped > st_cli->inode_config.size())
|
||||
{
|
||||
op->retval = -EINVAL;
|
||||
erase_op(op);
|
||||
return 1;
|
||||
}
|
||||
if (op->deoptimise_snapshot)
|
||||
{
|
||||
if (icache->chain.size() > 1)
|
||||
next_inode = icache->chain[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (icache->other_pool_parent_id)
|
||||
next_inode = icache->other_pool_parent_id;
|
||||
}
|
||||
skipped++;
|
||||
ino_it = st_cli->inode_config.find(ino_it->second.parent_id);
|
||||
}
|
||||
if (next_inode)
|
||||
if (ino_it != st_cli->inode_config.end() &&
|
||||
ino_it->second.parent_id &&
|
||||
ino_it->second.parent_id != op->inode)
|
||||
{
|
||||
// Continue reading from the parent inode
|
||||
icache = inode_cache_get(next_inode);
|
||||
op->cur_inode = next_inode;
|
||||
op->enc = (icache && icache->op_enc ? std::shared_ptr<osd_op_enc_t>(icache, icache->op_enc) : nullptr);
|
||||
op->cur_inode = ino_it->second.parent_id;
|
||||
op->parts.clear();
|
||||
op->done_count = 0;
|
||||
goto resume_0;
|
||||
@@ -1206,7 +1161,7 @@ resume_2:
|
||||
op->retval = op->len;
|
||||
if (op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
|
||||
{
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->inode));
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->inode));
|
||||
op->retval = op->len / pool_cfg.bitmap_granularity;
|
||||
}
|
||||
if (op->flush_id)
|
||||
@@ -1248,7 +1203,7 @@ resume_2:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov)
|
||||
static void add_iov(int size, bool skip, cluster_op_t *op, int &iov_idx, size_t &iov_pos, osd_op_buf_list_t &iov, void *scrap, int scrap_len)
|
||||
{
|
||||
int left = size;
|
||||
while (left > 0 && iov_idx < op->iov.count)
|
||||
@@ -1256,7 +1211,7 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
|
||||
int cur_left = op->iov.buf[iov_idx].iov_len - iov_pos;
|
||||
if (cur_left < left)
|
||||
{
|
||||
if (skip == 0)
|
||||
if (!skip)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, cur_left);
|
||||
}
|
||||
@@ -1266,7 +1221,7 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skip == 0)
|
||||
if (!skip)
|
||||
{
|
||||
iov.push_back((uint8_t*)op->iov.buf[iov_idx].iov_base + iov_pos, left);
|
||||
}
|
||||
@@ -1275,10 +1230,16 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
|
||||
}
|
||||
}
|
||||
assert(left == 0);
|
||||
if (skip == 1)
|
||||
if (skip && scrap_len > 0)
|
||||
{
|
||||
// data read into a NULL buffer will be discarded by messenger
|
||||
iov.push_back(NULL, size);
|
||||
// All skipped ranges are read into the same useless buffer
|
||||
left = size;
|
||||
while (left > 0)
|
||||
{
|
||||
int cur_left = scrap_len < left ? scrap_len : left;
|
||||
iov.push_back(scrap, cur_left);
|
||||
left -= cur_left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1286,7 +1247,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
// Slice the request into individual object stripe requests
|
||||
// Primary OSDs still operate individual stripes, but their size is multiplied by PG minsize in case of EC
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
|
||||
uint64_t first_stripe = (op->offset / pg_block_size) * pg_block_size;
|
||||
@@ -1298,11 +1259,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
// Allocate memory for the bitmap
|
||||
unsigned object_bitmap_size = ((op->len / pool_cfg.bitmap_granularity + 7) / 8);
|
||||
object_bitmap_size = (object_bitmap_size < 8 ? 8 : object_bitmap_size);
|
||||
unsigned bitmap_mem = object_bitmap_size +
|
||||
op->parts.size() * pg_data_size *
|
||||
(pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->enc ? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*op->len/pool_cfg.bitmap_granularity : 0));
|
||||
unsigned bitmap_mem = object_bitmap_size + (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8 * pg_data_size) * op->parts.size();
|
||||
if (!op->bitmap_buf || op->bitmap_buf_size < bitmap_mem)
|
||||
{
|
||||
op->bitmap_buf = realloc_or_die(op->bitmap_buf, bitmap_mem);
|
||||
@@ -1344,10 +1301,10 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
{
|
||||
begin = cur;
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(cur-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
add_iov(cur-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
}
|
||||
skip_prev = skip;
|
||||
prev = cur;
|
||||
@@ -1358,11 +1315,11 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
if (skip_prev)
|
||||
{
|
||||
// Just advance iov_idx & iov_pos
|
||||
add_iov(end-prev, 2, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
add_iov(end-prev, true, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
end = prev;
|
||||
}
|
||||
else
|
||||
add_iov(cur-prev, skip_prev ? 1 : 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
add_iov(cur-prev, skip_prev, op, iov_idx, iov_pos, op->parts[i].iov, scrap_buffer, scrap_buffer_size);
|
||||
if (end == begin)
|
||||
{
|
||||
op->done_count++;
|
||||
@@ -1371,7 +1328,7 @@ void cluster_client_t::slice_rw(cluster_op_t *op)
|
||||
}
|
||||
else if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_DELETE)
|
||||
{
|
||||
add_iov(end-begin, 0, op, iov_idx, iov_pos, op->parts[i].iov);
|
||||
add_iov(end-begin, false, op, iov_idx, iov_pos, op->parts[i].iov, NULL, 0);
|
||||
}
|
||||
op->parts[i].parent = op;
|
||||
op->parts[i].offset = begin;
|
||||
@@ -1389,7 +1346,7 @@ bool cluster_client_t::affects_pg(uint64_t inode, uint64_t offset, uint64_t len,
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(inode));
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(inode));
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
|
||||
uint64_t first_stripe = (offset / pg_block_size) * pg_block_size;
|
||||
@@ -1408,7 +1365,7 @@ bool cluster_client_t::affects_pg(uint64_t inode, uint64_t offset, uint64_t len,
|
||||
|
||||
bool cluster_client_t::affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd)
|
||||
{
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(inode));
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(inode));
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
|
||||
uint64_t first_stripe = (offset / pg_block_size) * pg_block_size;
|
||||
@@ -1432,7 +1389,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
init_msgr();
|
||||
}
|
||||
auto part = &op->parts[i];
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto pg_it = pool_cfg.pg_config.find(part->pg_num);
|
||||
if (pg_it != pool_cfg.pg_config.end() &&
|
||||
!pg_it->second.pause && pg_it->second.cur_primary &&
|
||||
@@ -1457,17 +1414,14 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
osd_client_t *cl = peer_it->second;
|
||||
part->flags |= PART_SENT|PART_VALID;
|
||||
op->inflight_count++;
|
||||
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
|
||||
uint64_t pg_bitmap_size = pg_data_size * (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8
|
||||
// read chain_info - max 4 bytes per block
|
||||
+ (op->opcode == OSD_OP_READ && op->enc
|
||||
? osd_op_rw_t::chain_info_bytes(op->enc->chain_size)*pool_cfg.data_block_size/pool_cfg.bitmap_granularity
|
||||
: 0));
|
||||
uint64_t pg_bitmap_size = (pool_cfg.data_block_size / pool_cfg.bitmap_granularity / 8) * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
uint64_t meta_rev = 0;
|
||||
if (op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_DELETE && !op->deoptimise_snapshot)
|
||||
{
|
||||
auto ino_it = st_cli.inode_config.find(op->cur_inode);
|
||||
if (ino_it != st_cli.inode_config.end())
|
||||
auto ino_it = st_cli->inode_config.find(op->cur_inode);
|
||||
if (ino_it != st_cli->inode_config.end())
|
||||
meta_rev = ino_it->second.mod_revision;
|
||||
}
|
||||
part->op = (osd_op_t){
|
||||
@@ -1481,7 +1435,6 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
.inode = op->cur_inode,
|
||||
.offset = part->offset,
|
||||
.len = part->len,
|
||||
.flags = op->opcode == OSD_OP_READ && op->enc && !op->deoptimise_snapshot ? OSD_OP_RETURN_CHAIN : 0,
|
||||
.meta_revision = meta_rev,
|
||||
.version = op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE ? op->version : 0,
|
||||
} },
|
||||
@@ -1489,7 +1442,6 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
|
||||
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
|
||||
? pg_bitmap_size : 0),
|
||||
.enc = op->enc,
|
||||
.callback = cb ? cb : [this, part](osd_op_t *op_part)
|
||||
{
|
||||
handle_op_part(part);
|
||||
@@ -1501,7 +1453,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
|
||||
}
|
||||
else if (msgr.wanted_peers.find(primary_osd) == msgr.wanted_peers.end())
|
||||
{
|
||||
msgr.connect_peer(primary_osd, st_cli.peer_states[primary_osd]);
|
||||
msgr.connect_peer(primary_osd, st_cli->peer_states[primary_osd]);
|
||||
return TRY_SEND_CONNECTING;
|
||||
}
|
||||
}
|
||||
@@ -1607,9 +1559,6 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
|
||||
}
|
||||
}
|
||||
|
||||
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
|
||||
|
||||
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
cluster_op_t *op = part->parent;
|
||||
@@ -1618,10 +1567,15 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
{
|
||||
// Operation failed, retry
|
||||
part->flags |= PART_ERROR;
|
||||
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
|
||||
if (!op->retval || op->retval == -EPIPE ||
|
||||
part->op.reply.hdr.retval == -ENOSPC && op->retval == -ETIMEDOUT ||
|
||||
part->op.reply.hdr.retval == -EIO)
|
||||
{
|
||||
// Error priority: EIO > ENOSPC > ETIMEDOUT > EPIPE
|
||||
op->retval = part->op.reply.hdr.retval;
|
||||
}
|
||||
uint64_t stop_client_id = 0;
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
|
||||
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC)
|
||||
{
|
||||
stop_client_id = part->op.client_id;
|
||||
if (op->retval != -EPIPE || log_level > 0)
|
||||
@@ -1694,7 +1648,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||
void cluster_client_t::copy_part_bitmap(cluster_op_t *op, cluster_op_part_t *part)
|
||||
{
|
||||
// Copy (OR) bitmap
|
||||
auto & pool_cfg = st_cli.pool_config.at(INODE_POOL(op->cur_inode));
|
||||
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
|
||||
uint32_t pg_block_size = pool_cfg.data_block_size * (
|
||||
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
|
||||
);
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "messenger.h"
|
||||
#include "etcd_state_client.h"
|
||||
#include "../util/robin_hood.h"
|
||||
#include "etcd_state_client_http.h"
|
||||
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_BYTES 32*1024*1024
|
||||
#define DEFAULT_CLIENT_MAX_DIRTY_OPS 1024
|
||||
@@ -72,7 +71,6 @@ protected:
|
||||
cluster_op_t *prev = NULL, *next = NULL;
|
||||
int prev_wait = 0;
|
||||
uint64_t flush_id = 0;
|
||||
std::shared_ptr<osd_op_enc_t> enc;
|
||||
friend class cluster_client_t;
|
||||
friend class writeback_cache_t;
|
||||
};
|
||||
@@ -82,33 +80,14 @@ struct inode_list_osd_t;
|
||||
struct inode_list_pg_t;
|
||||
class writeback_cache_t;
|
||||
|
||||
struct inode_cache_t
|
||||
{
|
||||
std::vector<inode_t> chain; // only parents from the same pool
|
||||
uint8_t *key_data = NULL;
|
||||
osd_op_enc_t *op_enc = NULL;
|
||||
bool readonly = false;
|
||||
bool has_parent_loop = false;
|
||||
inode_t other_pool_parent_id = 0;
|
||||
int err_code = 0;
|
||||
|
||||
~inode_cache_t();
|
||||
};
|
||||
|
||||
struct vault_load_key_t
|
||||
{
|
||||
int key_state = 0;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
// FIXME: Split into public and private interfaces
|
||||
class __attribute__((visibility("default"))) cluster_client_t
|
||||
{
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
ring_loop_t *ringloop = NULL;
|
||||
|
||||
// config:
|
||||
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
std::map<pool_pg_num_t, osd_num_t> pg_primary;
|
||||
// client_max_dirty_* is actually "max unsynced", for the case when immediate_commit is off
|
||||
uint64_t client_max_dirty_bytes = 0;
|
||||
uint64_t client_max_dirty_ops = 0;
|
||||
@@ -120,23 +99,12 @@ class __attribute__((visibility("default"))) cluster_client_t
|
||||
uint64_t client_max_writeback_iodepth = 0;
|
||||
std::string conf_hostname;
|
||||
|
||||
std::string vault_url;
|
||||
std::string vault_client_cert;
|
||||
std::string vault_client_key;
|
||||
std::string vault_ca;
|
||||
std::string vault_secret_api_path;
|
||||
uint64_t vault_timeout_ms = 0;
|
||||
uint64_t vault_error_timeout_sec = 0;
|
||||
uint64_t vault_refresh_leeway_sec = 0;
|
||||
|
||||
int log_level = 0;
|
||||
int client_retry_interval = 50; // ms
|
||||
int client_eio_retry_interval = 1000; // ms
|
||||
bool client_retry_enospc = true;
|
||||
int client_wait_up_timeout = 16; // sec (for listings)
|
||||
|
||||
// state:
|
||||
|
||||
std::string client_hostname;
|
||||
std::map<std::string, int> self_tree_metrics;
|
||||
std::map<osd_num_t, int> osd_tree_metrics;
|
||||
@@ -144,28 +112,15 @@ class __attribute__((visibility("default"))) cluster_client_t
|
||||
int retry_timeout_id = -1;
|
||||
int retry_timeout_duration = 0;
|
||||
std::vector<cluster_op_t*> offline_ops;
|
||||
std::vector<cluster_op_t*> key_wait_ops;
|
||||
cluster_op_t *op_queue_head = NULL, *op_queue_tail = NULL;
|
||||
writeback_cache_t *wb = NULL;
|
||||
std::set<osd_num_t> dirty_osds;
|
||||
uint64_t dirty_bytes = 0, dirty_ops = 0;
|
||||
|
||||
// inodes require some extra state for read/write, it's stored here.
|
||||
// moreover, robin_hood access is slightly faster than std::map :)
|
||||
robin_hood::unordered_flat_map<inode_t, std::shared_ptr<inode_cache_t>> inode_cache;
|
||||
std::set<std::pair<inode_t, inode_t>> inode_cache_children;
|
||||
|
||||
http_context_t *vault_http_ctx = NULL;
|
||||
http_co_t *vault_http_cli = NULL;
|
||||
bool vault_loading = false;
|
||||
std::string vault_token;
|
||||
bool vault_auth_error = false;
|
||||
timespec vault_token_expire = {};
|
||||
std::vector<std::string> vault_key_load_queue;
|
||||
std::map<std::string, vault_load_key_t> vault_keys;
|
||||
void *scrap_buffer = NULL;
|
||||
unsigned scrap_buffer_size = 0;
|
||||
|
||||
bool pgs_loaded = false;
|
||||
std::map<pool_id_t, uint64_t> pg_counts;
|
||||
ring_consumer_t consumer;
|
||||
std::vector<std::function<void(void)>> on_ready_hooks;
|
||||
int list_retry_timeout_id = -1;
|
||||
@@ -176,7 +131,7 @@ class __attribute__((visibility("default"))) cluster_client_t
|
||||
bool msgr_initialized = false;
|
||||
|
||||
public:
|
||||
etcd_state_client_t st_cli;
|
||||
std::unique_ptr<etcd_state_client_t> st_cli;
|
||||
|
||||
osd_messenger_t msgr;
|
||||
void init_msgr();
|
||||
@@ -184,7 +139,8 @@ public:
|
||||
json11::Json::object cli_config, file_config, etcd_global_config;
|
||||
json11::Json::object config;
|
||||
|
||||
cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config);
|
||||
static cluster_client_t* create(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config);
|
||||
cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config, std::unique_ptr<etcd_state_client_t> st_cli);
|
||||
~cluster_client_t();
|
||||
void execute(cluster_op_t *op);
|
||||
void execute_raw(osd_num_t osd_num, osd_op_t *op);
|
||||
@@ -200,13 +156,6 @@ public:
|
||||
protected:
|
||||
void continue_ops(int time_passed = 0);
|
||||
|
||||
std::shared_ptr<inode_cache_t> inode_cache_get(inode_t ino);
|
||||
void vault_parse_config();
|
||||
bool vault_check_token();
|
||||
void vault_load_keys();
|
||||
void vault_destroy();
|
||||
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
|
||||
|
||||
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
|
||||
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
|
||||
|
||||
@@ -216,7 +165,6 @@ protected:
|
||||
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
|
||||
void on_change_osd_state_hook(uint64_t peer_osd);
|
||||
void on_change_node_placement_hook();
|
||||
void on_change_inode_hook(uint64_t inode, bool removed);
|
||||
|
||||
void execute_internal(cluster_op_t *op);
|
||||
void execute_cas(cluster_op_t *op);
|
||||
@@ -233,7 +181,6 @@ protected:
|
||||
void erase_op(cluster_op_t *op);
|
||||
void calc_wait(cluster_op_t *op);
|
||||
void inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *next, int inc);
|
||||
|
||||
void continue_lists();
|
||||
bool continue_listing(inode_list_t *lst);
|
||||
bool restart_listing(inode_list_t* lst);
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
#include "cluster_client_impl.h"
|
||||
#include "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,6 +5,7 @@
|
||||
|
||||
#include "cluster_client.h"
|
||||
|
||||
#define SCRAP_BUFFER_SIZE 4*1024*1024
|
||||
#define PART_SENT 1
|
||||
#define PART_DONE 2
|
||||
#define PART_ERROR 4
|
||||
|
||||
@@ -63,14 +63,14 @@ void cluster_client_t::list_inode(inode_t inode, uint64_t min_offset, uint64_t m
|
||||
{
|
||||
init_msgr();
|
||||
pool_id_t pool_id = INODE_POOL(inode);
|
||||
if (!pool_id || st_cli.pool_config.find(pool_id) == st_cli.pool_config.end())
|
||||
if (!pool_id || st_cli->pool_config.find(pool_id) == st_cli->pool_config.end())
|
||||
{
|
||||
if (log_level > 0)
|
||||
fprintf(stderr, "Pool %u does not exist\n", pool_id);
|
||||
pg_callback(-EINVAL, 0, 0, std::set<object_id>());
|
||||
return;
|
||||
}
|
||||
auto pg_stripe_size = st_cli.pool_config.at(pool_id).pg_stripe_size;
|
||||
auto pg_stripe_size = st_cli->pool_config.at(pool_id).pg_stripe_size;
|
||||
if (min_offset)
|
||||
min_offset = (min_offset/pg_stripe_size) * pg_stripe_size;
|
||||
inode_list_t *lst = new inode_list_t();
|
||||
@@ -110,13 +110,13 @@ bool cluster_client_t::continue_listing(inode_list_t *lst)
|
||||
|
||||
bool cluster_client_t::restart_listing(inode_list_t* lst)
|
||||
{
|
||||
auto pool_it = st_cli.pool_config.find(lst->pool_id);
|
||||
auto pool_it = st_cli->pool_config.find(lst->pool_id);
|
||||
// We want listing to be consistent. To achieve it we should:
|
||||
// 1) retry listing of each PG if its state changes
|
||||
// 2) abort listing if PG count changes during listing
|
||||
// 3) ideally, only talk to the primary OSD - this will be done separately
|
||||
// So first we add all PGs without checking their state
|
||||
if (pool_it == st_cli.pool_config.end() ||
|
||||
if (pool_it == st_cli->pool_config.end() ||
|
||||
lst->real_pg_count != pool_it->second.real_pg_count)
|
||||
{
|
||||
for (auto pg: lst->pgs)
|
||||
@@ -136,7 +136,7 @@ bool cluster_client_t::restart_listing(inode_list_t* lst)
|
||||
fprintf(stderr, "PG count in pool %u changed during listing\n", lst->pool_id);
|
||||
}
|
||||
lst->pgs.clear();
|
||||
if (pool_it == st_cli.pool_config.end())
|
||||
if (pool_it == st_cli->pool_config.end())
|
||||
{
|
||||
// Unknown pool
|
||||
lst->callback(-EINVAL, 0, 0, std::set<object_id>());
|
||||
@@ -248,7 +248,7 @@ void cluster_client_t::set_list_retry_timeout(int ms, timespec new_time)
|
||||
|
||||
int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
{
|
||||
auto & pool_cfg = st_cli.pool_config.at(pg->lst->pool_id);
|
||||
auto & pool_cfg = st_cli->pool_config.at(pg->lst->pool_id);
|
||||
auto pg_it = pool_cfg.pg_config.find(pg->pg_num);
|
||||
assert(pg->lst->real_pg_count == pool_cfg.real_pg_count);
|
||||
if (pg_it == pool_cfg.pg_config.end() ||
|
||||
@@ -277,7 +277,7 @@ int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
for (auto peer_it = all_peers.begin(); peer_it != all_peers.end(); )
|
||||
{
|
||||
if (*peer_it != pg_it->second.cur_primary &&
|
||||
st_cli.peer_states[*peer_it].is_null())
|
||||
st_cli->peer_states[*peer_it].is_null())
|
||||
{
|
||||
pg->inactive_osds.push_back(*peer_it);
|
||||
all_peers.erase(peer_it++);
|
||||
@@ -298,11 +298,11 @@ int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
|
||||
if (msgr.osd_peers.find(peer_osd) == msgr.osd_peers.end())
|
||||
{
|
||||
// Initiate connection
|
||||
if (st_cli.peer_states[peer_osd].is_null())
|
||||
if (st_cli->peer_states[peer_osd].is_null())
|
||||
{
|
||||
return LIST_PG_WAIT_ACTIVE;
|
||||
}
|
||||
msgr.connect_peer(peer_osd, st_cli.peer_states[peer_osd]);
|
||||
msgr.connect_peer(peer_osd, st_cli->peer_states[peer_osd]);
|
||||
conn = false;
|
||||
}
|
||||
}
|
||||
@@ -336,7 +336,7 @@ void cluster_client_t::send_list(inode_list_osd_t *cur_list)
|
||||
if (!cur_list->pg->inflight_ops)
|
||||
cur_list->pg->lst->inflight_pgs++;
|
||||
cur_list->pg->inflight_ops++;
|
||||
auto & pool_cfg = st_cli.pool_config[cur_list->pg->lst->pool_id];
|
||||
auto & pool_cfg = st_cli->pool_config[cur_list->pg->lst->pool_id];
|
||||
osd_op_t *op = new osd_op_t();
|
||||
op->op_type = OSD_OP_OUT;
|
||||
// Already checked that it exists above, but anyway
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include "cluster_client.h"
|
||||
#include "etcd_state_client_http.h"
|
||||
|
||||
cluster_client_t* cluster_client_t::create(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config)
|
||||
{
|
||||
auto st_cli = new etcd_state_client_http_t(tfd);
|
||||
return new cluster_client_t(ringloop, tfd, config, std::unique_ptr<etcd_state_client_t>(st_cli));
|
||||
}
|
||||
@@ -131,6 +131,7 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
writeback_bytes -= op->len;
|
||||
}
|
||||
writeback_queue_size++;
|
||||
writeback_queue.push_back({ op->inode, new_end });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -165,6 +166,7 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
|
||||
{
|
||||
writeback_queue_size++;
|
||||
}
|
||||
writeback_queue.push_back({ op->inode, new_end });
|
||||
}
|
||||
auto new_dirty_it = dirty_buffers.emplace_hint(dirty_it, (object_id){
|
||||
.inode = op->inode,
|
||||
|
||||
@@ -2,17 +2,12 @@
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "malloc_or_die.h"
|
||||
|
||||
#include "osd_ops.h"
|
||||
#include "msgr_op.h"
|
||||
#include "pg_states.h"
|
||||
#include "etcd_state_client.h"
|
||||
#ifndef __MOCK__
|
||||
#include "addr_util.h"
|
||||
#include "http_client.h"
|
||||
#endif
|
||||
#include "str_util.h"
|
||||
#include "json_util.h"
|
||||
|
||||
etcd_state_client_t::~etcd_state_client_t()
|
||||
{
|
||||
@@ -21,33 +16,8 @@ etcd_state_client_t::~etcd_state_client_t()
|
||||
delete watch;
|
||||
}
|
||||
watches.clear();
|
||||
etcd_watches_initialised = -1;
|
||||
#ifndef __MOCK__
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_destroy(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (keepalive_client)
|
||||
{
|
||||
http_destroy(keepalive_client);
|
||||
keepalive_client = NULL;
|
||||
}
|
||||
if (http_ctx)
|
||||
{
|
||||
http_context_destroy(http_ctx);
|
||||
http_ctx = NULL;
|
||||
}
|
||||
#endif
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __MOCK__
|
||||
etcd_kv_t etcd_state_client_t::parse_etcd_kv(const json11::Json & kv_json)
|
||||
{
|
||||
etcd_kv_t kv;
|
||||
@@ -81,186 +51,48 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
|
||||
return addrs;
|
||||
}
|
||||
|
||||
std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & username)
|
||||
void etcd_state_client_t::add_etcd_url(std::string addr)
|
||||
{
|
||||
auto user_it = user_info.find(username);
|
||||
if (user_it != user_info.end())
|
||||
if (addr.length() > 0)
|
||||
{
|
||||
return user_it->second;
|
||||
}
|
||||
auto inf = std::make_shared<user_info_t>();
|
||||
inf->name = username;
|
||||
return inf;
|
||||
}
|
||||
|
||||
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
|
||||
{
|
||||
if (user_info->type == user_type_t::ADMIN)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto cache_it = user_info->perm_cache.find(inode_num);
|
||||
if (cache_it != user_info->perm_cache.end() &&
|
||||
cache_it->second.mod_revision == user_perm_cache_revision)
|
||||
{
|
||||
return write ? (cache_it->second.perm == user_perm_t::OWNER) : (cache_it->second.perm != user_perm_t::DENY);
|
||||
}
|
||||
auto inode_it = inode_config.find(inode_num);
|
||||
if (inode_it == inode_config.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// FIXME Implement cache reset after reworking etcd interaction to not keep everything in memory
|
||||
auto & perm_item = user_info->perm_cache[inode_num];
|
||||
perm_item.mod_revision = user_perm_cache_revision;
|
||||
perm_item.perm = (user_info->name == inode_it->second.owner || inode_it->second.owner_group != "" &&
|
||||
user_info->groups.find(inode_it->second.owner_group) != user_info->groups.end()
|
||||
? user_perm_t::OWNER : (inode_it->second.reader_group != "" &&
|
||||
user_info->groups.find(inode_it->second.reader_group) != user_info->groups.end()
|
||||
? user_perm_t::READER : user_perm_t::DENY));
|
||||
return write ? (perm_item.perm == user_perm_t::OWNER) : (perm_item.perm != user_perm_t::DENY);
|
||||
}
|
||||
|
||||
http_context_t *etcd_state_client_t::get_http_ctx()
|
||||
{
|
||||
if (!http_ctx)
|
||||
{
|
||||
std::string error;
|
||||
http_ctx = http_context_init(tfd, etcd_client_cert, etcd_client_key, etcd_ca, true, error);
|
||||
if (!http_ctx)
|
||||
if (strtolower(addr.substr(0, 7)) == "http://")
|
||||
addr = addr.substr(7);
|
||||
else if (strtolower(addr.substr(0, 8)) == "https://")
|
||||
{
|
||||
fprintf(stderr, "Failed to initialize HTTP context: %s\n", error.c_str());
|
||||
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return http_ctx;
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_oneshot(const std::string & etcd_url, const std::string & api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
auto http_cli = http_init(get_http_ctx());
|
||||
http_json_post(http_cli, etcd_url+api, payload, "", { .timeout = timeout }, [http_cli, callback](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
callback(err, data);
|
||||
http_destroy(http_cli);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
pick_next_etcd([=]()
|
||||
{
|
||||
etcd_call_selected(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::etcd_call_selected(const std::string & api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+url.path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+url.hostname+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: keep-alive\r\n"
|
||||
"Keep-Alive: timeout="+std::to_string(etcd_keepalive_timeout)+"\r\n"
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
cur_addr = url.addr](http_message_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Warning: etcd request failed: %s, retrying %d more times\n",
|
||||
err.c_str(), retries
|
||||
);
|
||||
}
|
||||
if (interval > 0)
|
||||
{
|
||||
// FIXME: Prevent destruction of etcd_state_client if timers or requests are active
|
||||
tfd->set_timer(interval, false, [this, api, payload, timeout, retries, interval, callback](int)
|
||||
{
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
else
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
keepalive_client = http_init(get_http_ctx());
|
||||
http_request(keepalive_client, url.addr, req, { .timeout = timeout, .keepalive = true, .ssl = url.ssl }, cb);
|
||||
}
|
||||
|
||||
|
||||
void etcd_state_client_t::add_etcd_url(std::string etcd_address)
|
||||
{
|
||||
if (etcd_address.size() > 0)
|
||||
{
|
||||
if (!local_ips.size())
|
||||
{
|
||||
// Fill local_ips
|
||||
for (auto & ip: getifaddr_list(std::vector<addr_mask_t>(), true))
|
||||
local_ips.insert(ip);
|
||||
}
|
||||
std::string etcd_api_path;
|
||||
bool ssl = false;
|
||||
if (etcd_address.substr(0, 8) == "https://")
|
||||
{
|
||||
ssl = true;
|
||||
etcd_address = etcd_address.substr(8);
|
||||
}
|
||||
else if (etcd_address.substr(0, 7) == "http://")
|
||||
etcd_address = etcd_address.substr(7);
|
||||
auto pos = etcd_address.find('/');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
|
||||
std::string check_addr;
|
||||
int pos = addr.find('/');
|
||||
int pos2 = addr.find(':');
|
||||
if (pos2 >= 0)
|
||||
check_addr = addr.substr(0, pos2);
|
||||
else if (pos >= 0)
|
||||
check_addr = addr.substr(0, pos);
|
||||
else
|
||||
etcd_api_path = "/v3";
|
||||
pos = etcd_address.find(':');
|
||||
auto check_addr = (pos != std::string::npos ? etcd_address.substr(0, pos) : etcd_address);
|
||||
bool is_local = local_ips.find(check_addr) != local_ips.end();
|
||||
auto & to = (is_local ? etcd_local : etcd_addresses);
|
||||
check_addr = (ssl ? "https://" : "http://") + etcd_address + etcd_api_path;
|
||||
size_t i;
|
||||
check_addr = addr;
|
||||
if (pos == std::string::npos)
|
||||
addr += "/v3";
|
||||
bool local = false;
|
||||
int i;
|
||||
for (i = 0; i < local_ips.size(); i++)
|
||||
{
|
||||
if (local_ips[i] == check_addr)
|
||||
{
|
||||
local = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto & to = local ? this->etcd_local : this->etcd_addresses;
|
||||
for (i = 0; i < to.size(); i++)
|
||||
{
|
||||
if (to[i] == check_addr)
|
||||
if (to[i] == addr)
|
||||
break;
|
||||
}
|
||||
if (i >= to.size())
|
||||
{
|
||||
to.push_back(check_addr);
|
||||
// Check if it's a domain name
|
||||
sockaddr_storage ss;
|
||||
bool is_name = !is_local && !string_to_addr(etcd_address, true, 0, &ss);
|
||||
auto & to_addr = (is_local ? etcd_local_addr_urls : (is_name ? etcd_name_urls : etcd_nonlocal_addr_urls));
|
||||
to_addr.push_back((http_url_t){ .ssl = ssl, .addr = etcd_address, .hostname = etcd_address, .path = etcd_api_path });
|
||||
}
|
||||
to.push_back(addr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,9 +100,6 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_local.clear();
|
||||
this->etcd_addresses.clear();
|
||||
this->etcd_local_addr_urls.clear();
|
||||
this->etcd_nonlocal_addr_urls.clear();
|
||||
this->etcd_name_urls.clear();
|
||||
if (config["etcd_address"].is_string())
|
||||
{
|
||||
std::string ea = config["etcd_address"].string_value();
|
||||
@@ -291,22 +120,6 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
add_etcd_url(ea.string_value());
|
||||
}
|
||||
}
|
||||
if (this->etcd_client_cert != "")
|
||||
{
|
||||
this->etcd_client_cert = config["etcd_client_cert"].string_value();
|
||||
this->etcd_client_key = config["etcd_client_key"].string_value();
|
||||
}
|
||||
else if (this->osd_num)
|
||||
{
|
||||
this->etcd_client_cert = config["osd_cert"].string_value();
|
||||
this->etcd_client_key = config["osd_pkey"].string_value();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->etcd_client_cert = config["cert"].string_value();
|
||||
this->etcd_client_key = config["pkey"].string_value();
|
||||
}
|
||||
this->etcd_ca = config["etcd_ca"].string_value();
|
||||
this->etcd_prefix = config["etcd_prefix"].string_value();
|
||||
if (this->etcd_prefix == "")
|
||||
{
|
||||
@@ -324,7 +137,6 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
if (this->etcd_keepalive_timeout < 30)
|
||||
this->etcd_keepalive_timeout = 30;
|
||||
}
|
||||
auto old_etcd_ws_keepalive_interval = this->etcd_ws_keepalive_interval;
|
||||
this->etcd_ws_keepalive_interval = config["etcd_ws_keepalive_interval"].uint64_value();
|
||||
if (this->etcd_ws_keepalive_interval <= 0)
|
||||
{
|
||||
@@ -350,347 +162,9 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
this->etcd_min_reload_interval = 50;
|
||||
}
|
||||
if (this->etcd_ws_keepalive_interval != old_etcd_ws_keepalive_interval && ws_keepalive_timer >= 0)
|
||||
{
|
||||
#ifndef __MOCK__
|
||||
stop_ws_keepalive();
|
||||
start_ws_keepalive();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::pick_next_etcd(std::function<void()> cb)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
if (selected_etcd_url.addr != "")
|
||||
{
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
if (etcd_urls_to_try.size() != 0)
|
||||
{
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
on_resolve_queue.push_back(std::move(cb));
|
||||
if (on_resolve_queue.size() > 1)
|
||||
{
|
||||
// Already resolving
|
||||
return;
|
||||
}
|
||||
assert(!resolve_count);
|
||||
local_to_try = 0;
|
||||
for (auto & url: etcd_local_addr_urls)
|
||||
{
|
||||
// Prefer local IPs, if any
|
||||
etcd_urls_to_try.push_back(url);
|
||||
local_to_try++;
|
||||
}
|
||||
for (auto & url: etcd_nonlocal_addr_urls)
|
||||
{
|
||||
etcd_urls_to_try.push_back(url);
|
||||
}
|
||||
resolve_count++;
|
||||
for (auto & url: etcd_name_urls)
|
||||
{
|
||||
resolve_count++;
|
||||
http_resolve(get_http_ctx(), url.ssl, url.addr, [this, url](const std::string & error, const std::vector<std::string>& addresses)
|
||||
{
|
||||
if (error != "")
|
||||
fprintf(stderr, "Error resolving %s: %s\n", url.addr.c_str(), error.c_str());
|
||||
for (auto & addr: addresses)
|
||||
{
|
||||
auto url_copy = url;
|
||||
url_copy.addr = addr;
|
||||
if (local_ips.find(addr) != local_ips.end())
|
||||
{
|
||||
etcd_urls_to_try.insert(etcd_urls_to_try.begin(), std::move(url_copy));
|
||||
local_to_try++;
|
||||
}
|
||||
else
|
||||
etcd_urls_to_try.push_back(std::move(url_copy));
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
pick_next_etcd_on_resolve();
|
||||
});
|
||||
}
|
||||
resolve_count--;
|
||||
if (!resolve_count)
|
||||
{
|
||||
pick_next_etcd_on_resolve();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::pick_next_etcd_on_resolve()
|
||||
{
|
||||
if (!etcd_urls_to_try.size())
|
||||
{
|
||||
fprintf(stderr, "None of etcd_address could be resolved\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
// Shuffle addresses
|
||||
for (size_t i = etcd_urls_to_try.size()-1; i > local_to_try; i--)
|
||||
{
|
||||
size_t j = local_to_try + lrand48() % (i - local_to_try);
|
||||
if (j != i)
|
||||
std::swap(etcd_urls_to_try[i], etcd_urls_to_try[j]);
|
||||
}
|
||||
selected_etcd_url = std::move(etcd_urls_to_try[0]);
|
||||
etcd_urls_to_try.erase(etcd_urls_to_try.begin());
|
||||
auto cbs = std::move(on_resolve_queue);
|
||||
for (auto cb: cbs)
|
||||
{
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher()
|
||||
{
|
||||
pick_next_etcd([this]()
|
||||
{
|
||||
start_etcd_watcher_selected();
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_etcd_watcher_selected()
|
||||
{
|
||||
const auto & url = selected_etcd_url;
|
||||
etcd_watches_initialised = 0;
|
||||
ws_alive = 1;
|
||||
if (this->log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s%s%s (hostname %s), watch from revision %ju/%ju/%ju\n",
|
||||
url.ssl ? "https://" : "http://", url.addr.c_str(), url.path.c_str(), url.hostname.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
if (!etcd_watch_ws)
|
||||
etcd_watch_ws = http_init(get_http_ctx());
|
||||
else
|
||||
http_close(etcd_watch_ws);
|
||||
open_websocket(etcd_watch_ws, url.addr, url.hostname, url.path+"/watch", { .timeout = etcd_slow_timeout, .ssl = url.ssl },
|
||||
[this, cur_addr = url.addr](http_message_t *msg)
|
||||
{
|
||||
if (msg->body.length())
|
||||
{
|
||||
ws_alive = 1;
|
||||
std::string json_err;
|
||||
json11::Json data = json11::Json::parse(msg->body, json_err);
|
||||
if (json_err != "")
|
||||
{
|
||||
fprintf(stderr, "Bad JSON in etcd event: %s, ignoring event\n", json_err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t watch_id = data["result"]["watch_id"].uint64_value();
|
||||
if (data["result"]["created"].bool_value())
|
||||
{
|
||||
if (watch_id == ETCD_CONFIG_WATCH_ID ||
|
||||
watch_id == ETCD_PG_STATE_WATCH_ID ||
|
||||
watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
{
|
||||
etcd_watches_initialised++;
|
||||
}
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Successfully subscribed to etcd at %s, revision %ju/%ju/%ju\n", cur_addr.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
}
|
||||
if (data["result"]["canceled"].bool_value())
|
||||
{
|
||||
// etcd watch canceled, maybe because the revision was compacted
|
||||
if (data["result"]["compact_revision"].uint64_value())
|
||||
{
|
||||
// we may miss events if we proceed
|
||||
// so we should restart from the beginning if we can
|
||||
if (on_reload_hook != NULL)
|
||||
{
|
||||
// check to not trigger on_reload_hook multiple times
|
||||
if (etcd_watch_ws != NULL)
|
||||
{
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, exiting\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Watch canceled by etcd, reason: %s, exiting\n", data["result"]["cancel_reason"].string_value().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
|
||||
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
|
||||
// Also don't save revision from the initial created: true messages because they always contain the latest revision
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES &&
|
||||
!data["result"]["header"]["revision"].is_null() &&
|
||||
!data["result"]["created"].bool_value())
|
||||
{
|
||||
// Restart watchers from the same revision number as in the last received message,
|
||||
// not from the next one to protect against revision being split into multiple messages,
|
||||
// even though etcd guarantees not to do that **within a single watcher** without fragment=true:
|
||||
// https://etcd.io/docs/v3.5/learning/api_guarantees/#watch-apis
|
||||
// Revision contents are ALWAYS split into separate messages for different watchers though!
|
||||
// So generally we have to resume each watcher from its own revision...
|
||||
// Progress messages may have watch_id=-1 if sent on behalf of multiple watchers though.
|
||||
// And antietcd has an advanced semantic which merges the same revision for all watchers
|
||||
// into one message and just omits watch_id.
|
||||
// So we also have to handle the case where watch_id is -1 or not present (0).
|
||||
auto watch_rev = data["result"]["header"]["revision"].uint64_value();
|
||||
if (!watch_id || watch_id == UINT64_MAX)
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_CONFIG_WATCH_ID)
|
||||
etcd_watch_revision_config = watch_rev;
|
||||
else if (watch_id == ETCD_PG_STATE_WATCH_ID)
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
etcd_urls_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
for (auto & ev: data["result"]["events"].array_items())
|
||||
{
|
||||
auto kv = parse_etcd_kv(ev["kv"]);
|
||||
if (kv.key != "")
|
||||
{
|
||||
changes[kv.key] = kv;
|
||||
}
|
||||
}
|
||||
for (auto & kv: changes)
|
||||
{
|
||||
if (this->log_level > 3)
|
||||
{
|
||||
fprintf(stderr, "Incoming event: %s -> %s\n", kv.first.c_str(), kv.second.value.dump().c_str());
|
||||
}
|
||||
parse_state(kv.second);
|
||||
}
|
||||
// React to changes
|
||||
if (on_change_hook != NULL)
|
||||
{
|
||||
on_change_hook(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
if (cur_addr == selected_etcd_url.addr)
|
||||
selected_etcd_url = (http_url_t){};
|
||||
if (etcd_watches_initialised == 0)
|
||||
{
|
||||
// Connection not established, retry in <etcd_quick_timeout>
|
||||
tfd->set_timer(etcd_quick_timeout, false, [this](int)
|
||||
{
|
||||
start_etcd_watcher();
|
||||
});
|
||||
}
|
||||
else if (etcd_watches_initialised > 0)
|
||||
{
|
||||
// Connection was live, retry immediately
|
||||
etcd_watches_initialised = 0;
|
||||
start_etcd_watcher();
|
||||
}
|
||||
}
|
||||
});
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
{ "start_revision", etcd_watch_revision_config },
|
||||
{ "watch_id", ETCD_CONFIG_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/osd/state/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/osd/state0") },
|
||||
{ "start_revision", etcd_watch_revision_osd },
|
||||
{ "watch_id", ETCD_OSD_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/pg0") },
|
||||
{ "start_revision", etcd_watch_revision_pg },
|
||||
{ "watch_id", ETCD_PG_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
// FIXME: Do not watch /pg/history/ at all in client code (not in OSD)
|
||||
if (on_start_watcher_hook)
|
||||
{
|
||||
on_start_watcher_hook(etcd_watch_ws);
|
||||
}
|
||||
start_ws_keepalive();
|
||||
}
|
||||
|
||||
void etcd_state_client_t::stop_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer >= 0)
|
||||
{
|
||||
tfd->clear_timer(ws_keepalive_timer);
|
||||
ws_keepalive_timer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::start_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer < 0)
|
||||
{
|
||||
ws_keepalive_timer = tfd->set_timer(etcd_ws_keepalive_interval*1000, true, [this](int)
|
||||
{
|
||||
if (!etcd_watch_ws || etcd_watches_initialised < ETCD_TOTAL_WATCHES)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (!ws_alive)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_url.addr.c_str());
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
else
|
||||
{
|
||||
ws_alive = 0;
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "progress_request", json11::Json::object { } }
|
||||
}).dump());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_global_config()
|
||||
void etcd_state_client_t::load_global_config(std::function<void(const std::string & error)> cb)
|
||||
{
|
||||
json11::Json::object req = { { "success", json11::Json::array {
|
||||
json11::Json::object {
|
||||
@@ -704,22 +178,12 @@ void etcd_state_client_t::load_global_config()
|
||||
} }
|
||||
},
|
||||
} } };
|
||||
etcd_txn(req, etcd_quick_timeout, max_etcd_attempts, 0, [this](std::string err, json11::Json data)
|
||||
etcd_txn(req, etcd_quick_timeout, max_etcd_attempts, 0, [this, cb](std::string err, json11::Json data)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
fprintf(stderr, "Error reading configuration from etcd: %s\n", err.c_str());
|
||||
if (infinite_start)
|
||||
{
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_global_config();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
json11::Json config_kv = data["responses"][0]["response_range"]["kvs"][0];
|
||||
@@ -750,33 +214,21 @@ void etcd_state_client_t::load_global_config()
|
||||
parse_state(kv);
|
||||
}
|
||||
on_load_config_hook(global_config);
|
||||
cb("");
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_pgs()
|
||||
void etcd_state_client_t::load_pgs(std::function<void(const std::string &)> cb)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
uint64_t ms_passed = (tv.tv_sec-etcd_last_reload.tv_sec)*1000 + (tv.tv_nsec-etcd_last_reload.tv_nsec)/1000000;
|
||||
if (ms_passed < etcd_min_reload_interval)
|
||||
{
|
||||
if (load_pgs_timer_id < 0)
|
||||
{
|
||||
load_pgs_timer_id = tfd->set_timer(etcd_min_reload_interval+50-ms_passed, false, [this](int) { load_pgs(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
etcd_last_reload = tv;
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
json11::Json::array txn = {
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pools") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/pgs") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
@@ -784,6 +236,12 @@ void etcd_state_client_t::load_pgs()
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/config") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/inode/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config/inode0") },
|
||||
} }
|
||||
},
|
||||
json11::Json::object {
|
||||
{ "request_range", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/history/") },
|
||||
@@ -809,16 +267,13 @@ void etcd_state_client_t::load_pgs()
|
||||
{
|
||||
req["compare"] = checks;
|
||||
}
|
||||
etcd_txn_slow(req, [this](std::string err, json11::Json data)
|
||||
etcd_txn_slow(req, [this, cb](std::string err, json11::Json data)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
// Retry indefinitely
|
||||
fprintf(stderr, "Error loading PGs from etcd: %s\n", err.c_str());
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_pgs();
|
||||
});
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
if (!data["succeeded"].bool_value())
|
||||
@@ -846,24 +301,9 @@ void etcd_state_client_t::load_pgs()
|
||||
}
|
||||
clean_nonexistent_pgs();
|
||||
on_load_pgs_hook(true);
|
||||
start_etcd_watcher();
|
||||
cb("");
|
||||
});
|
||||
}
|
||||
#else
|
||||
void etcd_state_client_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_global_config()
|
||||
{
|
||||
json11::Json::object global_config;
|
||||
on_load_config_hook(global_config);
|
||||
}
|
||||
|
||||
void etcd_state_client_t::load_pgs()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void etcd_state_client_t::reset_pg_exists()
|
||||
{
|
||||
@@ -976,7 +416,8 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (pc.pg_size < 1 ||
|
||||
pool_item.second["pg_size"].uint64_value() < 3 &&
|
||||
(pc.scheme == POOL_SCHEME_XOR || pc.scheme == POOL_SCHEME_EC) ||
|
||||
pool_item.second["pg_size"].uint64_value() > 256)
|
||||
// limit is 64 because osd_peering_pg.cpp uses a 64-bit mask for has_roles
|
||||
pool_item.second["pg_size"].uint64_value() > 64)
|
||||
{
|
||||
fprintf(stderr, "Pool %u has invalid pg_size, skipping pool\n", pool_id);
|
||||
continue;
|
||||
@@ -1053,8 +494,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
pc.used_for_app = "fs:"+pc.used_for_app;
|
||||
else
|
||||
pc.used_for_app = pool_item.second["used_for_app"].as_string();
|
||||
// Create group permission
|
||||
pc.creator_group = pool_item.second["creator_group"].string_value();
|
||||
// Local Read Configuration
|
||||
std::string local_reads = pool_item.second["local_reads"].string_value();
|
||||
if (local_reads == "nearest")
|
||||
@@ -1318,8 +757,14 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
else if (key.substr(0, etcd_prefix.length()+11) == etcd_prefix+"/osd/state/")
|
||||
{
|
||||
// <etcd_prefix>/osd/state/%d
|
||||
osd_num_t peer_osd = std::stoull(key.substr(etcd_prefix.length()+11));
|
||||
if (peer_osd > 0)
|
||||
osd_num_t peer_osd = 0;
|
||||
char null_byte = 0;
|
||||
int scanned = sscanf(key.c_str() + etcd_prefix.length()+11, "%ju%c", &peer_osd, &null_byte);
|
||||
if (scanned != 1 || !peer_osd)
|
||||
{
|
||||
fprintf(stderr, "Bad etcd key %s, ignoring\n", key.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.is_object() && value["state"] == "up")
|
||||
{
|
||||
@@ -1372,15 +817,37 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
{
|
||||
on_inode_change_hook(inode_num, true);
|
||||
}
|
||||
if (this->inode_config.find(inode_num) != this->inode_config.end())
|
||||
{
|
||||
user_perm_cache_revision = kv.mod_revision;
|
||||
}
|
||||
this->inode_config.erase(inode_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
insert_inode_config(deserialize_inode_cfg(inode_num, kv.value, kv.mod_revision));
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !(parent_inode_num >> (64-POOL_ID_BITS)))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num |= pool_id << (64-POOL_ID_BITS);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %ju/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
inode_num >> (64-POOL_ID_BITS), inode_num & (((uint64_t)1 << (64-POOL_ID_BITS)) - 1)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
insert_inode_config((inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = kv.mod_revision,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1391,38 +858,6 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
|
||||
if (on_change_node_placement_hook)
|
||||
on_change_node_placement_hook();
|
||||
}
|
||||
else if (key.substr(0, etcd_prefix.length()+13) == etcd_prefix+"/config/user/")
|
||||
{
|
||||
// <etcd_prefix>/config/user/<username>
|
||||
auto name = key.substr(etcd_prefix.length()+13);
|
||||
auto & inf = user_info[name];
|
||||
if (!value.is_object())
|
||||
{
|
||||
if (inf)
|
||||
{
|
||||
inf->type = user_type_t::CLIENT;
|
||||
inf->groups.clear();
|
||||
inf->perm_cache.clear();
|
||||
}
|
||||
user_info.erase(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inf)
|
||||
{
|
||||
inf = std::make_shared<user_info_t>();
|
||||
inf->name = name;
|
||||
}
|
||||
inf->type = value["type"] == "admin" ? user_type_t::ADMIN : user_type_t::CLIENT;
|
||||
inf->groups.clear();
|
||||
for (auto & group: value["groups"].array_items())
|
||||
{
|
||||
if (group.string_value() != "")
|
||||
inf->groups.insert(group.string_value());
|
||||
}
|
||||
inf->perm_cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t etcd_state_client_t::parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value)
|
||||
@@ -1445,12 +880,7 @@ uint32_t etcd_state_client_t::parse_scheme(const std::string & scheme)
|
||||
|
||||
void etcd_state_client_t::insert_inode_config(const inode_config_t & cfg)
|
||||
{
|
||||
auto & cfg_ref = this->inode_config[cfg.num];
|
||||
if (cfg_ref.mod_revision != cfg.mod_revision)
|
||||
{
|
||||
user_perm_cache_revision = cfg.mod_revision;
|
||||
}
|
||||
cfg_ref = cfg;
|
||||
this->inode_config[cfg.num] = cfg;
|
||||
if (cfg.name != "")
|
||||
{
|
||||
this->inode_by_name[cfg.name] = cfg.num;
|
||||
@@ -1506,10 +936,6 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
new_cfg["parent_pool"] = (uint64_t)INODE_POOL(cfg->parent_id);
|
||||
new_cfg["parent_id"] = (uint64_t)INODE_NO_POOL(cfg->parent_id);
|
||||
}
|
||||
if (!cfg->enc_key.empty())
|
||||
{
|
||||
new_cfg["enc_key"] = cfg->enc_key;
|
||||
}
|
||||
if (cfg->readonly)
|
||||
{
|
||||
new_cfg["readonly"] = true;
|
||||
@@ -1518,18 +944,6 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
{
|
||||
new_cfg["deleted"] = true;
|
||||
}
|
||||
if (!cfg->owner.empty())
|
||||
{
|
||||
new_cfg["owner"] = cfg->owner;
|
||||
}
|
||||
if (!cfg->owner_group.empty())
|
||||
{
|
||||
new_cfg["owner_group"] = cfg->owner_group;
|
||||
}
|
||||
if (!cfg->reader_group.empty())
|
||||
{
|
||||
new_cfg["reader_group"] = cfg->reader_group;
|
||||
}
|
||||
if (cfg->meta.is_object())
|
||||
{
|
||||
new_cfg["meta"] = cfg->meta;
|
||||
@@ -1537,53 +951,6 @@ json11::Json::object etcd_state_client_t::serialize_inode_cfg(inode_config_t *cf
|
||||
return new_cfg;
|
||||
}
|
||||
|
||||
inode_config_t etcd_state_client_t::deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision)
|
||||
{
|
||||
inode_t parent_inode_num = value["parent_id"].uint64_value();
|
||||
if (parent_inode_num && !INODE_POOL(parent_inode_num))
|
||||
{
|
||||
uint64_t parent_pool_id = value["parent_pool"].uint64_value();
|
||||
if (!parent_pool_id)
|
||||
parent_inode_num = INODE_WITH_POOL(INODE_POOL(inode_num), parent_inode_num);
|
||||
else if (parent_pool_id >= POOL_ID_MAX)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Inode %u/%ju parent_pool value is invalid, ignoring parent setting\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num)
|
||||
);
|
||||
parent_inode_num = 0;
|
||||
}
|
||||
else
|
||||
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
|
||||
}
|
||||
std::string enc_key;
|
||||
if (!value["enc_key"].is_null())
|
||||
{
|
||||
enc_key = value["enc_key"].string_value();
|
||||
if (enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) != VAULT_KEY_PREFIX &&
|
||||
(enc_key.size() != 2*AES_256_XTS_KEY_SIZE || !ishexstr(enc_key)))
|
||||
{
|
||||
enc_key = "";
|
||||
fprintf(stderr, "Inode %u/%ju has invalid enc_key, should be %u bit hex string or Vault key reference\n",
|
||||
INODE_POOL(inode_num), INODE_NO_POOL(inode_num), AES_256_XTS_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
return (inode_config_t){
|
||||
.num = inode_num,
|
||||
.name = value["name"].string_value(),
|
||||
.size = value["size"].uint64_value(),
|
||||
.parent_id = parent_inode_num,
|
||||
.readonly = value["readonly"].bool_value(),
|
||||
.deleted = value["deleted"].bool_value(),
|
||||
.enc_key = std::move(enc_key),
|
||||
.owner = value["owner"].string_value(),
|
||||
.owner_group = value["owner_group"].string_value(),
|
||||
.reader_group = value["reader_group"].string_value(),
|
||||
.meta = value["meta"],
|
||||
.mod_revision = mod_revision,
|
||||
};
|
||||
}
|
||||
|
||||
int etcd_state_client_t::address_count()
|
||||
{
|
||||
return etcd_addresses.size() + etcd_local.size();
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "json11/json11.hpp"
|
||||
#include "object_id.h"
|
||||
#include "timerfd_manager.h"
|
||||
#include "../util/robin_hood.h"
|
||||
|
||||
#define ETCD_CONFIG_WATCH_ID 1
|
||||
#define ETCD_OSD_STATE_WATCH_ID 2
|
||||
@@ -21,8 +19,6 @@
|
||||
#define MAX_DATA_BLOCK_SIZE 128*1024*1024
|
||||
#define DEFAULT_BITMAP_GRANULARITY 4096
|
||||
|
||||
#define VAULT_KEY_PREFIX "vault:"
|
||||
|
||||
#ifndef IMMEDIATE_NONE
|
||||
#define IMMEDIATE_NONE 0
|
||||
#define IMMEDIATE_SMALL 1
|
||||
@@ -70,7 +66,6 @@ struct pool_config_t
|
||||
std::map<pg_num_t, pg_config_t> pg_config;
|
||||
uint64_t scrub_interval = 0;
|
||||
std::string used_for_app;
|
||||
std::string creator_group;
|
||||
int backfillfull = 0;
|
||||
int local_reads = 0;
|
||||
|
||||
@@ -88,9 +83,6 @@ struct inode_config_t
|
||||
inode_t parent_id = 0;
|
||||
bool readonly = false;
|
||||
bool deleted = false;
|
||||
std::string enc_key;
|
||||
// Permissions
|
||||
std::string owner, owner_group, reader_group;
|
||||
// Arbitrary metadata
|
||||
json11::Json meta;
|
||||
// Change revision of the metadata in etcd
|
||||
@@ -103,65 +95,21 @@ struct inode_watch_t
|
||||
inode_config_t cfg = {};
|
||||
};
|
||||
|
||||
struct http_url_t
|
||||
{
|
||||
bool ssl;
|
||||
std::string addr;
|
||||
std::string hostname;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
enum class user_type_t
|
||||
{
|
||||
CLIENT = 0,
|
||||
ADMIN = 1,
|
||||
};
|
||||
|
||||
struct user_perm_t
|
||||
{
|
||||
enum class perm_type_t: uint8_t;
|
||||
constexpr static perm_type_t DENY = (perm_type_t)0;
|
||||
constexpr static perm_type_t READER = (perm_type_t)1;
|
||||
constexpr static perm_type_t OWNER = (perm_type_t)2;
|
||||
uint64_t mod_revision = 0;
|
||||
perm_type_t perm = DENY;
|
||||
};
|
||||
|
||||
struct user_info_t
|
||||
{
|
||||
std::string name;
|
||||
user_type_t type;
|
||||
robin_hood::unordered_flat_set<std::string> groups;
|
||||
robin_hood::unordered_flat_map<inode_t, user_perm_t> perm_cache;
|
||||
};
|
||||
|
||||
struct http_co_t;
|
||||
struct http_context_t;
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
std::set<std::string> local_ips;
|
||||
std::vector<std::string> etcd_local;
|
||||
std::vector<std::string> local_ips;
|
||||
std::vector<std::string> etcd_addresses;
|
||||
std::vector<http_url_t> etcd_local_addr_urls;
|
||||
std::vector<http_url_t> etcd_nonlocal_addr_urls;
|
||||
std::vector<http_url_t> etcd_name_urls;
|
||||
size_t local_to_try = 0;
|
||||
std::vector<http_url_t> etcd_urls_to_try;
|
||||
http_url_t selected_etcd_url;
|
||||
size_t resolve_count = 0;
|
||||
std::vector<std::string> etcd_local;
|
||||
std::vector<inode_watch_t*> watches;
|
||||
std::vector<std::function<void()>> on_resolve_queue;
|
||||
std::set<osd_num_t> seen_peers;
|
||||
bool new_pg_config = false;
|
||||
int ws_keepalive_timer = -1;
|
||||
int ws_alive = 0;
|
||||
bool rand_initialized = false;
|
||||
|
||||
void add_etcd_url(std::string);
|
||||
void pick_next_etcd(std::function<void()> cb);
|
||||
void pick_next_etcd_on_resolve();
|
||||
void etcd_call_selected(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void start_etcd_watcher_selected();
|
||||
void reset_pg_exists();
|
||||
void clean_nonexistent_pgs();
|
||||
public:
|
||||
int etcd_keepalive_timeout = 30;
|
||||
int etcd_ws_keepalive_interval = 5;
|
||||
@@ -173,30 +121,17 @@ public:
|
||||
uint64_t global_block_size = DEFAULT_BLOCK_SIZE;
|
||||
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
|
||||
uint32_t global_immediate_commit = IMMEDIATE_NONE;
|
||||
|
||||
uint64_t osd_num = 0;
|
||||
std::string etcd_prefix;
|
||||
std::string etcd_client_cert;
|
||||
std::string etcd_client_key;
|
||||
std::string etcd_ca;
|
||||
int log_level = 0;
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
|
||||
http_context_t *http_ctx = NULL;
|
||||
http_co_t *etcd_watch_ws = NULL, *keepalive_client = NULL;
|
||||
int etcd_watches_initialised = 0;
|
||||
uint64_t etcd_watch_revision_config = 0;
|
||||
uint64_t etcd_watch_revision_osd = 0;
|
||||
uint64_t etcd_watch_revision_pg = 0;
|
||||
timespec etcd_last_reload = {};
|
||||
int load_pgs_timer_id = -1;
|
||||
|
||||
std::map<pool_id_t, pool_config_t> pool_config;
|
||||
std::map<osd_num_t, json11::Json> peer_states;
|
||||
std::set<osd_num_t> seen_peers;
|
||||
std::map<inode_t, inode_config_t> inode_config;
|
||||
std::map<std::string, inode_t> inode_by_name;
|
||||
robin_hood::unordered_flat_map<std::string, std::shared_ptr<user_info_t>> user_info;
|
||||
uint64_t user_perm_cache_revision = 0;
|
||||
json11::Json node_placement;
|
||||
|
||||
std::function<void(std::map<std::string, etcd_kv_t> &)> on_change_hook;
|
||||
@@ -215,30 +150,24 @@ public:
|
||||
std::function<void(http_co_t *)> on_start_watcher_hook;
|
||||
|
||||
json11::Json::object serialize_inode_cfg(inode_config_t *cfg);
|
||||
inode_config_t deserialize_inode_cfg(uint64_t inode_num, json11::Json value, uint64_t mod_revision);
|
||||
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
|
||||
std::vector<std::string> get_addresses();
|
||||
std::shared_ptr<user_info_t> get_user(const std::string & username);
|
||||
bool check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write);
|
||||
http_context_t *get_http_ctx();
|
||||
void etcd_call_oneshot(const std::string & etcd_address, const std::string & api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_call(const std::string & api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
virtual void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
virtual void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) = 0;
|
||||
void etcd_txn(json11::Json txn, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
|
||||
void etcd_txn_slow(json11::Json txn, std::function<void(std::string, json11::Json)> callback);
|
||||
void start_etcd_watcher();
|
||||
void stop_ws_keepalive();
|
||||
void start_ws_keepalive();
|
||||
void load_global_config();
|
||||
void load_pgs();
|
||||
void reset_pg_exists();
|
||||
void clean_nonexistent_pgs();
|
||||
virtual void etcd_add_watch(json11::Json watch) = 0;
|
||||
void load_global_config(std::function<void(const std::string &)> cb);
|
||||
virtual void load_global_config() = 0;
|
||||
void load_pgs(std::function<void(const std::string &)> cb);
|
||||
virtual void load_pgs() = 0;
|
||||
void parse_state(const etcd_kv_t & kv);
|
||||
void parse_config(const json11::Json & config);
|
||||
virtual void parse_config(const json11::Json & config);
|
||||
void insert_inode_config(const inode_config_t & cfg);
|
||||
inode_watch_t* watch_inode(std::string name);
|
||||
void close_watch(inode_watch_t* watch);
|
||||
int address_count();
|
||||
~etcd_state_client_t();
|
||||
virtual ~etcd_state_client_t();
|
||||
|
||||
static uint32_t parse_immediate_commit(const std::string & immediate_commit_str, uint32_t default_value);
|
||||
static uint32_t parse_scheme(const std::string & scheme_str);
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include "etcd_state_client_http.h"
|
||||
#include "addr_util.h"
|
||||
#include "http_client.h"
|
||||
#include "str_util.h"
|
||||
|
||||
etcd_state_client_http_t::etcd_state_client_http_t(timerfd_manager_t *tfd)
|
||||
{
|
||||
this->tfd = tfd;
|
||||
}
|
||||
|
||||
etcd_state_client_http_t::~etcd_state_client_http_t()
|
||||
{
|
||||
stop_ws_keepalive();
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (keepalive_client)
|
||||
{
|
||||
http_close(keepalive_client);
|
||||
keepalive_client = NULL;
|
||||
}
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
etcd_watches_initialised = -1;
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_add_watch(json11::Json watch)
|
||||
{
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, watch.dump());
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"+req;
|
||||
auto http_cli = http_init(tfd);
|
||||
auto cb = [http_cli, callback](const http_response_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
callback(err, data);
|
||||
http_close(http_cli);
|
||||
};
|
||||
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::etcd_call(std::string api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
std::string req = payload.dump();
|
||||
req = "POST "+etcd_api_path+api+" HTTP/1.1\r\n"
|
||||
"Host: "+etcd_address+"\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Content-Length: "+std::to_string(req.size())+"\r\n"
|
||||
"Connection: keep-alive\r\n"
|
||||
"Keep-Alive: timeout="+std::to_string(etcd_keepalive_timeout)+"\r\n"
|
||||
"\r\n"+req;
|
||||
retries--;
|
||||
auto cb = [this, api, payload, timeout, retries, interval, callback,
|
||||
cur_addr = selected_etcd_address](const http_response_t *response)
|
||||
{
|
||||
std::string err;
|
||||
json11::Json data;
|
||||
response->parse_json_response(err, data);
|
||||
if (err != "")
|
||||
{
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (retries > 0)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(
|
||||
stderr, "Warning: etcd request failed: %s, retrying %d more times\n",
|
||||
err.c_str(), retries
|
||||
);
|
||||
}
|
||||
if (interval > 0)
|
||||
{
|
||||
// FIXME: Prevent destruction of etcd_state_client if timers or requests are active
|
||||
tfd->set_timer(interval, false, [this, api, payload, timeout, retries, interval, callback](int)
|
||||
{
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
});
|
||||
}
|
||||
else
|
||||
etcd_call(api, payload, timeout, retries, interval, callback);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
}
|
||||
else
|
||||
callback(err, data);
|
||||
};
|
||||
if (!keepalive_client)
|
||||
{
|
||||
keepalive_client = http_init(tfd);
|
||||
}
|
||||
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::parse_config(const json11::Json & config)
|
||||
{
|
||||
auto old_etcd_ws_keepalive_interval = this->etcd_ws_keepalive_interval;
|
||||
etcd_state_client_t::parse_config(config);
|
||||
if (this->etcd_ws_keepalive_interval != old_etcd_ws_keepalive_interval && ws_keepalive_timer >= 0)
|
||||
{
|
||||
stop_ws_keepalive();
|
||||
start_ws_keepalive();
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::pick_next_etcd()
|
||||
{
|
||||
if (selected_etcd_address != "")
|
||||
return;
|
||||
if (addresses_to_try.size() == 0)
|
||||
{
|
||||
// Prefer local etcd, if any
|
||||
for (int i = 0; i < etcd_local.size(); i++)
|
||||
addresses_to_try.push_back(etcd_local[i]);
|
||||
std::vector<int> ns;
|
||||
for (int i = 0; i < etcd_addresses.size(); i++)
|
||||
ns.push_back(i);
|
||||
if (!rand_initialized)
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
rand_initialized = true;
|
||||
}
|
||||
while (ns.size())
|
||||
{
|
||||
int i = lrand48() % ns.size();
|
||||
addresses_to_try.push_back(etcd_addresses[ns[i]]);
|
||||
ns.erase(ns.begin()+i, ns.begin()+i+1);
|
||||
}
|
||||
}
|
||||
selected_etcd_address = addresses_to_try[0];
|
||||
addresses_to_try.erase(addresses_to_try.begin(), addresses_to_try.begin()+1);
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_etcd_watcher()
|
||||
{
|
||||
if (!etcd_addresses.size() && !etcd_local.size())
|
||||
{
|
||||
fprintf(stderr, "etcd_address is missing in Vitastor configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
pick_next_etcd();
|
||||
std::string etcd_address = selected_etcd_address;
|
||||
std::string etcd_api_path;
|
||||
int pos = etcd_address.find('/');
|
||||
if (pos >= 0)
|
||||
{
|
||||
etcd_api_path = etcd_address.substr(pos);
|
||||
etcd_address = etcd_address.substr(0, pos);
|
||||
}
|
||||
etcd_watches_initialised = 0;
|
||||
ws_alive = 1;
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (this->log_level > 1)
|
||||
{
|
||||
fprintf(stderr, "Trying to connect to etcd websocket at %s, watch from revision %ju/%ju/%ju\n", etcd_address.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
etcd_watch_ws = open_websocket(tfd, etcd_address, etcd_api_path+"/watch", etcd_slow_timeout,
|
||||
[this, cur_addr = selected_etcd_address](const http_response_t *msg)
|
||||
{
|
||||
if (msg->body.length())
|
||||
{
|
||||
ws_alive = 1;
|
||||
std::string json_err;
|
||||
json11::Json data = json11::Json::parse(msg->body, json_err);
|
||||
if (json_err != "")
|
||||
{
|
||||
fprintf(stderr, "Bad JSON in etcd event: %s, ignoring event\n", json_err.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t watch_id = data["result"]["watch_id"].uint64_value();
|
||||
if (data["result"]["created"].bool_value())
|
||||
{
|
||||
if (watch_id == ETCD_CONFIG_WATCH_ID ||
|
||||
watch_id == ETCD_PG_STATE_WATCH_ID ||
|
||||
watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
{
|
||||
etcd_watches_initialised++;
|
||||
}
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Successfully subscribed to etcd at %s, revision %ju/%ju/%ju\n", cur_addr.c_str(),
|
||||
etcd_watch_revision_config, etcd_watch_revision_osd, etcd_watch_revision_pg);
|
||||
}
|
||||
}
|
||||
if (data["result"]["canceled"].bool_value())
|
||||
{
|
||||
// etcd watch canceled, maybe because the revision was compacted
|
||||
if (data["result"]["compact_revision"].uint64_value())
|
||||
{
|
||||
// we may miss events if we proceed
|
||||
// so we should restart from the beginning if we can
|
||||
if (on_reload_hook != NULL)
|
||||
{
|
||||
// check to not trigger on_reload_hook multiple times
|
||||
if (etcd_watch_ws != NULL)
|
||||
{
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
|
||||
on_reload_hook();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Revisions before %ju were compacted by etcd, exiting\n",
|
||||
data["result"]["compact_revision"].uint64_value());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Watch canceled by etcd, reason: %s, exiting\n", data["result"]["cancel_reason"].string_value().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
|
||||
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
|
||||
// Also don't save revision from the initial created: true messages because they always contain the latest revision
|
||||
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES &&
|
||||
!data["result"]["header"]["revision"].is_null() &&
|
||||
!data["result"]["created"].bool_value())
|
||||
{
|
||||
// Restart watchers from the same revision number as in the last received message,
|
||||
// not from the next one to protect against revision being split into multiple messages,
|
||||
// even though etcd guarantees not to do that **within a single watcher** without fragment=true:
|
||||
// https://etcd.io/docs/v3.5/learning/api_guarantees/#watch-apis
|
||||
// Revision contents are ALWAYS split into separate messages for different watchers though!
|
||||
// So generally we have to resume each watcher from its own revision...
|
||||
// Progress messages may have watch_id=-1 if sent on behalf of multiple watchers though.
|
||||
// And antietcd has an advanced semantic which merges the same revision for all watchers
|
||||
// into one message and just omits watch_id.
|
||||
// So we also have to handle the case where watch_id is -1 or not present (0).
|
||||
auto watch_rev = data["result"]["header"]["revision"].uint64_value();
|
||||
if (!watch_id || watch_id == UINT64_MAX)
|
||||
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_CONFIG_WATCH_ID)
|
||||
etcd_watch_revision_config = watch_rev;
|
||||
else if (watch_id == ETCD_PG_STATE_WATCH_ID)
|
||||
etcd_watch_revision_pg = watch_rev;
|
||||
else if (watch_id == ETCD_OSD_STATE_WATCH_ID)
|
||||
etcd_watch_revision_osd = watch_rev;
|
||||
addresses_to_try.clear();
|
||||
}
|
||||
// First gather all changes into a hash to remove multiple overwrites
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
for (auto & ev: data["result"]["events"].array_items())
|
||||
{
|
||||
auto kv = parse_etcd_kv(ev["kv"]);
|
||||
if (kv.key != "")
|
||||
{
|
||||
changes[kv.key] = kv;
|
||||
}
|
||||
}
|
||||
for (auto & kv: changes)
|
||||
{
|
||||
if (this->log_level > 3)
|
||||
{
|
||||
fprintf(stderr, "Incoming event: %s -> %s\n", kv.first.c_str(), kv.second.value.dump().c_str());
|
||||
}
|
||||
parse_state(kv.second);
|
||||
}
|
||||
// React to changes
|
||||
if (on_change_hook != NULL)
|
||||
{
|
||||
on_change_hook(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (msg->eof)
|
||||
{
|
||||
fprintf(stderr, "Disconnected from etcd %s\n", cur_addr.c_str());
|
||||
if (cur_addr == selected_etcd_address)
|
||||
selected_etcd_address = "";
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
if (etcd_watches_initialised == 0)
|
||||
{
|
||||
// Connection not established, retry in <etcd_quick_timeout>
|
||||
tfd->set_timer(etcd_quick_timeout, false, [this](int)
|
||||
{
|
||||
start_etcd_watcher();
|
||||
});
|
||||
}
|
||||
else if (etcd_watches_initialised > 0)
|
||||
{
|
||||
// Connection was live, retry immediately
|
||||
etcd_watches_initialised = 0;
|
||||
start_etcd_watcher();
|
||||
}
|
||||
}
|
||||
});
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/config/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/config0") },
|
||||
{ "start_revision", etcd_watch_revision_config },
|
||||
{ "watch_id", ETCD_CONFIG_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/osd/state/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/osd/state0") },
|
||||
{ "start_revision", etcd_watch_revision_osd },
|
||||
{ "watch_id", ETCD_OSD_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "create_request", json11::Json::object {
|
||||
{ "key", base64_encode(etcd_prefix+"/pg/") },
|
||||
{ "range_end", base64_encode(etcd_prefix+"/pg0") },
|
||||
{ "start_revision", etcd_watch_revision_pg },
|
||||
{ "watch_id", ETCD_PG_STATE_WATCH_ID },
|
||||
{ "progress_notify", true },
|
||||
} }
|
||||
}).dump());
|
||||
// FIXME: Do not watch /pg/history/ at all in client code (not in OSD)
|
||||
if (on_start_watcher_hook)
|
||||
{
|
||||
on_start_watcher_hook(etcd_watch_ws);
|
||||
}
|
||||
start_ws_keepalive();
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::stop_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer >= 0)
|
||||
{
|
||||
tfd->clear_timer(ws_keepalive_timer);
|
||||
ws_keepalive_timer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::start_ws_keepalive()
|
||||
{
|
||||
if (ws_keepalive_timer < 0)
|
||||
{
|
||||
ws_keepalive_timer = tfd->set_timer(etcd_ws_keepalive_interval*1000, true, [this](int)
|
||||
{
|
||||
if (!etcd_watch_ws || etcd_watches_initialised < ETCD_TOTAL_WATCHES)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else if (!ws_alive)
|
||||
{
|
||||
if (this->log_level > 0)
|
||||
{
|
||||
fprintf(stderr, "Websocket ping failed, disconnecting from etcd %s\n", selected_etcd_address.c_str());
|
||||
}
|
||||
if (etcd_watch_ws)
|
||||
{
|
||||
http_close(etcd_watch_ws);
|
||||
etcd_watch_ws = NULL;
|
||||
}
|
||||
start_etcd_watcher();
|
||||
}
|
||||
else
|
||||
{
|
||||
ws_alive = 0;
|
||||
http_post_message(etcd_watch_ws, WS_TEXT, json11::Json(json11::Json::object {
|
||||
{ "progress_request", json11::Json::object { } }
|
||||
}).dump());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::load_global_config()
|
||||
{
|
||||
etcd_state_client_t::load_global_config([this](const std::string & err)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
fprintf(stderr, "Error reading configuration from etcd: %s\n", err.c_str());
|
||||
if (infinite_start)
|
||||
{
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_global_config();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void etcd_state_client_http_t::load_pgs()
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
uint64_t ms_passed = (tv.tv_sec-etcd_last_reload.tv_sec)*1000 + (tv.tv_nsec-etcd_last_reload.tv_nsec)/1000000;
|
||||
if (ms_passed < etcd_min_reload_interval)
|
||||
{
|
||||
if (load_pgs_timer_id < 0)
|
||||
{
|
||||
load_pgs_timer_id = tfd->set_timer(etcd_min_reload_interval+50-ms_passed, false, [this](int) { load_pgs(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
etcd_last_reload = tv;
|
||||
if (load_pgs_timer_id >= 0)
|
||||
{
|
||||
tfd->clear_timer(load_pgs_timer_id);
|
||||
load_pgs_timer_id = -1;
|
||||
}
|
||||
etcd_state_client_t::load_pgs([this](const std::string & err)
|
||||
{
|
||||
if (err != "")
|
||||
{
|
||||
// Retry indefinitely
|
||||
fprintf(stderr, "Error loading PGs from etcd: %s\n", err.c_str());
|
||||
tfd->set_timer(etcd_slow_timeout, false, [this](int timer_id)
|
||||
{
|
||||
load_pgs();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
start_etcd_watcher();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
|
||||
struct __attribute__((visibility("default"))) etcd_state_client_http_t: public etcd_state_client_t
|
||||
{
|
||||
protected:
|
||||
timerfd_manager_t *tfd = NULL;
|
||||
std::string selected_etcd_address;
|
||||
std::vector<std::string> addresses_to_try;
|
||||
int ws_keepalive_timer = -1;
|
||||
int ws_alive = 0;
|
||||
bool rand_initialized = false;
|
||||
int etcd_watches_initialised = 0;
|
||||
timespec etcd_last_reload = {};
|
||||
int load_pgs_timer_id = -1;
|
||||
http_co_t *keepalive_client = NULL;
|
||||
|
||||
void pick_next_etcd();
|
||||
void start_etcd_watcher();
|
||||
void stop_ws_keepalive();
|
||||
void start_ws_keepalive();
|
||||
public:
|
||||
http_co_t *etcd_watch_ws = NULL;
|
||||
etcd_state_client_http_t(timerfd_manager_t *tfd);
|
||||
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_add_watch(json11::Json watch) override;
|
||||
void load_global_config() override;
|
||||
void load_pgs() override;
|
||||
void parse_config(const json11::Json & config) override;
|
||||
~etcd_state_client_http_t();
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <assert.h>
|
||||
#include "etcd_state_client_mock.h"
|
||||
#include "str_util.h"
|
||||
|
||||
etcd_state_client_mock_t::etcd_state_client_mock_t()
|
||||
{
|
||||
timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
srand48(tv.tv_sec*1000000000 + tv.tv_nsec);
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_add_watch(json11::Json watch)
|
||||
{
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload,
|
||||
int timeout, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::pause()
|
||||
{
|
||||
paused = true;
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::resume()
|
||||
{
|
||||
paused = false;
|
||||
auto queue = std::move(this->queue);
|
||||
for (auto& req: queue)
|
||||
{
|
||||
etcd_call(req.api, req.payload, req.timeout, req.retries, req.interval, req.callback);
|
||||
}
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::set(const std::string& key, json11::Json data, uint64_t mod_revision, uint64_t lease_id)
|
||||
{
|
||||
if (!mod_revision)
|
||||
mod_revision = ++this->mod_revision;
|
||||
this->data[key] = (etcd_mock_key_data_t){ .value = data.dump(), .mod_revision = mod_revision, .lease_id = lease_id };
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::etcd_call(std::string api, json11::Json payload, int timeout,
|
||||
int retries, int interval, std::function<void(std::string, json11::Json)> callback)
|
||||
{
|
||||
if (paused)
|
||||
{
|
||||
queue.push_back({ api, payload, timeout, retries, interval, callback });
|
||||
return;
|
||||
}
|
||||
printf("+ etcd: %s\n", api.c_str());
|
||||
if (api == "/kv/txn")
|
||||
{
|
||||
bool ok = true;
|
||||
for (auto& check: payload["compare"].array_items())
|
||||
{
|
||||
auto key = base64_decode(check["key"].string_value());
|
||||
etcd_mock_key_data_t *key_data = data.find(key) != data.end() ? &data.at(key) : NULL;
|
||||
auto target = check["target"].string_value();
|
||||
auto res = check["result"].string_value();
|
||||
assert(res == "LESS" || res == "");
|
||||
bool less = res == "LESS";
|
||||
if (target == "MOD")
|
||||
{
|
||||
uint64_t rev = check["mod_revision"].uint64_value();
|
||||
assert(!less || rev);
|
||||
ok = ok && (less ? (!key_data || key_data->mod_revision < rev) : (key_data && key_data->mod_revision == rev));
|
||||
}
|
||||
else if (target == "CREATE")
|
||||
{
|
||||
uint64_t rev = check["create_revision"].uint64_value();
|
||||
assert(rev == 0 && !less);
|
||||
ok = ok && !key_data;
|
||||
}
|
||||
else if (target == "VERSION")
|
||||
{
|
||||
uint64_t rev = check["version"].uint64_value();
|
||||
assert(rev == 0 && !less);
|
||||
ok = ok && !key_data;
|
||||
}
|
||||
else if (target == "LEASE")
|
||||
{
|
||||
assert(!less);
|
||||
uint64_t lease_id = check["lease"].uint64_value();
|
||||
ok = ok && key_data && key_data->lease_id == lease_id;
|
||||
}
|
||||
else
|
||||
assert(0);
|
||||
}
|
||||
std::map<std::string, etcd_kv_t> changes;
|
||||
bool has_mod = false;
|
||||
for (auto& op: payload[ok ? "success" : "failure"].array_items())
|
||||
{
|
||||
auto& obj = op.object_items();
|
||||
has_mod = has_mod || obj.find("request_put") != obj.end() ||
|
||||
obj.find("request_delete_range") != obj.end();
|
||||
}
|
||||
if (has_mod)
|
||||
{
|
||||
mod_revision++;
|
||||
}
|
||||
json11::Json::array responses;
|
||||
for (auto& op_ptr: payload[ok ? "success" : "failure"].array_items())
|
||||
{
|
||||
auto& op = op_ptr.object_items();
|
||||
if (op.find("request_range") != op.end())
|
||||
{
|
||||
json11::Json::array kvs;
|
||||
auto req = op.at("request_range");
|
||||
auto key = base64_decode(req["key"].string_value());
|
||||
auto range_end = base64_decode(req["range_end"].string_value());
|
||||
auto begin_it = range_end.empty() ? data.find(key) : data.lower_bound(key);
|
||||
auto end_it = range_end.empty() ? (begin_it == data.end() ? begin_it : std::next(begin_it)) : data.lower_bound(range_end);
|
||||
for (auto it = begin_it; it != end_it; it++)
|
||||
{
|
||||
printf("\\- get: %s = %s, rev %ju\n", it->first.c_str(), it->second.value.c_str(), it->second.mod_revision);
|
||||
kvs.push_back(json11::Json::object {
|
||||
{ "key", base64_encode(it->first) },
|
||||
{ "value", base64_encode(it->second.value) },
|
||||
{ "mod_revision", it->second.mod_revision },
|
||||
});
|
||||
}
|
||||
responses.push_back(json11::Json::object {
|
||||
{ "response_range", json11::Json::object{ { "header", json11::Json::object{ { "revision", mod_revision } } }, { "kvs", kvs } } },
|
||||
});
|
||||
}
|
||||
else if (op.find("request_put") != op.end())
|
||||
{
|
||||
auto req = op.at("request_put");
|
||||
auto key = base64_decode(req["key"].string_value());
|
||||
auto value = base64_decode(req["value"].string_value());
|
||||
auto lease_id = req["lease"].uint64_value();
|
||||
printf("\\- put: %s = %s, rev %ju, lease %ju\n", key.c_str(), value.c_str(), mod_revision, lease_id);
|
||||
data[key] = {
|
||||
.value = value,
|
||||
.mod_revision = mod_revision,
|
||||
.lease_id = lease_id,
|
||||
};
|
||||
std::string err;
|
||||
json11::Json json_value = json11::Json::parse(value, err);
|
||||
if (err != "")
|
||||
{
|
||||
fprintf(stderr, "Invalid JSON in etcd key %s during test: %s\n", key.c_str(), value.c_str());
|
||||
exit(1);
|
||||
}
|
||||
changes[key] = { .key = key, .value = json_value, .mod_revision = mod_revision };
|
||||
responses.push_back(json11::Json::object {
|
||||
{ "response_put", json11::Json::object{ { "header", json11::Json::object{ { "revision", mod_revision } } } } },
|
||||
});
|
||||
}
|
||||
else if (op.find("request_delete_range") != op.end())
|
||||
{
|
||||
auto req = op.at("request_delete_range");
|
||||
auto key = base64_decode(req["key"].string_value());
|
||||
auto range_end = base64_decode(req["range_end"].string_value());
|
||||
uint64_t n_del = 0;
|
||||
for (auto it = data.lower_bound(key); it != data.end() && (range_end == "" || it->first < range_end); )
|
||||
{
|
||||
auto & key = it->first;
|
||||
printf("\\- del: %s\n", key.c_str());
|
||||
changes[key] = { .key = key, .mod_revision = mod_revision };
|
||||
n_del++;
|
||||
data.erase(it++);
|
||||
}
|
||||
responses.push_back(json11::Json::object {
|
||||
{ "response_delete_range", json11::Json::object{ { "header", json11::Json::object{ { "revision", mod_revision } } }, { "deleted", n_del } } },
|
||||
});
|
||||
}
|
||||
}
|
||||
callback("", json11::Json::object{
|
||||
{ "header", json11::Json::object{ { "revision", mod_revision } } },
|
||||
{ "succeeded", ok },
|
||||
{ "responses", responses }
|
||||
});
|
||||
// Push changes to watcher
|
||||
if (changes.size())
|
||||
{
|
||||
for (auto & kv: changes)
|
||||
parse_state(kv.second);
|
||||
if (on_change_hook != NULL)
|
||||
on_change_hook(changes);
|
||||
}
|
||||
}
|
||||
else if (api == "/lease/grant")
|
||||
{
|
||||
uint64_t lease_id = (((uint64_t)lrand48()) << 32) | lrand48();
|
||||
leases[lease_id] = payload["TTL"].uint64_value();
|
||||
callback("", json11::Json::object{ { "ID", std::to_string(lease_id) } });
|
||||
}
|
||||
else
|
||||
callback("Unsupported", json11::Json());
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::load_global_config()
|
||||
{
|
||||
etcd_state_client_t::load_global_config([this](const std::string & err) {});
|
||||
}
|
||||
|
||||
void etcd_state_client_mock_t::load_pgs()
|
||||
{
|
||||
etcd_state_client_t::load_pgs([this](const std::string & err) {});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "etcd_state_client.h"
|
||||
|
||||
struct etcd_mock_key_data_t
|
||||
{
|
||||
std::string value;
|
||||
uint64_t mod_revision;
|
||||
uint64_t lease_id;
|
||||
};
|
||||
|
||||
struct etcd_mock_request_t
|
||||
{
|
||||
std::string api;
|
||||
json11::Json payload;
|
||||
int timeout;
|
||||
int retries;
|
||||
int interval;
|
||||
std::function<void(std::string, json11::Json)> callback;
|
||||
};
|
||||
|
||||
struct etcd_state_client_mock_t: public etcd_state_client_t
|
||||
{
|
||||
uint64_t mod_revision = 0;
|
||||
bool paused = false;
|
||||
std::vector<etcd_mock_request_t> queue;
|
||||
public:
|
||||
std::map<uint64_t, uint64_t> leases;
|
||||
std::map<std::string, etcd_mock_key_data_t> data;
|
||||
etcd_state_client_mock_t();
|
||||
void set(const std::string& key, json11::Json data, uint64_t mod_revision = 0, uint64_t lease_id = 0);
|
||||
void pause();
|
||||
void resume();
|
||||
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback) override;
|
||||
void etcd_add_watch(json11::Json watch) override;
|
||||
void load_global_config() override;
|
||||
void load_pgs() override;
|
||||
};
|
||||
+87
-629
File diff suppressed because it is too large
Load Diff
@@ -17,19 +17,14 @@
|
||||
|
||||
class timerfd_manager_t;
|
||||
|
||||
#pragma GCC visibility push(default)
|
||||
|
||||
struct http_options_t
|
||||
{
|
||||
int timeout;
|
||||
bool want_streaming;
|
||||
bool keepalive;
|
||||
bool ssl;
|
||||
};
|
||||
|
||||
struct http_context_t;
|
||||
|
||||
struct http_message_t
|
||||
struct http_response_t
|
||||
{
|
||||
std::string error;
|
||||
|
||||
@@ -46,26 +41,10 @@ struct http_message_t
|
||||
// Opened websocket or keepalive HTTP connection
|
||||
struct http_co_t;
|
||||
|
||||
http_context_t* http_context_init(timerfd_manager_t *tfd, const std::string & ssl_cert, const std::string & ssl_key,
|
||||
const std::string & ssl_ca, bool verify_peer, std::string & error);
|
||||
std::string http_context_get_ssl_cn(http_context_t *ctx);
|
||||
void http_resolve(http_context_t *ctx, bool ssl, std::string host,
|
||||
std::function<void(const std::string & error, const std::vector<std::string> & addrs)> cb);
|
||||
void http_context_destroy(http_context_t *ctx);
|
||||
http_co_t* http_init(http_context_t *ctx = NULL);
|
||||
void open_websocket(http_co_t *handler, const std::string & addr, const std::string & hostname, const std::string & path,
|
||||
const http_options_t & options, std::function<void(http_message_t *msg)> on_message);
|
||||
http_co_t* http_init(timerfd_manager_t *tfd);
|
||||
http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, const std::string & path,
|
||||
int timeout, std::function<void(const http_response_t *msg)> on_message);
|
||||
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_get(http_co_t *handler, const std::string & url, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
void http_json_post(http_co_t *handler, const std::string & url, json11::Json body, const std::string & headers,
|
||||
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
|
||||
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
|
||||
void http_post_message(http_co_t *handler, uint8_t type, const std::string & msg);
|
||||
void http_serve(http_co_t *handler, int peer_fd, const http_options_t & options,
|
||||
std::function<void(http_message_t *msg)> request_callback);
|
||||
void http_reply(http_co_t *handler, const std::string & reply);
|
||||
void http_close(http_co_t *co);
|
||||
void http_destroy(http_co_t *co);
|
||||
|
||||
#pragma GCC visibility pop
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user