Compare commits

..
Author SHA1 Message Date
Vitaliy Filippov 6467ebb806 WIP Add AES-XTS client encryption support 2026-02-22 02:48:34 +03:00
Vitaliy Filippov 8c2f0b2c93 Rework msgr send/receive to allow encryption support 2026-02-21 12:31:39 +03:00
Vitaliy Filippov 50db154bb3 Update antietcd to 1.2.4 2026-02-20 21:21:03 +03:00
Vitaliy Filippov 4c53fcdf39 Move fromhexstr() to str_util 2026-02-20 21:18:29 +03:00
Vitaliy Filippov 467d51a48c Add openapi description 2026-02-20 21:18:29 +03:00
Vitaliy Filippov 44fa7c189f Slightly fix API return and input types 2026-02-20 21:18:29 +03:00
Vitaliy Filippov 1502474e20 Implement vitastor-cli serve command to serve simple HTTP API 2026-02-20 21:18:29 +03:00
Vitaliy Filippov 8d93a97374 Implement HTTP server support O_o 2026-02-19 12:38:16 +03:00
Vitaliy Filippov 79f9631f03 Update antietcd to 1.2.3 2026-02-19 12:38:16 +03:00
Vitaliy Filippov 6024b3b2a5 Rename http_response_t to http_message_t 2026-02-19 12:38:16 +03:00
Vitaliy Filippov 069841d899 Extract common HTTP context 2026-02-19 12:38:16 +03:00
Vitaliy Filippov 59ccb73298 Support xxhash 32-bit checksums (data_csum_type=xxh3_32) 2026-02-19 12:38:16 +03:00
Vitaliy Filippov 7add655916 Detect block checksums using csum_block_size, not data_csum_type 2026-02-18 01:08:05 +03:00
Vitaliy Filippov bf1382a5ad Add client certificate support 2026-02-18 01:08:05 +03:00
Vitaliy Filippov b8558e163d Do not re-initialize TLS context every connection 2026-02-18 01:08:05 +03:00
Vitaliy Filippov 3669258078 Add https support to antietcd 2026-02-18 01:08:05 +03:00
Vitaliy Filippov 2a06a3e903 Implement etcd SSL support via OpenSSL
Maybe I should remove all of this and use libwebsockets :)
2026-02-18 01:08:05 +03:00
123 changed files with 11718 additions and 1106 deletions
+7 -8
View File
@@ -1,29 +1,28 @@
FROM node:16-bookworm
FROM node:16-bullseye
WORKDIR /root
ADD ./docker/etc/apt/trusted.gpg.d /etc/apt/trusted.gpg.d
ADD ./docker/vitastor.gpg /etc/apt/trusted.gpg.d
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list; \
echo 'deb http://vitastor.io/debian bookworm main' >> /etc/apt/sources.list; \
RUN echo 'deb http://deb.debian.org/debian bullseye-backports main' >> /etc/apt/sources.list; \
echo 'deb http://vitastor.io/debian bullseye main' >> /etc/apt/sources.list; \
echo >> /etc/apt/preferences; \
echo 'Package: *' >> /etc/apt/preferences; \
echo 'Pin: release n=bookworm-backports' >> /etc/apt/preferences; \
echo 'Pin: release a=bullseye-backports' >> /etc/apt/preferences; \
echo 'Pin-Priority: 500' >> /etc/apt/preferences; \
echo >> /etc/apt/preferences; \
echo 'Package: *' >> /etc/apt/preferences; \
echo 'Pin: origin "vitastor.io"' >> /etc/apt/preferences; \
echo 'Pin-Priority: 1000' >> /etc/apt/preferences; \
perl -i -pe 's/Types: deb$/Types: deb deb-src/' /etc/apt/sources.list.d/debian.sources; \
grep '^deb ' /etc/apt/sources.list | perl -pe 's/^deb/deb-src/' >> /etc/apt/sources.list; \
echo 'APT::Install-Recommends false;' >> /etc/apt/apt.conf; \
echo 'APT::Install-Suggests false;' >> /etc/apt/apt.conf
RUN apt-get update
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan8 \
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan5 \
libgoogle-perftools-dev devscripts libjerasure-dev cmake libibverbs-dev libisal-dev
RUN apt-get -y build-dep fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted libc-ares-dev udev
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted
RUN apt-get --download-only source fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
RUN set -ex; \
+72
View File
@@ -234,6 +234,60 @@ jobs:
echo ""
done
test_etcd_fail_https:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: ETCD_SCHEME=https /root/vitastor/tests/test_etcd_fail.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_etcd_fail_https_antietcd:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 10
run: ETCD_SCHEME=https ANTIETCD=1 /root/vitastor/tests/test_etcd_fail.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_snapshot_https:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: ETCD_SCHEME=https /root/vitastor/tests/test_snapshot.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_interrupted_rebalance:
runs-on: ubuntu-latest
needs: build
@@ -1224,6 +1278,24 @@ jobs:
echo ""
done
test_checksum_xxhash:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: TEST_NAME=xxhash OSD_ARGS="--data_csum_type xxh3_32" /root/vitastor/tests/test_checksum.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_old_checksum:
runs-on: ubuntu-latest
needs: build
+4
View File
@@ -38,6 +38,10 @@ for my $line (<>)
{
$test_name .= '_antietcd';
}
elsif ($1 eq 'ETCD_SCHEME' && $2 eq 'https')
{
$test_name .= '_https';
}
elsif ($1 eq 'OLD')
{
$test_name =~ s/^test_/test_old_/s;
+1 -1
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.12)
project(vitastor)
set(VITASTOR_VERSION "3.0.6")
set(VITASTOR_VERSION "3.0.3")
include(CTest)
+2 -2
View File
@@ -25,8 +25,8 @@ RUN apt-get update && \
# NFS mount dependencies
nfs-common netbase \
# dependencies of qemu-storage-daemon
libaio1t64 libc6 libfuse3-4 libglib2.0-0t64 libgmp10 libgnutls30t64 \
libhogweed6t64 libnettle8t64 libnuma1 libselinux1 liburing2 libzstd1 zlib1g && \
libnuma1 liburing2 libglib2.0-0 libfuse3-3 libaio1 libzstd1 libnettle8 \
libgmp10 libhogweed6 libp11-kit0 libidn2-0 libunistring2 libtasn1-6 libpcre2-8-0 libffi8 && \
apt-get clean && \
(echo options nbd nbds_max=128 > /etc/modprobe.d/nbd.conf)
+4 -4
View File
@@ -1,5 +1,5 @@
# Compile stage
FROM golang:trixie AS build
FROM golang:bookworm AS build
ADD go.sum go.mod /app/
RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go mod download -x
@@ -9,7 +9,7 @@ RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)e
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi
# Final stage
FROM debian:trixie
FROM debian:bookworm
LABEL maintainers="Vitaliy Filippov <vitalif@yourcmc.ru>"
LABEL description="Vitastor CSI Driver"
@@ -36,8 +36,8 @@ ADD deb /deb
RUN apt-get update && \
apt-get -y install /deb/vitastor-client_*.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-bookworm-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
dpkg -x qemu-utils*.deb tmp1 && \
dpkg -x qemu-block-extra*.deb tmp1 && \
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.6
VITASTOR_VERSION ?= v3.0.3
all: build push
+1 -1
View File
@@ -49,7 +49,7 @@ spec:
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: vitalif/vitastor-csi:v3.0.6
image: vitalif/vitastor-csi:v3.0.3
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -121,7 +121,7 @@ spec:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
image: vitalif/vitastor-csi:v3.0.6
image: vitalif/vitastor-csi:v3.0.3
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
+1 -1
View File
@@ -5,7 +5,7 @@ package vitastor
const (
vitastorCSIDriverName = "csi.vitastor.io"
vitastorCSIDriverVersion = "3.0.6"
vitastorCSIDriverVersion = "3.0.3"
)
// Config struct fills the parameters of request or user input
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# 26.04 Resolute Raccoon
docker build --build-arg DISTRO=ubuntu --build-arg REL=resolute -t vitastor-buildenv:resolute -f vitastor-buildenv.Dockerfile .
docker run -it --rm -e REL=resolute -v `dirname $0`/../:/root/vitastor vitastor-buildenv:resolute /root/vitastor/debian/vitastor-build.sh
+1 -1
View File
@@ -1,4 +1,4 @@
vitastor (3.0.6-1) unstable; urgency=medium
vitastor (3.0.3-1) unstable; urgency=medium
* Bugfixes
+1 -1
View File
@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.0.6
VITASTOR_VERSION ?= v3.0.3
all: build push
+1 -1
View File
@@ -1,3 +1,3 @@
Package: *
Pin: release n=trixie-backports
Pin: release n=bookworm-backports
Pin-Priority: 500
+2 -2
View File
@@ -1,2 +1,2 @@
deb http://vitastor.io/debian trixie main
#deb http://http.debian.net/debian/ trixie-backports main
deb http://vitastor.io/debian bookworm main
deb http://http.debian.net/debian/ bookworm-backports main
@@ -7,7 +7,7 @@ PartOf=vitastor.target
[Service]
Restart=always
EnvironmentFile=/etc/vitastor/docker.conf
ExecStart=bash -c 'docker run --rm -i -v /etc/vitastor:/etc/vitastor -v /dev:/dev -v /run:/run -e SYSTEMD_IN_CHROOT=0 \
ExecStart=bash -c 'docker run --rm -i -v /etc/vitastor:/etc/vitastor -v /dev:/dev -v /run:/run \
--security-opt seccomp=unconfined --privileged --pid=host --log-driver none --network host --name vitastor vitastor:$VITASTOR_VERSION \
sleep.sh'
ExecStartPost=udevadm trigger
+1 -1
View File
@@ -4,7 +4,7 @@
#
# Desired Vitastor version
VITASTOR_VERSION=v3.0.6
VITASTOR_VERSION=v3.0.3
# Additional arguments for all containers
# For example, you may want to specify a custom logging driver here
+3 -2
View File
@@ -2,7 +2,8 @@
set -e
cp -urv /etc/systemd/system/vitastor* /host-etc/systemd/system/
cp -urv /etc/udev/rules.d /host-etc/udev/
cp -urv /etc/default /host-etc/
cp -urv /etc/systemd /host-etc/
cp -urv /etc/udev /host-etc/
cp -urnv /etc/vitastor /host-etc/
cp -urnv /opt/scripts/* /host-bin/
+7 -27
View File
@@ -38,7 +38,6 @@ with an OSD restart or, for some of them, even without restarting by updating co
- [journal_io](#journal_io)
- [journal_sector_buffer_count](#journal_sector_buffer_count)
- [journal_no_same_sector_overwrites](#journal_no_same_sector_overwrites)
- [skip_corrupted_meta_entries](#skip_corrupted_meta_entries)
- [throttle_small_writes](#throttle_small_writes)
- [throttle_target_iops](#throttle_target_iops)
- [throttle_target_mbs](#throttle_target_mbs)
@@ -280,19 +279,13 @@ Maximum number of journal flushers (see above min_flusher_count).
- Type: boolean
- Default: true
Only for the old store ([meta_format](layout-osd.en.md#meta_format) 2).
This parameter makes Vitastor keep a copy of metadata area in memory as it is
on disk, in addition to the metadata database. When the option is enabled, every
metadata entry is effectively stored in RAM twice. It's required for good performance
because it allows to avoid additional read-modify-write cycles during metadata
modifications. Metadata area size with the old store is roughly 224 MB per 1 TB
of data. You can turn the option off to reduce memory usage by this value, but
it will reduce performance.
For the new store ([meta_format](layout-osd.en.md#meta_format) 3), the option
may be changed in the future to support operation without loading full metadata
database in memory.
This parameter makes Vitastor always keep metadata area of the block device
in memory. It's required for good performance because it allows to avoid
additional read-modify-write cycles during metadata modifications. Metadata
area size is currently roughly 224 MB per 1 TB of data. You can turn it off
to reduce memory usage by this value, but it will hurt performance. This
restriction is likely to be removed in the future along with the upgrade
of the metadata storage scheme.
## inmemory_journal
@@ -371,8 +364,6 @@ blocks. The only situation when you should increase it to a larger value
is when you enable journal_no_same_sector_overwrites. In this case set
it to, for example, 1024.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
## journal_no_same_sector_overwrites
- Type: boolean
@@ -386,17 +377,6 @@ journal after writing it instead of possibly overwriting it the second time.
Most (99%) other SSDs don't need this option.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
## skip_corrupted_meta_entries
- Type: boolean
- Default: false
Only for the new store ([meta_format](layout-osd.en.md#meta_format) 3).
Allow OSD to start when some metadata entries or blocks are corrupted by
skipping them. Should be only used as an emergency measure.
## throttle_small_writes
- Type: boolean
+7 -28
View File
@@ -39,7 +39,6 @@
- [journal_io](#journal_io)
- [journal_sector_buffer_count](#journal_sector_buffer_count)
- [journal_no_same_sector_overwrites](#journal_no_same_sector_overwrites)
- [skip_corrupted_meta_entries](#skip_corrupted_meta_entries)
- [throttle_small_writes](#throttle_small_writes)
- [throttle_target_iops](#throttle_target_iops)
- [throttle_target_mbs](#throttle_target_mbs)
@@ -288,19 +287,13 @@ Flusher - это микро-поток (корутина), которая коп
- Тип: булево (да/нет)
- Значение по умолчанию: true
Только для старого хранилища ([meta_format](layout-osd.en.md#meta_format) 2).
Данный параметр заставляет Vitastor всегда держать копию области метаданных
в памяти в том же виде, как она лежит на диске, в дополнение к БД метаданных.
То есть, с включённой опцией каждая запись метаданных хранится в памяти дважды.
Это нужно, чтобы избегать дополнительных операций чтения с диска при записи.
Размер области метаданных в старом хранилище составляет примерно 224 МБ на
1 ТБ данных. Вы можете отключить опцию, чтобы снизить потребление памяти
примерно на эту величину, но при этом также снизится и производительность.
Для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3) опция,
возможно, будет переработана в будущем для поддержки работы без полной
загрузки метаданных в памяти.
Данный параметр заставляет Vitastor всегда держать область метаданных диска
в памяти. Это нужно, чтобы избегать дополнительных операций чтения с диска
при записи. Размер области метаданных на данный момент составляет примерно
224 МБ на 1 ТБ данных. При включении потребление памяти снизится примерно
на эту величину, но при этом также снизится и производительность. В будущем,
после обновления схемы хранения метаданных, это ограничение, скорее всего,
будет ликвидировано.
## inmemory_journal
@@ -383,8 +376,6 @@ fsync небезопасным даже с режимом "directsync".
нужно менять - это если вы включаете journal_no_same_sector_overwrites. В
этом случае установите данный параметр, например, в 1024.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
## journal_no_same_sector_overwrites
- Тип: булево (да/нет)
@@ -400,18 +391,6 @@ fsync небезопасным даже с режимом "directsync".
Почти все другие SSD (99% моделей) не требуют данной опции.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
## skip_corrupted_meta_entries
- Тип: булево (да/нет)
- Значение по умолчанию: false
Только для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3).
Разрешить OSD запускаться, даже если часть блоков или записей метаданных
повреждена, пропуская их. Опция предназначена для использования только в
целях аварийного восстановления.
## throttle_small_writes
- Тип: булево (да/нет)
+14 -46
View File
@@ -253,33 +253,21 @@
type: bool
default: true
info: |
Only for the old store ([meta_format](layout-osd.en.md#meta_format) 2).
This parameter makes Vitastor keep a copy of metadata area in memory as it is
on disk, in addition to the metadata database. When the option is enabled, every
metadata entry is effectively stored in RAM twice. It's required for good performance
because it allows to avoid additional read-modify-write cycles during metadata
modifications. Metadata area size with the old store is roughly 224 MB per 1 TB
of data. You can turn the option off to reduce memory usage by this value, but
it will reduce performance.
For the new store ([meta_format](layout-osd.en.md#meta_format) 3), the option
may be changed in the future to support operation without loading full metadata
database in memory.
This parameter makes Vitastor always keep metadata area of the block device
in memory. It's required for good performance because it allows to avoid
additional read-modify-write cycles during metadata modifications. Metadata
area size is currently roughly 224 MB per 1 TB of data. You can turn it off
to reduce memory usage by this value, but it will hurt performance. This
restriction is likely to be removed in the future along with the upgrade
of the metadata storage scheme.
info_ru: |
Только для старого хранилища ([meta_format](layout-osd.en.md#meta_format) 2).
Данный параметр заставляет Vitastor всегда держать копию области метаданных
в памяти в том же виде, как она лежит на диске, в дополнение к БД метаданных.
То есть, с включённой опцией каждая запись метаданных хранится в памяти дважды.
Это нужно, чтобы избегать дополнительных операций чтения с диска при записи.
Размер области метаданных в старом хранилище составляет примерно 224 МБ на
1 ТБ данных. Вы можете отключить опцию, чтобы снизить потребление памяти
примерно на эту величину, но при этом также снизится и производительность.
Для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3) опция,
возможно, будет переработана в будущем для поддержки работы без полной
загрузки метаданных в памяти.
Данный параметр заставляет Vitastor всегда держать область метаданных диска
в памяти. Это нужно, чтобы избегать дополнительных операций чтения с диска
при записи. Размер области метаданных на данный момент составляет примерно
224 МБ на 1 ТБ данных. При включении потребление памяти снизится примерно
на эту величину, но при этом также снизится и производительность. В будущем,
после обновления схемы хранения метаданных, это ограничение, скорее всего,
будет ликвидировано.
- name: inmemory_journal
type: bool
default: true
@@ -398,15 +386,11 @@
blocks. The only situation when you should increase it to a larger value
is when you enable journal_no_same_sector_overwrites. In this case set
it to, for example, 1024.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
info_ru: |
Максимальное число буферов, разрешённых для использования под записываемые
в журнал блоки метаданных. Единственная ситуация, в которой этот параметр
нужно менять - это если вы включаете journal_no_same_sector_overwrites. В
этом случае установите данный параметр, например, в 1024.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
- name: journal_no_same_sector_overwrites
type: bool
default: false
@@ -418,8 +402,6 @@
journal after writing it instead of possibly overwriting it the second time.
Most (99%) other SSDs don't need this option.
Not applicable to the new store ([meta_format](layout-osd.en.md#meta_format) 3).
info_ru: |
Включайте данную опцию для SSD вроде Intel D3-S4510 и D3-S4610, которые
ОЧЕНЬ не любят, когда ПО перезаписывает один и тот же сектор несколько раз
@@ -430,20 +412,6 @@
самого сектора.
Почти все другие SSD (99% моделей) не требуют данной опции.
Неприменимо к новому хранилищу ([meta_format](layout-osd.en.md#meta_format) 3).
- name: skip_corrupted_meta_entries
type: bool
default: false
info: |
Only for the new store ([meta_format](layout-osd.en.md#meta_format) 3).
Allow OSD to start when some metadata entries or blocks are corrupted by
skipping them. Should be only used as an emergency measure.
info_ru: |
Только для нового хранилища ([meta_format](layout-osd.en.md#meta_format) 3).
Разрешить OSD запускаться, даже если часть блоков или записей метаданных
повреждена, пропуская их. Опция предназначена для использования только в
целях аварийного восстановления.
- name: throttle_small_writes
type: bool
default: false
+3 -27
View File
@@ -26,37 +26,13 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
The instruction is very simple.
1. Download a Docker image of the desired version: \
`docker pull vitalif/vitastor:v3.0.6`
`docker pull vitalif/vitastor:v3.0.3`
2. Install scripts to the host system: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.6 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.3 install.sh`
3. Reload udev rules: \
`udevadm control --reload-rules`
4. Enable the vitastor-host service: \
`systemctl enable --now vitastor-host`
After these steps, you can return to [Quick Start](../intro/quickstart.en.md).
## Podman
If you use Podman, run the following commands as root before installing Vitastor containers:
```
ln -s podman /usr/bin/docker
mkdir -p /etc/systemd/system/systemd-udevd.service.d
cat >/etc/systemd/system/systemd-udevd.service.d/override.conf <<EOF
[Service]
CapabilityBoundingSet=~
SystemCallFilter=@mount capset
EOF
systemctl daemon-reload
systemctl restart systemd-udevd
```
Without it, udev fails to do calls into a Podman container and Vitastor disk detection doesn't work.
And you can return to [Quick Start](../intro/quickstart.en.md).
## Upgrading Containers
+2 -27
View File
@@ -25,39 +25,14 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
Инструкция по установке максимально простая.
1. Скачайте Docker-образ желаемой версии: \
`docker pull vitalif/vitastor:v3.0.6`
`docker pull vitalif/vitastor:v3.0.3`
2. Установите скрипты в хост-систему командой: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.6 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.3 install.sh`
3. Перезагрузите правила udev: \
`udevadm control --reload-rules`
4. Включите сервис vitastor-host: \
`systemctl enable --now vitastor-host`
После этого вы можете возвращаться к разделу [Быстрый старт](../intro/quickstart.ru.md).
## Podman
Если вы используете Podman, перед установкой контейнеров Vitastor выполните следующие
команды от имени суперпользователя:
```
ln -s podman /usr/bin/docker
mkdir -p /etc/systemd/system/systemd-udevd.service.d
cat >/etc/systemd/system/systemd-udevd.service.d/override.conf <<EOF
[Service]
CapabilityBoundingSet=~
SystemCallFilter=@mount capset
EOF
systemctl daemon-reload
systemctl restart systemd-udevd
```
Без этих настроек udev не может делать вызовы внутрь Podman-контейнеров и определение дисков Vitastor не работает.
## Обновление контейнеров
Сначала обязательно проверьте раздел [Обновление Vitastor](../usage/admin.ru.md#обновление-vitastor),
+1 -3
View File
@@ -33,17 +33,15 @@
- CentOS 7: `yum install https://vitastor.io/rpms/centos/7/vitastor-release.rpm`
- CentOS 8: `dnf install https://vitastor.io/rpms/centos/8/vitastor-release.rpm`
- AlmaLinux 9 and other RHEL 9 clones (Rocky, Oracle...): `dnf install https://vitastor.io/rpms/centos/9/vitastor-release.rpm`
- AlmaLinux 10 and other RHEL 10 clones: `dnf install https://vitastor.io/rpms/centos/10/vitastor-release.rpm`
- Enable EPEL: `yum/dnf install epel-release`
- Enable additional CentOS repositories:
- CentOS 7: `yum install centos-release-scl`
- CentOS 8: `dnf install centos-release-advanced-virtualization`
- RHEL 9/10 clones: not required
- RHEL 9 clones: not required
- Enable elrepo-kernel:
- CentOS 7: `yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm`
- CentOS 8: `dnf install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm`
- RHEL 9 clones: `dnf install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm`
- RHEL 10 clones: not required
- Install packages: `yum/dnf install vitastor lpsolve etcd kernel-ml qemu-kvm`
## Installation requirements
+1 -3
View File
@@ -33,17 +33,15 @@
- CentOS 7: `yum install https://vitastor.io/rpms/centos/7/vitastor-release.rpm`
- CentOS 8: `dnf install https://vitastor.io/rpms/centos/8/vitastor-release.rpm`
- AlmaLinux 9 и другие клоны RHEL 9 (Rocky, Oracle...): `dnf install https://vitastor.io/rpms/centos/9/vitastor-release.rpm`
- AlmaLinux 10 и другие клоны RHEL 10: `dnf install https://vitastor.io/rpms/centos/10/vitastor-release.rpm`
- Включите EPEL: `yum/dnf install epel-release`
- Включите дополнительные репозитории CentOS:
- CentOS 7: `yum install centos-release-scl`
- CentOS 8: `dnf install centos-release-advanced-virtualization`
- Клоны RHEL 9/10: не нужно
- Клоны RHEL 9: не нужно
- Включите elrepo-kernel:
- CentOS 7: `yum install https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm`
- CentOS 8: `dnf install https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm`
- Клоны RHEL 9: `dnf install https://www.elrepo.org/elrepo-release-9.el9.elrepo.noarch.rpm`
- Клоны RHEL 10: не нужно
- Установите пакеты: `yum/dnf install vitastor lpsolve etcd kernel-ml qemu-kvm`
## Установочные требования
+11 -7
View File
@@ -18,7 +18,7 @@ class AntiEtcdAdapter
cluster = cluster ? (''+(cluster||'')).split(/,+/) : [];
cluster = Object.keys(cluster.reduce((a, url) =>
{
a[url.toLowerCase().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '')] = true;
a[url.toLowerCase().replace(/^(https?:\/\/)?(.*?)(\/.*)?$/, (m, m1, m2) => (m1||'http://')+m2)] = true;
return a;
}, {}));
const cfg_port = config.antietcd_port;
@@ -26,7 +26,8 @@ class AntiEtcdAdapter
is_local['0.0.0.0'] = true;
is_local['::'] = true;
is_local[''] = true;
const selected = cluster.map(s => s.split(':', 2)).filter(ip => is_local[ip[0]] && (!cfg_port || ip[1] == cfg_port));
// split :, 3 -> <schema>:<//ip>:<port>
const selected = cluster.map(s => s.split(':', 3)).filter(ip => is_local[ip[1].substr(2)] && (!cfg_port || ip[2] == cfg_port));
if (selected.length > 1)
{
console.error('More than 1 etcd_address matches local IPs, please specify port');
@@ -35,12 +36,15 @@ class AntiEtcdAdapter
else if (selected.length == 1)
{
const antietcd_config = {
ip: selected[0][0],
port: selected[0][1],
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][1]+'.json.gz'),
ip: selected[0][1].substr(2),
port: selected[0][2],
cert: config.antietcd_cert,
key: config.antietcd_key,
ca: config.etcd_ca,
data: config.antietcd_data_file || ((config.antietcd_data_dir || '/var/lib/vitastor') + '/mon_'+selected[0][2]+'.json.gz'),
persist_filter: vitastor_persist_filter({ vitastor_prefix: config.etcd_prefix || '/vitastor' }),
node_id: selected[0][0]+':'+selected[0][1], // node_id = ip:port
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c] = "http://"+c; return a; }, {})),
node_id: selected[0][1].substr(2)+':'+selected[0][2], // node_id = ip:port
cluster: (cluster.length == 1 ? null : cluster.reduce((a, c) => { a[c.replace(/^(https?:\/\/)/, '')] = c; return a; }, {})),
cluster_key: (config.etcd_prefix || '/vitastor'),
stale_read: 1,
log_level: 1,
+15 -6
View File
@@ -1,7 +1,9 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
const fs = require('fs');
const http = require('http');
const https = require('https');
const WebSocket = require('ws');
const { b64, local_ips } = require('./utils.js');
@@ -15,11 +17,18 @@ 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.etcd_client_cert)
this.opts.cert = fs.readFileSync(config.etcd_client_cert, { encoding: 'utf-8' });
if (config.etcd_client_key)
this.opts.key = fs.readFileSync(config.etcd_client_key, { encoding: 'utf-8' });
if (config.etcd_ca)
this.opts.ca = fs.readFileSync(config.etcd_ca, { encoding: 'utf-8' });
}
parse_etcd_addresses(addrs)
@@ -39,7 +48,7 @@ class EtcdAdapter
for (let url of addrs)
{
let scheme = 'http';
url = url.trim().replace(/^(https?):\/\//, (m, m1) => { scheme = m1; return ''; });
url = url.trim().replace(/^(https?):\/\//i, (m, m1) => { scheme = m1.toLowerCase(); return ''; });
const slash = url.indexOf('/');
const colon = url.indexOf(':');
const is_local = is_local_ip[colon >= 0 ? url.substr(0, colon) : (slash >= 0 ? url.substr(0, slash) : url)];
@@ -130,7 +139,7 @@ class EtcdAdapter
}
ok(false);
}, this.mon.config.etcd_mon_timeout);
this.ws = new WebSocket(base+'/watch');
this.ws = new WebSocket(base+'/watch', this.opts);
this.ws_used_url = cur_addr;
const fail = () =>
{
@@ -272,7 +281,7 @@ class EtcdAdapter
{
throw new Error(MON_STOPPED);
}
const res = await POST(base+path, body, timeout);
const res = await POST(base+path, body, timeout, this.opts);
if (this.mon.stopped)
{
throw new Error(MON_STOPPED);
@@ -298,7 +307,7 @@ class EtcdAdapter
}
}
function POST(url, body, timeout)
function POST(url, body, timeout, opts)
{
return new Promise(ok =>
{
@@ -310,10 +319,10 @@ function POST(url, body, timeout)
req = null;
ok({ error: 'timeout' });
}, timeout) : null;
let req = http.request(url, { method: 'POST', headers: {
let req = (url.substr(0, 5) == 'https' ? https : http).request(url, { method: 'POST', headers: {
'Content-Type': 'application/json',
'Content-Length': body_text.length,
} }, (res) =>
}, ...(opts||{}) }, (res) =>
{
if (!req)
{
+4 -1
View File
@@ -45,7 +45,10 @@ const etcd_tree = {
config_path: "/etc/vitastor/vitastor.conf",
etcd_prefix: "/vitastor",
// etcd connection - configurable online
etcd_address: "10.0.115.10:2379/v3",
etcd_address: "http://10.0.115.10:2379/v3",
etcd_client_cert: "",
etcd_client_key: "",
etcd_ca: "",
// mon
etcd_mon_ttl: 5, // min: 1
etcd_mon_timeout: 1000, // ms. min: 0
+1 -1
View File
@@ -16,7 +16,7 @@ async function create_http_server(cfg, handler)
};
if (cfg.mon_https_ca)
{
tls.ca = await fsp.readFile(cfg.mon_https_ca);
tls.mon_https_ca = await fsp.readFile(cfg.mon_https_ca);
}
if (cfg.mon_https_client_auth)
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor-mon",
"version": "3.0.6",
"version": "3.0.3",
"description": "Vitastor SDS monitor service",
"main": "mon-main.js",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vitastor",
"version": "3.0.6",
"version": "3.0.3",
"description": "Low-level native bindings to Vitastor client library",
"main": "index.js",
"keywords": [
+232 -30
View File
@@ -50,7 +50,7 @@ from cinder.volume import configuration
from cinder.volume import driver
from cinder.volume import volume_utils
VITASTOR_VERSION = '3.0.6'
VITASTOR_VERSION = '3.0.3'
LOG = logging.getLogger(__name__)
@@ -275,7 +275,7 @@ class VitastorDriver(driver.CloneableImageVD,
LOG.exception('error getting vitastor pool stats: '+str(e))
self._stats = stats
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, run update the stats first.
@@ -291,14 +291,6 @@ class VitastorDriver(driver.CloneableImageVD,
else:
return (1 + resp['kvs'][0]['value'], resp['kvs'][0]['mod_revision'])
def _cli(self, descr, *args):
args = [ 'vitastor-cli', *args, *(self._vitastor_args()) ]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to "+descr+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
def create_volume(self, volume):
"""Creates a logical volume."""
@@ -310,7 +302,7 @@ class VitastorDriver(driver.CloneableImageVD,
LOG.debug("creating volume '%s'", vol_name)
self._cli('create volume', 'create', vol_name, '--size', size)
self._create_image(vol_name, { 'size': size })
if volume.encryption_key_id:
self._create_encrypted_volume(volume, volume.obj_context)
@@ -354,7 +346,7 @@ class VitastorDriver(driver.CloneableImageVD,
snap_name = utils.convert_str(snapshot.name)
if snap_name.find('@') >= 0 or snap_name.find('/') >= 0:
raise exception.VolumeBackendAPIException(data = '@ and / are forbidden in volume and snapshot names')
self._cli('create snapshot', 'snap-create', vol_name+'@'+snap_name)
self._create_snapshot(vol_name, vol_name+'@'+snap_name)
def snapshot_revert_use_temp_snapshot(self):
"""Disable the use of a temporary snapshot on revert."""
@@ -367,8 +359,21 @@ class VitastorDriver(driver.CloneableImageVD,
snap_name = utils.convert_str(snapshot.name)
# Delete the image and recreate it from the snapshot
self._cli('delete image', 'rm', vol_name)
self._cli('recreate image', 'create', '--parent', vol_name+'@'+snap_name, vol_name)
args = [ 'vitastor-cli', 'rm', vol_name, *(self._vitastor_args()) ]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to delete image "+vol_name+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
args = [
'vitastor-cli', 'create', '--parent', vol_name+'@'+snap_name,
vol_name, *(self._vitastor_args())
]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to recreate image "+vol_name+" from "+vol_name+"@"+snap_name+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
@@ -376,7 +381,15 @@ class VitastorDriver(driver.CloneableImageVD,
vol_name = utils.convert_str(snapshot.volume_name)
snap_name = utils.convert_str(snapshot.name)
self._cli('remove snapshot', 'rm', vol_name+'@'+snap_name)
args = [
'vitastor-cli', 'rm', vol_name+'@'+snap_name,
*(self._vitastor_args())
]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to remove snapshot "+vol_name+'@'+snap_name+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
def _child_count(self, parents):
children = 0
@@ -414,7 +427,13 @@ class VitastorDriver(driver.CloneableImageVD,
if src_vref.admin_metadata.get('readonly') == 'True':
# source volume is a volume-image cache entry or other readonly volume
# clone without intermediate snapshot
self._cli('create clone', 'create', '--parent', src_name, '--size', size, dest_name)
src = self._get_image(src_name)
LOG.debug("creating image '%s' from '%s'", dest_name, src_name)
new_cfg = self._create_image(dest_name, {
'size': size,
'parent_id': src['idx']['id'],
'parent_pool_id': src['idx']['pool_id'],
})
return {}
clone_snap = "%s@%s.clone_snap" % (src_name, dest_name)
@@ -427,12 +446,15 @@ class VitastorDriver(driver.CloneableImageVD,
clone_snap = dest_name
make_img = False
LOG.debug("creating snapshot '%s'", clone_snap)
self._cli('create base snapshot', 'snap-create', '--allow-existing', '1', clone_snap)
LOG.debug("creating layer '%s' under '%s'", clone_snap, src_name)
new_cfg = self._create_snapshot(src_name, clone_snap, True)
if make_img:
# Then create a clone from it
self._cli('create clone', 'create', '--parent', clone_snap, '--size', size, dest_name)
new_cfg = self._create_image(dest_name, {
'size': size,
'parent_id': new_cfg['parent_id'],
'parent_pool_id': new_cfg['parent_pool_id'],
})
return {}
@@ -442,8 +464,7 @@ class VitastorDriver(driver.CloneableImageVD,
vol_name = utils.convert_str(volume.name)
snap_name = utils.convert_str(snapshot.name)
src_snap = 'volume-'+snapshot.volume_id+'@'+snap_name
snap = self._get_image(src_snap)
snap = self._get_image('volume-'+snapshot.volume_id+'@'+snap_name)
if not snap:
raise exception.SnapshotNotFound(snapshot_id = snap_name)
snap_inode_id = int(resp['responses'][0]['kvs'][0]['value']['id'])
@@ -452,8 +473,12 @@ class VitastorDriver(driver.CloneableImageVD,
size = snap['cfg']['size']
if int(volume.size):
size = int(volume.size) * units.Gi
new_cfg = self._create_image(vol_name, {
'size': size,
'parent_id': snap['idx']['id'],
'parent_pool_id': snap['idx']['pool_id'],
})
self._cli('create clone', 'create', vol_name, '--size', size, '--parent', src_snap)
return {}
def _vitastor_args(self):
@@ -480,7 +505,49 @@ class VitastorDriver(driver.CloneableImageVD,
"""Deletes a logical volume."""
vol_name = utils.convert_str(volume.name)
self._cli('delete volume', 'rm', '--matching', vol_name, vol_name+'@*', '--progress', '0')
# Find the volume and all its snapshots
range_end = b'index/image/' + vol_name.encode('utf-8')
range_end = range_end[0 : len(range_end)-1] + six.int2byte(range_end[len(range_end)-1] + 1)
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'index/image/'+vol_name, 'range_end': range_end } },
] })
if len(resp['responses'][0]['kvs']) == 0:
# already deleted
LOG.info("volume %s no longer exists in backend", vol_name)
return
layers = resp['responses'][0]['kvs']
layer_ids = {}
for kv in layers:
inode_id = int(kv['value']['id'])
pool_id = int(kv['value']['pool_id'])
inode_pool_id = (pool_id << 48) | (inode_id & 0xffffffffffff)
layer_ids[inode_pool_id] = True
# Check if the volume has clones and raise 'busy' if so
children = self._child_count(layer_ids)
if children > 0:
raise exception.VolumeIsBusy(volume_name = vol_name)
# Clear data
for kv in layers:
args = [
'vitastor-cli', 'rm-data', '--pool', str(kv['value']['pool_id']),
'--inode', str(kv['value']['id']), '--progress', '0',
*(self._vitastor_args())
]
try:
self._execute(*args)
except processutils.ProcessExecutionError as exc:
LOG.error("Failed to remove layer "+kv['key']+": "+exc)
raise exception.VolumeBackendAPIException(data = exc.stderr)
# Delete all layers from etcd
requests = []
for kv in layers:
requests.append({ 'request_delete_range': { 'key': kv['key'] } })
requests.append({ 'request_delete_range': { 'key': 'config/inode/'+str(kv['value']['pool_id'])+'/'+str(kv['value']['id']) } })
self._etcd_txn({ 'success': requests })
def retype(self, context, volume, new_type, diff, host):
"""Change extra type specifications for a volume."""
@@ -500,6 +567,98 @@ class VitastorDriver(driver.CloneableImageVD,
"""Removes an export for a logical volume."""
pass
def _create_image(self, vol_name, cfg):
pool_s = str(self.cfg['pool_id'])
image_id = 0
while image_id == 0:
# check if the image already exists and find a free ID
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'index/image/'+vol_name } },
{ 'request_range': { 'key': 'index/maxid/'+pool_s } },
] })
if len(resp['responses'][0]['kvs']) > 0:
# already exists
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' already exists')
image_id, id_mod = self._next_id(resp['responses'][1])
# try to create the image
resp = self._etcd_txn({ 'compare': [
{ 'target': 'MOD', 'mod_revision': id_mod, 'key': 'index/maxid/'+pool_s },
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+vol_name },
{ 'target': 'VERSION', 'version': 0, 'key': 'config/inode/'+pool_s+'/'+str(image_id) },
], 'success': [
{ 'request_put': { 'key': 'index/maxid/'+pool_s, 'value': image_id } },
{ 'request_put': { 'key': 'index/image/'+vol_name, 'value': json.dumps({
'id': image_id, 'pool_id': self.cfg['pool_id']
}) } },
{ 'request_put': { 'key': 'config/inode/'+pool_s+'/'+str(image_id), 'value': json.dumps({
**cfg, 'name': vol_name,
}) } },
] })
if not resp.get('succeeded'):
# repeat
image_id = 0
def _create_snapshot(self, vol_name, snap_vol_name, allow_existing = False):
while True:
# check if the image already exists and snapshot doesn't
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'index/image/'+vol_name } },
{ 'request_range': { 'key': 'index/image/'+snap_vol_name } },
] })
if len(resp['responses'][0]['kvs']) == 0:
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
if len(resp['responses'][1]['kvs']) > 0:
if allow_existing:
snap_idx = resp['responses'][1]['kvs'][0]['value']
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'config/inode/'+str(snap_idx['pool_id'])+'/'+str(snap_idx['id']) } },
] })
if len(resp['responses'][0]['kvs']) == 0:
raise exception.VolumeBackendAPIException(data =
'Volume '+snap_vol_name+' is already indexed, but does not exist'
)
return resp['responses'][0]['kvs'][0]['value']
raise exception.VolumeBackendAPIException(
data = 'Volume '+snap_vol_name+' already exists'
)
vol_idx = resp['responses'][0]['kvs'][0]['value']
vol_idx_mod = resp['responses'][0]['kvs'][0]['mod_revision']
# get image inode config and find a new ID
resp = self._etcd_txn({ 'success': [
{ 'request_range': { 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']) } },
{ 'request_range': { 'key': 'index/maxid/'+str(self.cfg['pool_id']) } },
] })
if len(resp['responses'][0]['kvs']) == 0:
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
vol_cfg = resp['responses'][0]['kvs'][0]['value']
vol_mod = resp['responses'][0]['kvs'][0]['mod_revision']
new_id, id_mod = self._next_id(resp['responses'][1])
# try to redirect image to the new inode
new_cfg = {
**vol_cfg, 'name': vol_name, 'parent_id': vol_idx['id'], 'parent_pool_id': vol_idx['pool_id']
}
resp = self._etcd_txn({ 'compare': [
{ 'target': 'MOD', 'mod_revision': vol_idx_mod, 'key': 'index/image/'+vol_name },
{ 'target': 'MOD', 'mod_revision': vol_mod, 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']) },
{ 'target': 'MOD', 'mod_revision': id_mod, 'key': 'index/maxid/'+str(self.cfg['pool_id']) },
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+snap_vol_name },
{ 'target': 'VERSION', 'version': 0, 'key': 'config/inode/'+str(self.cfg['pool_id'])+'/'+str(new_id) },
], 'success': [
{ 'request_put': { 'key': 'index/maxid/'+str(self.cfg['pool_id']), 'value': new_id } },
{ 'request_put': { 'key': 'index/image/'+vol_name, 'value': json.dumps({
'id': new_id, 'pool_id': self.cfg['pool_id']
}) } },
{ 'request_put': { 'key': 'config/inode/'+str(self.cfg['pool_id'])+'/'+str(new_id), 'value': json.dumps(new_cfg) } },
{ 'request_put': { 'key': 'index/image/'+snap_vol_name, 'value': json.dumps({
'id': vol_idx['id'], 'pool_id': vol_idx['pool_id']
}) } },
{ 'request_put': { 'key': 'config/inode/'+str(vol_idx['pool_id'])+'/'+str(vol_idx['id']), 'value': json.dumps({
**vol_cfg, 'name': snap_vol_name, 'readonly': True
}) } }
] })
if resp.get('succeeded'):
return new_cfg
def initialize_connection(self, volume, connector):
data = {
'driver_volume_type': 'vitastor',
@@ -538,9 +697,13 @@ class VitastorDriver(driver.CloneableImageVD,
size = int(volume.size) * units.Gi
dest_name = utils.convert_str(volume.name)
# Find or create the base snapshot
self._cli('create base snapshot', 'create', '--allow-existing', '1', base_vol.name+'@.clone_snap')
snap_cfg = self._create_snapshot(base_vol.name, base_vol.name+'@.clone_snap', True)
# Then create a clone from it
self._cli('create clone', 'create', dest_name, '--size', size, '--parent', base_vol.name+'@.clone_snap')
new_cfg = self._create_image(dest_name, {
'size': size,
'parent_id': snap_cfg['parent_id'],
'parent_pool_id': snap_cfg['parent_pool_id'],
})
return ({}, True)
return ({}, False)
@@ -607,8 +770,26 @@ class VitastorDriver(driver.CloneableImageVD,
def extend_volume(self, volume, new_size):
"""Extend an existing volume."""
vol_name = utils.convert_str(volume.name)
size = int(new_size) * units.Gi
self._cli('extend volume', 'modify', vol_name, '--resize', new_size)
while True:
vol = self._get_image(vol_name)
if not vol:
raise exception.VolumeBackendAPIException(data = 'Volume '+vol_name+' does not exist')
# change size
size = int(new_size) * units.Gi
if size == vol['cfg']['size']:
break
resp = self._etcd_txn({ 'compare': [ {
'target': 'MOD',
'mod_revision': vol['cfg_mod'],
'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
} ], 'success': [
{ 'request_put': {
'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
'value': json.dumps({ **vol['cfg'], 'size': size }),
} },
] })
if resp.get('succeeded'):
break
LOG.debug(
"Extend volume from %(old_size)s GB to %(new_size)s GB.",
{'old_size': volume.size, 'new_size': new_size}
@@ -681,7 +862,28 @@ class VitastorDriver(driver.CloneableImageVD,
"""
from_name = self._get_existing_name(existing_ref)
to_name = utils.convert_str(volume.name)
self._cli('rename', 'modify', from_name, '--rename', to_name)
self._rename(from_name, to_name)
def _rename(self, from_name, to_name):
while True:
vol = self._get_image(from_name)
if not vol:
raise exception.VolumeBackendAPIException(data = 'Volume '+from_name+' does not exist')
to = self._get_image(to_name)
if to:
raise exception.VolumeBackendAPIException(data = 'Volume '+to_name+' already exists')
resp = self._etcd_txn({ 'compare': [
{ 'target': 'MOD', 'mod_revision': vol['idx_mod'], 'key': 'index/image/'+vol['cfg']['name'] },
{ 'target': 'MOD', 'mod_revision': vol['cfg_mod'], 'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']) },
{ 'target': 'VERSION', 'version': 0, 'key': 'index/image/'+to_name },
], 'success': [
{ 'request_delete_range': { 'key': 'index/image/'+vol['cfg']['name'] } },
{ 'request_put': { 'key': 'index/image/'+to_name, 'value': json.dumps(vol['idx']) } },
{ 'request_put': { 'key': 'config/inode/'+str(vol['idx']['pool_id'])+'/'+str(vol['idx']['id']),
'value': json.dumps({ **vol['cfg'], 'name': to_name }) } },
] })
if resp.get('succeeded'):
break
def unmanage(self, volume):
pass
@@ -754,7 +956,7 @@ class VitastorDriver(driver.CloneableImageVD,
snap_name = self._get_existing_name(existing_ref)
from_name = vol_name+'@'+snap_name
to_name = vol_name+'@'+utils.convert_str(snapshot.name)
self._cli('rename', 'modify', from_name, '--rename', to_name)
self._rename(from_name, to_name)
def unmanage_snapshot(self, snapshot):
"""Removes the specified snapshot from Cinder management."""
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.6
Version: 3.0.3
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.6.el10.tar.gz
Source0: vitastor-3.0.3.el10.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.6
Version: 3.0.3
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.6.el7.tar.gz
Source0: vitastor-3.0.3.el7.tar.gz
BuildRequires: gperftools-devel
BuildRequires: devtoolset-9-gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.6
Version: 3.0.3
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.6.el8.tar.gz
Source0: vitastor-3.0.3.el8.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-toolset-9-gcc-c++
+2 -2
View File
@@ -1,11 +1,11 @@
Name: vitastor
Version: 3.0.6
Version: 3.0.3
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.0.6.el9.tar.gz
Source0: vitastor-3.0.3.el9.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
+6 -1
View File
@@ -21,7 +21,7 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
endif()
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
add_definitions(-DVITASTOR_VERSION="3.0.6")
add_definitions(-DVITASTOR_VERSION="3.0.3")
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
add_link_options(-fno-omit-frame-pointer)
if (${WITH_ASAN})
@@ -75,6 +75,11 @@ if (RDMACM_LIBRARIES)
add_definitions(-DWITH_RDMACM)
endif (RDMACM_LIBRARIES)
find_package(OpenSSL)
if (OPENSSL_FOUND)
add_definitions(-DWITH_OPENSSL)
endif (OPENSSL_FOUND)
if (${WITH_SYSTEM_LIBURING})
pkg_check_modules(LIBURING REQUIRED liburing>=2.10)
include_directories(${LIBURING_INCLUDE_DIRS})
+1 -1
View File
@@ -4,7 +4,7 @@ project(vitastor)
# libvitastor_blk.a
add_library(vitastor_blk STATIC
../util/allocator.cpp ../util/crc32c.c ../util/ringloop.cpp
../util/allocator.cpp ../util/crc32c.c ../util/xxhash.c ../util/ringloop.cpp
multilist.cpp blockstore_heap.cpp blockstore_disk.cpp
blockstore.cpp blockstore_impl.cpp blockstore_init.cpp blockstore_open.cpp
blockstore_flush.cpp blockstore_read.cpp blockstore_stable.cpp blockstore_sync.cpp blockstore_write.cpp
+8 -2
View File
@@ -83,13 +83,17 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
{
data_csum_type = BLOCKSTORE_CSUM_CRC32C;
}
else if (config["data_csum_type"] == "xxh3_32")
{
data_csum_type = BLOCKSTORE_CSUM_XXH3_32;
}
else if (config["data_csum_type"] == "" || config["data_csum_type"] == "none")
{
data_csum_type = BLOCKSTORE_CSUM_NONE;
}
else
{
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\" and \"none\" are supported");
throw std::runtime_error("data_csum_type="+config["data_csum_type"]+" is unsupported, only \"crc32c\", \"xxh3_32\" and \"none\" are supported");
}
csum_block_size = parse_size(config["csum_block_size"]);
discard_on_start = config.find("discard_on_start") != config.end() &&
@@ -173,7 +177,9 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
}
if (data_block_size / bitmap_granularity < 8)
{
throw std::runtime_error("Data block size must be at least bitmap_granularity*8");
fprintf(stderr, "Warning: block_size (%u) / bitmap_granularity (%u) = %u bits. "
"Consider using larger block_size or bitmap_granularity for better performance.\n",
data_block_size, bitmap_granularity, data_block_size / bitmap_granularity);
}
if (!data_csum_type)
{
+4 -3
View File
@@ -16,6 +16,7 @@
#define BLOCKSTORE_CSUM_NONE 0
// Lower byte of checksum type is its length
#define BLOCKSTORE_CSUM_CRC32C 0x104
#define BLOCKSTORE_CSUM_XXH3_32 0x204
#define MOCK_DATA_FD 1000
#define MOCK_META_FD 1001
@@ -26,14 +27,14 @@ class allocator_t;
struct blockstore_disk_t
{
std::string data_device, meta_device, journal_device;
uint64_t data_block_size;
uint32_t data_block_size;
uint64_t cfg_journal_size, cfg_data_size;
// Required write alignment and journal/metadata/data areas' location alignment
uint32_t disk_alignment = 4096;
// Journal block size - minimum_io_size of the journal device is the best choice
uint64_t journal_block_size = 4096;
uint32_t journal_block_size = 4096;
// Metadata block size - minimum_io_size of the metadata device is the best choice
uint64_t meta_block_size = 4096;
uint32_t meta_block_size = 4096;
// Atomic write size of the data block device
uint32_t atomic_write_size = 4096;
// Whether we should set RWF_ATOMIC on atomic writes
+115 -43
View File
@@ -12,6 +12,7 @@
#include "blockstore_heap.h"
#include "../util/allocator.h"
#include "../util/crc32c.h"
#include "../util/xxhash.h"
#include "../util/malloc_or_die.h"
#define BS_HEAP_FREE_MVCC 1
@@ -62,19 +63,19 @@ uint32_t blockstore_heap_t::get_simple_entry_size()
uint32_t blockstore_heap_t::get_big_entry_size()
{
return sizeof(heap_big_write_t) + dsk->clean_entry_bitmap_size*2 +
(!dsk->data_csum_type ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
(!dsk->csum_block_size ? 0 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
}
uint32_t blockstore_heap_t::get_big_intent_entry_size()
{
return sizeof(heap_big_intent_t) + dsk->clean_entry_bitmap_size*2 +
(!dsk->data_csum_type ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
(!dsk->csum_block_size ? 4 : dsk->data_block_size/dsk->csum_block_size * (dsk->data_csum_type & 0xFF));
}
uint32_t blockstore_heap_t::get_small_entry_size(uint32_t offset, uint32_t len)
{
return sizeof(heap_small_write_t) + dsk->clean_entry_bitmap_size +
(!dsk->data_csum_type ? 4 : (dsk->data_csum_type & 0xFF) *
(!dsk->csum_block_size ? 4 : (dsk->data_csum_type & 0xFF) *
((offset+len+dsk->csum_block_size-1)/dsk->csum_block_size - offset/dsk->csum_block_size));
}
@@ -89,7 +90,7 @@ uint32_t blockstore_heap_t::get_csum_size(heap_entry_t *wr)
uint32_t blockstore_heap_t::get_csum_size(uint32_t entry_type, uint32_t offset, uint32_t len)
{
if (!dsk->data_csum_type)
if (!dsk->csum_block_size)
{
return 0;
}
@@ -212,15 +213,24 @@ void heap_entry_t::set_big_location(blockstore_heap_t *heap, uint64_t location)
big().block_num = location / heap->dsk->data_block_size;
}
uint32_t heap_entry_t::calc_crc32c()
uint32_t heap_entry_t::calc_checksum(blockstore_disk_t *dsk)
{
auto old_crc32c = crc32c;
crc32c = 0;
uint32_t res = ::crc32c(0, (uint8_t*)this, size);
crc32c = old_crc32c;
auto old_checksum = checksum;
checksum = 0;
uint32_t res = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
res = (uint32_t)XXH3_64bits(this, size);
else
res = ::crc32c(0, (uint8_t*)this, size);
checksum = old_checksum;
return res;
}
uint32_t heap_entry_t::calc_checksum(blockstore_heap_t *heap)
{
return calc_checksum(heap->dsk);
}
uint64_t blockstore_heap_t::get_pg_id(inode_t inode, uint64_t stripe)
{
uint64_t pg_num = 0;
@@ -301,13 +311,12 @@ int blockstore_heap_t::read_blocks(uint64_t disk_offset, uint64_t disk_size, uin
heap_entry_t *wr = (heap_entry_t*)data;
if (wr->size > dsk->meta_block_size-block_offset)
{
fprintf(stderr, "Error: entry is too large in metadata block %u at %u (%u > max %ju bytes). ",
fprintf(stderr, "Error: entry is too large in metadata block %u at %u (%u > max %u bytes). ",
block_num, block_offset, wr->size, dsk->meta_block_size-block_offset);
corrupted_block:
if (allow_corrupted)
{
fprintf(stderr, "Metadata block is corrupted, skipping\n");
recheck_modified_blocks.insert(block_num);
break;
}
else
@@ -358,7 +367,6 @@ corrupted_object:
if (allow_corrupted)
{
fprintf(stderr, "Entry is corrupted, skipping\n");
recheck_modified_blocks.insert(block_num);
block_offset += wr->size;
continue;
}
@@ -385,12 +393,12 @@ corrupted_object:
goto corrupted_object;
}
// Verify crc
uint32_t expected_crc32c = wr->calc_crc32c();
if (wr->crc32c != expected_crc32c)
uint32_t expected_checksum = wr->calc_checksum(this);
if (wr->checksum != expected_checksum)
{
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (crc32c mismatch: expected %08x, got %08x). ",
fprintf(stderr, "Error: entry %jx:%jx v%ju l%ju in metadata block %u at %u is corrupt (checksum mismatch: expected %08x, got %08x). ",
wr->inode, wr->stripe, wr->version, wr->lsn,
block_num, block_offset, expected_crc32c, wr->crc32c);
block_num, block_offset, expected_checksum, wr->checksum);
goto corrupted_object;
}
// Verify offset & len
@@ -878,7 +886,11 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
len = wr->big_intent().len;
else
assert(0);
uint32_t real_csum = crc32c(0, data, len);
uint32_t real_csum = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
real_csum = (uint32_t)XXH3_64bits(data, len);
else
real_csum = crc32c(0, data, len);
if (set)
{
*wr_csum = real_csum;
@@ -928,11 +940,26 @@ static uint32_t crc32c_iter(uint32_t prev_crc, const std::function<uint8_t*(uint
return prev_crc;
}
static void xxh3_iter(XXH3_state_t* xxh3_state, const std::function<uint8_t*(uint32_t start, uint32_t & len)> & next, uint32_t pos, uint32_t size)
{
uint32_t cur_len = 0;
while (size > 0)
{
uint8_t *data = next(pos, cur_len);
assert(data);
cur_len = (cur_len < size ? cur_len : size);
XXH3_64bits_update(xxh3_state, data, cur_len);
pos += cur_len;
size -= cur_len;
}
}
bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bitmap,
uint32_t start, uint32_t end, std::function<uint8_t*(uint32_t start, uint32_t & len)> next,
bool set, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb)
{
bool res = true;
XXH3_state_t* xxh3_state = NULL;
uint32_t pos = start;
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
uint32_t block_crc = 0;
@@ -949,42 +976,89 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
pos += dsk->bitmap_granularity;
// zero padding at the beginning or at the end of the block is not counted
if (pos > prev && prev > 0 && pos < block_end)
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
{
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
uint32_t zeropad = pos-prev;
while (zeropad > 0)
{
uint32_t zerolen = zeropad > 4096 ? 4096 : zeropad;
XXH3_64bits_update(xxh3_state, zero_page, zerolen);
zeropad -= zerolen;
}
}
else
block_crc = crc32c_pad(block_crc, NULL, 0, pos-prev, 0);
}
prev = pos;
while (pos < end && pos < block_end && (bitmap[pos/dsk->bitmap_granularity/8] & (1 << ((pos/dsk->bitmap_granularity) % 8))))
pos += dsk->bitmap_granularity;
if (pos > prev)
{
isset = true;
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
xxh3_iter(xxh3_state, next, prev, pos-prev);
}
else
block_crc = crc32c_iter(block_crc, next, prev, pos-prev);
}
prev = pos;
}
}
else
{
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
{
if (!xxh3_state)
{
xxh3_state = XXH3_createState();
XXH3_64bits_reset(xxh3_state);
}
xxh3_iter(xxh3_state, next, pos, (end > block_end ? block_end : end)-pos);
}
else
block_crc = crc32c_iter(block_crc, next, pos, (end > block_end ? block_end : end)-pos);
pos = (end > block_end ? block_end : end);
isset = true;
}
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
{
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
XXH3_64bits_reset(xxh3_state);
}
if (set)
{
*block_csums = block_crc;
}
else if (isset && block_crc != *block_csums)
{
res = false;
if (bad_block_cb)
{
bad_block_cb(blk_start, *block_csums, block_crc);
res = false;
}
else
return false;
break;
}
block_end += dsk->csum_block_size;
block_crc = 0;
block_csums++;
}
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32 && xxh3_state)
{
block_crc = (uint32_t)XXH3_64bits_digest(xxh3_state);
XXH3_freeState(xxh3_state);
xxh3_state = NULL;
}
return res;
}
@@ -1336,7 +1410,7 @@ int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
insert_list_item(li);
li->block_num = block_num;
new_wr->size = wr_size;
new_wr->crc32c = new_wr->calc_crc32c();
new_wr->checksum = new_wr->calc_checksum(this);
return 0;
}
@@ -1396,7 +1470,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->data_csum_type)
if (dsk->csum_block_size)
{
memset(wr->get_checksums(this), 0, get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true, offset, len);
@@ -1425,7 +1499,7 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
memset(wr->get_ext_bitmap(this), 0, dsk->clean_entry_bitmap_size);
memset(wr->get_int_bitmap(this), 0, dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->data_csum_type)
if (dsk->csum_block_size)
memset(wr->get_checksums(this), 0, get_csum_size(wr));
calc_checksums(wr, (uint8_t*)data, true);
*obj_ptr = wr;
@@ -1463,7 +1537,7 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
memcpy(wr->get_ext_bitmap(this), obj->get_ext_bitmap(this), dsk->clean_entry_bitmap_size);
memcpy(wr->get_int_bitmap(this), obj->get_int_bitmap(this), dsk->clean_entry_bitmap_size);
bitmap_set(wr->get_int_bitmap(this), offset, len, dsk->bitmap_granularity);
if (dsk->data_csum_type)
if (dsk->csum_block_size)
{
if (checksums)
memcpy(wr->get_checksums(this), checksums, get_csum_size(wr));
@@ -1510,7 +1584,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
new_wr->set_big_location(this, compact_location);
memcpy(new_wr->get_int_bitmap(this), new_int_bitmap, dsk->clean_entry_bitmap_size);
memcpy(new_wr->get_ext_bitmap(this), new_ext_bitmap, dsk->clean_entry_bitmap_size);
if (dsk->data_csum_type && new_csums)
if (dsk->csum_block_size && new_csums)
memcpy(new_wr->get_checksums(this), new_csums, dsk->data_block_size/dsk->csum_block_size*(dsk->data_csum_type & 0xFF));
});
}
@@ -1980,21 +2054,24 @@ int blockstore_heap_t::list_objects(uint32_t pg_num, object_id min_oid, object_i
return;
}
uint64_t stable_version = 0;
iterate_with_stable(obj, UINT64_MAX, [&](heap_entry_t* wr, bool stable)
auto first_wr = obj;
for (auto wr = first_wr; wr; wr = prev(wr))
{
if (stable)
if ((wr->entry_type & BS_HEAP_STABLE) || wr->type() == BS_HEAP_COMMIT || wr->type() == BS_HEAP_ROLLBACK)
{
stable_version = wr->version;
return false;
break;
}
if (unstable_size >= unstable_alloc)
else
{
unstable_alloc = (!unstable_alloc ? 128 : unstable_alloc*2);
unstable = (obj_ver_id*)realloc_or_die(unstable, sizeof(obj_ver_id) * unstable_alloc);
if (unstable_size >= unstable_alloc)
{
unstable_alloc = (!unstable_alloc ? 128 : unstable_alloc*2);
unstable = (obj_ver_id*)realloc_or_die(unstable, sizeof(obj_ver_id) * unstable_alloc);
}
unstable[unstable_size++] = (obj_ver_id){ .oid = oid, .version = wr->version };
}
unstable[unstable_size++] = (obj_ver_id){ .oid = oid, .version = wr->version };
return true;
});
}
if (stable_version)
{
if (res_size >= res_alloc)
@@ -2291,9 +2368,7 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
}
if (!next)
{
// The last freed entry must be a deletion
assert(!prev);
assert(wr->entry_type == BS_HEAP_DELETE|BS_HEAP_STABLE);
auto & pg_idx = block_index[get_pg_id(wr->inode, wr->stripe)];
auto & inode_idx = pg_idx[wr->inode];
heap_inode_map_t::iterator li_it;
@@ -2397,10 +2472,7 @@ void inode_map_get(void *inode_idx, heap_inode_map_t::iterator & li_it, heap_lis
size_t map_n = ((size_t)inode_idx & IMAP_MALLOC_LOW_BITS);
if (!map_n)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
li_it = ((heap_inode_map_t*)inode_idx)->find(list_item_key(&stripe));
#pragma GCC diagnostic pop
li = li_it != ((heap_inode_map_t*)inode_idx)->end() ? *li_it : NULL;
}
else if (map_n == 1)
+5 -4
View File
@@ -43,7 +43,7 @@ struct __attribute__((__packed__)) heap_entry_t
{
uint16_t size;
uint16_t entry_type;
uint32_t crc32c;
uint32_t checksum;
uint64_t lsn;
uint64_t inode;
uint64_t stripe;
@@ -69,7 +69,8 @@ struct __attribute__((__packed__)) heap_entry_t
uint32_t *get_checksum(blockstore_heap_t *heap);
uint64_t big_location(blockstore_heap_t *heap);
void set_big_location(blockstore_heap_t *heap, uint64_t location);
uint32_t calc_crc32c();
uint32_t calc_checksum(blockstore_heap_t *heap);
uint32_t calc_checksum(blockstore_disk_t *dsk);
};
struct __attribute__((__packed__)) heap_small_write_t
@@ -80,7 +81,7 @@ struct __attribute__((__packed__)) heap_small_write_t
uint32_t offset;
uint32_t len;
// Also includes 1 bitmap and 1 crc32c after the bitmap if checksums are disabled
// Also includes 1 bitmap and 1 checksum after the bitmap if block checksums are disabled
};
struct __attribute__((__packed__)) heap_big_write_t
@@ -98,7 +99,7 @@ struct __attribute__((__packed__)) heap_big_intent_t
uint32_t offset;
uint32_t len;
// Also includes 2 bitmaps and 1 crc32c if checksums are disabled
// Also includes 2 bitmaps and 1 checksums if block checksums are disabled
};
struct __attribute__((__packed__)) heap_list_item_t
+1 -1
View File
@@ -193,12 +193,12 @@ void blockstore_impl_t::loop()
heap->start_block_write(block_num);
mb.sent = true;
}
pending_modified_blocks.clear();
int ret = ringloop->submit();
if (ret < 0)
{
throw std::runtime_error(std::string("io_uring_submit: ") + strerror(-ret));
}
pending_modified_blocks.clear();
if ((initial_ring_space - ringloop->space_left()) > 0)
{
live = true;
-1
View File
@@ -78,7 +78,6 @@ public:
// Suitable only for server SSDs with capacitors, requires disabled data and journal fsyncs
int immediate_commit = IMMEDIATE_NONE;
bool inmemory_meta = false;
bool skip_corrupted_meta_entries = false;
uint32_t meta_write_recheck_parallelism = 0;
// Maximum and minimum flusher count
unsigned max_flusher_count = 0, min_flusher_count = 0;
+2 -2
View File
@@ -145,7 +145,7 @@ resume_1:
printf(
"Configuration stored in metadata superblock"
" (meta_block_size=%u, data_block_size=%u, bitmap_granularity=%u, data_csum_type=%u, csum_block_size=%u, meta_area_size=%ju)"
" differs from OSD configuration (%ju/%ju/%u, %u/%u, %ju).\n",
" differs from OSD configuration (%u/%u/%u, %u/%u, %ju).\n",
hdr->meta_block_size, hdr->data_block_size, hdr->bitmap_granularity,
hdr->data_csum_type, hdr->csum_block_size, hdr->meta_area_size,
bs->dsk.meta_block_size, bs->dsk.data_block_size, bs->dsk.bitmap_granularity,
@@ -225,7 +225,7 @@ resume_4:
{
// Handle result
uint64_t loaded = 0;
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, bs->skip_corrupted_meta_entries, loaded);
int r = bs->heap->load_blocks(bufs[i].offset-bs->dsk.meta_block_size, bufs[i].size, bufs[i].buf, false, loaded);
if (r != 0)
exit(1);
entries_loaded += loaded;
-1
View File
@@ -28,7 +28,6 @@ void blockstore_impl_t::parse_config(blockstore_config_t & config, bool init)
throttle_target_parallelism = strtoull(config["throttle_target_parallelism"].c_str(), NULL, 10);
throttle_threshold_us = strtoull(config["throttle_threshold_us"].c_str(), NULL, 10);
perfect_csum_update = config["perfect_csum_update"] == "true" || config["perfect_csum_update"] == "1" || config["perfect_csum_update"] == "yes";
skip_corrupted_meta_entries = config["skip_corrupted_meta_entries"] == "true" || config["skip_corrupted_meta_entries"] == "1" || config["skip_corrupted_meta_entries"] == "yes";
if (config["autosync_writes"] != "")
{
autosync_writes = strtoull(config["autosync_writes"].c_str(), NULL, 10);
+7 -10
View File
@@ -22,23 +22,21 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
ring_data_t *data = ((ring_data_t*)sqe->user_data);
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, dsk.meta_block_size);
data->iov = (struct iovec){ buf, (size_t)dsk.meta_block_size };
data->callback = [this, modified_block](ring_data_t *data)
data->callback = [this, modified_block, buf](ring_data_t *data)
{
free(buf);
live = true;
if (data->res != data->iov.iov_len)
{
// FIXME: our state becomes corrupted after a write error. maybe do something better than just die
disk_error_abort("data write", data->res, data->iov.iov_len);
}
auto it = modified_blocks.find(modified_block);
assert(it != modified_blocks.end());
free(it->second.buf);
modified_blocks.erase(it);
modified_blocks.erase(modified_block);
heap->complete_block_write(modified_block);
ringloop->wakeup();
};
io_uring_prep_writev(
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + (modified_block+1)*dsk.meta_block_size
);
unsynced_meta_write_count++;
pending_modified_blocks.push_back(modified_block);
@@ -251,12 +249,13 @@ enospc:
goto enospc;
assert(res == 0);
PRIV(op)->lsn = obj->lsn;
if (op->len)
heap->use_buffer_area(op->oid.inode, loc, op->len);
prepare_meta_block_write(PRIV(op)->modified_block);
PRIV(op)->pending_ops++;
if (op->len > 0)
{
// Prepare buffered data write
heap->use_buffer_area(op->oid.inode, loc, op->len);
if (dsk.inmemory_journal)
{
memcpy((uint8_t*)buffer_area + loc, op->buf, op->len);
@@ -349,7 +348,6 @@ resume_12:
}
resume_4:
{
BS_SUBMIT_CHECK_SQES(1);
auto obj = heap->read_entry(op->oid);
int res = 0;
if (PRIV(op)->write_type == _REDIRECT_INTENT)
@@ -406,12 +404,11 @@ resume_6:
if (ref_us > exec_us + throttle_threshold_us)
{
// Pause reply
PRIV(op)->pending_ops++;
PRIV(op)->op_state = 7;
// Remember that the timer can in theory be called right here
tfd->set_timer_us(ref_us-exec_us, false, [this, op](int timer_id)
{
PRIV(op)->pending_ops--;
PRIV(op)->op_state = 8;
ringloop->wakeup();
});
return 1;
+1 -1
View File
@@ -189,7 +189,7 @@ resume_1:
printf(
"Configuration stored in metadata superblock"
" (meta_block_size=%u, data_block_size=%u, bitmap_granularity=%u, data_csum_type=%u, csum_block_size=%u)"
" differs from OSD configuration (%ju/%ju/%u, %u/%u).\n",
" differs from OSD configuration (%u/%u/%u, %u/%u).\n",
hdr->meta_block_size, hdr->data_block_size, hdr->bitmap_granularity,
hdr->data_csum_type, hdr->csum_block_size,
bs->dsk.meta_block_size, bs->dsk.data_block_size, bs->dsk.bitmap_granularity,
+2 -1
View File
@@ -620,7 +620,8 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
else if (from_journal)
{
// Don't scan bitmap - journal writes don't have holes (internal bitmap)!
uint8_t *csum = !dsk.csum_block_size ? 0 : (clean_entry_bitmap + dsk.clean_entry_bitmap_size);
uint8_t *csum = !dsk.csum_block_size ? 0 : (clean_entry_bitmap + dsk.clean_entry_bitmap_size +
item_start/dsk.csum_block_size*(dsk.data_csum_type & 0xFF));
if (!fulfill_read(read_op, fulfilled, item_start, item_end,
(BS_ST_BIG_WRITE | BS_ST_STABLE), 0, clean_loc + item_start, 0, csum, dyn_data))
{
+5 -3
View File
@@ -13,10 +13,10 @@ if (RDMACM_LIBRARIES)
endif (RDMACM_LIBRARIES)
add_library(vitastor_common STATIC
../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp
msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp
http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM}
)
target_link_libraries(vitastor_common pthread)
target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES})
target_compile_options(vitastor_common PUBLIC -fPIC)
# libvitastor_client.so
@@ -33,6 +33,7 @@ target_link_libraries(vitastor_client
${LIBURING_LIBRARIES}
${IBVERBS_LIBRARIES}
${RDMACM_LIBRARIES}
${OPENSSL_LIBRARIES}
)
set_target_properties(vitastor_client PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
configure_file(vitastor.pc.in vitastor.pc @ONLY)
@@ -98,9 +99,10 @@ endif (${WITH_QEMU})
add_executable(test_cluster_client
EXCLUDE_FROM_ALL
../test/test_cluster_client.cpp
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp
pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp
etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp
)
target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES})
target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__)
target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock)
add_dependencies(build_tests test_cluster_client)
+15 -2
View File
@@ -958,9 +958,22 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
{
op->flags |= OP_IMMEDIATE_COMMIT;
}
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end() && ino_it->second.enc)
{
// FIXME: Rework client API by adding open/close and cache inode information in the "FD"
op->enc = ino_it->second.enc;
if (!op->enc->bitmap_granularity)
{
op->enc->bitmap_granularity = pool_it->second.bitmap_granularity;
}
}
else
{
op->enc.reset();
}
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY))
{
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
{
op->retval = -EROFS;
@@ -972,7 +985,6 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
op->deoptimise_snapshot = false;
if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
{
auto ino_it = st_cli.inode_config.find(op->inode);
if (ino_it != st_cli.inode_config.end())
{
int chain_size = 0;
@@ -1435,6 +1447,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
? (uint8_t*)op->part_bitmaps + pg_bitmap_size*i : NULL),
.bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP
? pg_bitmap_size : 0),
.enc = op->enc,
.callback = cb ? cb : [this, part](osd_op_t *op_part)
{
handle_op_part(part);
+1
View File
@@ -71,6 +71,7 @@ protected:
cluster_op_t *prev = NULL, *next = NULL;
int prev_wait = 0;
uint64_t flush_id = 0;
std::shared_ptr<inode_enc_t> enc;
friend class cluster_client_t;
friend class writeback_cache_t;
};
+60 -35
View File
@@ -22,14 +22,19 @@ etcd_state_client_t::~etcd_state_client_t()
stop_ws_keepalive();
if (etcd_watch_ws)
{
http_close(etcd_watch_ws);
http_destroy(etcd_watch_ws);
etcd_watch_ws = NULL;
}
if (keepalive_client)
{
http_close(keepalive_client);
http_destroy(keepalive_client);
keepalive_client = NULL;
}
if (http_ctx)
{
http_context_destroy(http_ctx);
http_ctx = NULL;
}
#endif
if (load_pgs_timer_id >= 0)
{
@@ -72,10 +77,27 @@ std::vector<std::string> etcd_state_client_t::get_addresses()
return addrs;
}
http_context_t *etcd_state_client_t::get_http_ctx()
{
if (!http_ctx)
{
std::string error;
http_ctx = http_context_init(etcd_client_cert, etcd_client_key, etcd_ca, true, error);
if (!http_ctx)
{
fprintf(stderr, "Failed to initialize HTTP context: %s\n", error.c_str());
exit(1);
}
}
return http_ctx;
}
void etcd_state_client_t::etcd_call_oneshot(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;
bool ssl = etcd_address.substr(0, 8) == "https://";
etcd_address = etcd_address.substr(ssl ? 8 : 7);
int pos = etcd_address.find('/');
if (pos >= 0)
{
@@ -89,16 +111,16 @@ void etcd_state_client_t::etcd_call_oneshot(std::string etcd_address, std::strin
"Content-Length: "+std::to_string(req.size())+"\r\n"
"Connection: close\r\n"
"\r\n"+req;
auto http_cli = http_init(tfd);
auto cb = [http_cli, callback](const http_response_t *response)
auto http_cli = http_init(tfd, get_http_ctx());
auto cb = [http_cli, callback](http_message_t *response)
{
std::string err;
json11::Json data;
response->parse_json_response(err, data);
callback(err, data);
http_close(http_cli);
http_destroy(http_cli);
};
http_request(http_cli, etcd_address, req, { .timeout = timeout }, cb);
http_request(http_cli, etcd_address, req, { .timeout = timeout, .ssl = ssl }, cb);
}
void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int timeout,
@@ -112,6 +134,8 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
pick_next_etcd();
std::string etcd_address = selected_etcd_address;
std::string etcd_api_path;
bool ssl = etcd_address.substr(0, 8) == "https://";
etcd_address = etcd_address.substr(ssl ? 8 : 7);
int pos = etcd_address.find('/');
if (pos >= 0)
{
@@ -128,7 +152,7 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
"\r\n"+req;
retries--;
auto cb = [this, api, payload, timeout, retries, interval, callback,
cur_addr = selected_etcd_address](const http_response_t *response)
cur_addr = selected_etcd_address](http_message_t *response)
{
std::string err;
json11::Json data;
@@ -164,22 +188,21 @@ void etcd_state_client_t::etcd_call(std::string api, json11::Json payload, int t
callback(err, data);
};
if (!keepalive_client)
{
keepalive_client = http_init(tfd);
}
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true }, cb);
keepalive_client = http_init(tfd, get_http_ctx());
http_request(keepalive_client, etcd_address, req, { .timeout = timeout, .keepalive = true, .ssl = ssl }, cb);
}
void etcd_state_client_t::add_etcd_url(std::string addr)
{
if (addr.length() > 0)
{
bool ssl = false;
if (strtolower(addr.substr(0, 7)) == "http://")
addr = addr.substr(7);
else if (strtolower(addr.substr(0, 8)) == "https://")
{
fprintf(stderr, "HTTPS is unsupported for etcd. Either use plain HTTP or setup a local proxy for etcd interaction\n");
exit(1);
addr = addr.substr(8);
ssl = true;
}
if (!local_ips.size())
local_ips = getifaddr_list(std::vector<addr_mask_t>(), true);
@@ -194,6 +217,7 @@ void etcd_state_client_t::add_etcd_url(std::string addr)
check_addr = addr;
if (pos == std::string::npos)
addr += "/v3";
addr = (ssl ? "https://" : "http://") + addr;
bool local = false;
int i;
for (i = 0; i < local_ips.size(); i++)
@@ -239,6 +263,9 @@ void etcd_state_client_t::parse_config(const json11::Json & config)
add_etcd_url(ea.string_value());
}
}
this->etcd_client_cert = config["etcd_client_cert"].string_value();
this->etcd_client_key = config["etcd_client_key"].string_value();
this->etcd_ca = config["etcd_ca"].string_value();
this->etcd_prefix = config["etcd_prefix"].string_value();
if (this->etcd_prefix == "")
{
@@ -331,6 +358,8 @@ void etcd_state_client_t::start_etcd_watcher()
pick_next_etcd();
std::string etcd_address = selected_etcd_address;
std::string etcd_api_path;
bool ssl = etcd_address.substr(0, 8) == "https://";
etcd_address = etcd_address.substr(ssl ? 8 : 7);
int pos = etcd_address.find('/');
if (pos >= 0)
{
@@ -339,18 +368,17 @@ void etcd_state_client_t::start_etcd_watcher()
}
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 (!etcd_watch_ws)
etcd_watch_ws = http_init(tfd, get_http_ctx());
else
http_close(etcd_watch_ws);
open_websocket(etcd_watch_ws, etcd_address, etcd_api_path+"/watch", { .timeout = etcd_slow_timeout, .ssl = ssl },
[this, cur_addr = selected_etcd_address](http_message_t *msg)
{
if (msg->body.length())
{
@@ -393,7 +421,6 @@ void etcd_state_client_t::start_etcd_watcher()
fprintf(stderr, "Revisions before %ju were compacted by etcd, reloading state\n",
data["result"]["compact_revision"].uint64_value());
http_close(etcd_watch_ws);
etcd_watch_ws = NULL;
etcd_watch_revision_config = etcd_watch_revision_osd = etcd_watch_revision_pg = 0;
on_reload_hook();
}
@@ -414,10 +441,7 @@ void etcd_state_client_t::start_etcd_watcher()
}
// Save revision only if it's present in the message - because sometimes etcd sends something without a header, like:
// {"error": {"grpc_code": 14, "http_code": 503, "http_status": "Service Unavailable", "message": "error reading from server: EOF"}}
// 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())
if (etcd_watches_initialised == ETCD_TOTAL_WATCHES && !data["result"]["header"]["revision"].is_null())
{
// 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,
@@ -470,11 +494,6 @@ void etcd_state_client_t::start_etcd_watcher()
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>
@@ -551,11 +570,6 @@ void etcd_state_client_t::start_ws_keepalive()
{
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
@@ -1282,6 +1296,16 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
else
parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS);
}
std::shared_ptr<inode_enc_t> enc;
if (!value["enc_key"].string_value().empty())
{
std::vector<uint8_t> k = hexdecode(value["enc_key"].string_value());
if (k.size() == 512/8)
{
enc = std::make_shared<inode_enc_t>();
enc->key = std::move(k);
}
}
insert_inode_config((inode_config_t){
.num = inode_num,
.name = value["name"].string_value(),
@@ -1289,6 +1313,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
.parent_id = parent_inode_num,
.readonly = value["readonly"].bool_value(),
.deleted = value["deleted"].bool_value(),
.enc = enc,
.meta = value["meta"],
.mod_revision = kv.mod_revision,
});
+17 -1
View File
@@ -4,9 +4,10 @@
#pragma once
#include <set>
#include <memory>
#include "json11/json11.hpp"
#include "object_id.h"
#include "osd_id.h"
#include "timerfd_manager.h"
#define ETCD_CONFIG_WATCH_ID 1
@@ -75,6 +76,14 @@ struct pool_config_t
void *reshard_state = NULL;
};
struct inode_enc_t
{
int refs = 0;
std::vector<uint8_t> key;
// FIXME It may also contain snapshot chain and key information
uint32_t bitmap_granularity = 0;
};
struct inode_config_t
{
uint64_t num = 0;
@@ -83,6 +92,7 @@ struct inode_config_t
inode_t parent_id = 0;
bool readonly = false;
bool deleted = false;
std::shared_ptr<inode_enc_t> enc;
// Arbitrary metadata
json11::Json meta;
// Change revision of the metadata in etcd
@@ -96,6 +106,7 @@ struct inode_watch_t
};
struct http_co_t;
struct http_context_t;
struct __attribute__((visibility("default"))) etcd_state_client_t
{
@@ -125,9 +136,13 @@ public:
uint32_t global_immediate_commit = IMMEDIATE_NONE;
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;
@@ -160,6 +175,7 @@ public:
json11::Json::object serialize_inode_cfg(inode_config_t *cfg);
etcd_kv_t parse_etcd_kv(const json11::Json & kv_json);
std::vector<std::string> get_addresses();
http_context_t *get_http_ctx();
void etcd_call_oneshot(std::string etcd_address, std::string api, json11::Json payload, int timeout, std::function<void(std::string, json11::Json)> callback);
void etcd_call(std::string api, json11::Json payload, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
void etcd_txn(json11::Json txn, int timeout, int retries, int interval, std::function<void(std::string, json11::Json)> callback);
+433 -68
View File
@@ -10,9 +10,17 @@
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <stdexcept>
#ifdef WITH_OPENSSL
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#endif
#include "addr_util.h"
#include "str_util.h"
#include "json_util.h"
@@ -24,14 +32,51 @@
static std::string ws_format_frame(int type, uint64_t size);
static bool ws_parse_frame(std::string & buf, uint8_t & type, std::string & res);
static void parse_http_headers(std::string & res, http_response_t *parsed);
static void parse_http_headers(std::string & res, http_message_t *parsed, bool is_request);
struct http_context_t
{
std::string ssl_cert;
std::string ssl_key;
std::string ssl_ca;
#ifdef WITH_OPENSSL
SSL_CTX *ssl_ctx = NULL;
#endif
~http_context_t()
{
#ifdef WITH_OPENSSL
if (ssl_ctx)
{
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
}
#endif
}
};
struct http_call_t
{
std::string host;
std::string request;
http_options_t options;
std::function<void(http_message_t *)> cb;
};
struct http_co_t
{
http_context_t *ctx = NULL;
#ifdef WITH_OPENSSL
SSL *ssl_cli = NULL;
BIO *ssl_bio = NULL;
#endif
timerfd_manager_t *tfd;
std::function<void(const http_response_t*)> response_callback;
std::function<void(http_message_t*)> response_callback;
int request_timeout = 0;
bool ssl = false;
std::string host;
std::string request;
std::string ws_outbox;
@@ -39,7 +84,7 @@ struct http_co_t
bool want_streaming;
bool keepalive;
std::vector<std::function<void()>> keepalive_queue;
std::vector<http_call_t> keepalive_queue;
int state = 0;
std::string connected_host;
@@ -47,10 +92,10 @@ struct http_co_t
int timeout_id = -1;
int epoll_events = 0;
int sent = 0;
std::vector<char> rbuf;
std::vector<uint8_t> rbuf;
iovec read_iov, send_iov;
msghdr read_msg = { 0 }, send_msg = { 0 };
http_response_t parsed;
http_message_t parsed;
uint64_t target_response_size = 0;
int onstack = 0;
@@ -70,9 +115,14 @@ struct http_co_t
void submit_read(bool check_timeout);
void submit_send();
bool handle_read();
#ifdef WITH_OPENSSL
bool do_ssl_handshake(bool init_send);
void on_ssl_error(int res);
#endif
void post_message(uint8_t type, const std::string & msg);
void reply(const std::string & msg);
void send_request(const std::string & host, const std::string & request,
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
};
#define HTTP_CO_CLOSED 0
@@ -83,20 +133,64 @@ struct http_co_t
#define HTTP_CO_WEBSOCKET 5
#define HTTP_CO_CHUNKED 6
#define HTTP_CO_KEEPALIVE 7
#define HTTP_CO_SERVER 8
#define HTTP_CO_REQ_HDR_RECEIVED 9
#define HTTP_CO_REQUEST_RECEIVED 10
#define DEFAULT_TIMEOUT 5000
http_co_t *http_init(timerfd_manager_t *tfd)
http_context_t* http_context_init(const std::string & ssl_cert, const std::string & ssl_key,
const std::string & ssl_ca, bool verify_peer, std::string & error)
{
http_context_t *ctx = new http_context_t;
#ifdef WITH_OPENSSL
SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
ctx->ssl_cert = ssl_cert;
ctx->ssl_key = ssl_key;
ctx->ssl_ca = ssl_ca;
ctx->ssl_ctx = ssl_ctx;
if (!ssl_ctx)
goto init_err;
SSL_CTX_set_verify(ssl_ctx, verify_peer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
if (!SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION))
goto init_err;
if ((ssl_ca != "")
? !SSL_CTX_load_verify_locations(ssl_ctx, ssl_ca.c_str(), NULL)
: !SSL_CTX_set_default_verify_paths(ssl_ctx))
goto init_err;
if (ssl_cert != "" && ssl_key != "" &&
(!SSL_CTX_use_certificate_file(ssl_ctx, ssl_cert.c_str(), SSL_FILETYPE_PEM) ||
!SSL_CTX_use_PrivateKey_file(ssl_ctx, ssl_key.c_str(), SSL_FILETYPE_PEM)))
goto init_err;
#endif
return ctx;
init_err:
error = std::string("openssl initialization failed: ")+ERR_error_string(ERR_get_error(), NULL);
delete ctx;
return NULL;
}
void http_context_destroy(http_context_t *ctx)
{
delete ctx;
}
http_co_t *http_init(timerfd_manager_t *tfd, http_context_t *ctx)
{
http_co_t *handler = new http_co_t();
handler->tfd = tfd;
handler->state = HTTP_CO_CLOSED;
handler->ctx = ctx;
return handler;
}
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)> response_callback)
void open_websocket(http_co_t *handler, const std::string & host, const std::string & path,
const http_options_t & options, std::function<void(http_message_t *msg)> response_callback)
{
if (handler->state == HTTP_CO_KEEPALIVE && (handler->connected_host != host || handler->ssl != options.ssl))
handler->close_connection();
if (handler->state != HTTP_CO_KEEPALIVE && handler->state != HTTP_CO_CLOSED)
throw std::runtime_error("Attempt to open websocket on a keepalive stream");
std::string request = "GET "+path+" HTTP/1.1\r\n"
"Host: "+host+"\r\n"
"Upgrade: websocket\r\n"
@@ -104,29 +198,54 @@ http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, cons
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n";
http_co_t *handler = new http_co_t();
handler->tfd = tfd;
handler->state = HTTP_CO_CLOSED;
handler->host = host;
handler->request_timeout = timeout < 0 ? -1 : (timeout == 0 ? DEFAULT_TIMEOUT : timeout);
handler->request_timeout = options.timeout < 0 ? -1 : (options.timeout == 0 ? DEFAULT_TIMEOUT : options.timeout);
handler->want_streaming = false;
handler->keepalive = false;
handler->ssl = options.ssl;
handler->request = request;
handler->response_callback = response_callback;
handler->ws_outbox = "";
handler->response = "";
handler->sent = 0;
handler->parsed = {};
handler->start_ws_connection();
return handler;
}
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback)
const http_options_t & options, std::function<void(http_message_t *response)> response_callback)
{
handler->send_request(host, request, options, response_callback);
}
void http_serve(http_co_t *handler, int peer_fd, const http_options_t & options, std::function<void(http_message_t *msg)> request_callback)
{
if (handler->state != HTTP_CO_SERVER || handler->peer_fd != peer_fd)
handler->close_connection();
handler->host = "";
handler->request_timeout = options.timeout < 0 ? -1 : (options.timeout == 0 ? DEFAULT_TIMEOUT : options.timeout);
handler->want_streaming = false;
handler->keepalive = false;
handler->ssl = options.ssl;
handler->request = "";
handler->response_callback = request_callback;
handler->ws_outbox = "";
handler->response = "";
handler->sent = 0;
handler->parsed = {};
handler->peer_fd = peer_fd;
handler->state = HTTP_CO_SERVER;
handler->tfd->set_fd_handler(peer_fd, false, [handler](int peer_fd, int epoll_events)
{
handler->epoll_events |= epoll_events;
handler->handle_events();
});
}
void http_co_t::run_cb_and_clear()
{
parsed.eof = true;
std::function<void(const http_response_t*)> cb;
std::function<void(http_message_t*)> cb;
cb.swap(response_callback);
// Call callback after clearing it because otherwise we may hit reenterability problems
if (cb != NULL)
@@ -135,7 +254,7 @@ void http_co_t::run_cb_and_clear()
}
void http_co_t::send_request(const std::string & host, const std::string & request,
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback)
const http_options_t & options, std::function<void(http_message_t *response)> response_callback)
{
stackin();
if (state == HTTP_CO_WEBSOCKET)
@@ -145,20 +264,18 @@ void http_co_t::send_request(const std::string & host, const std::string & reque
}
else if (state != HTTP_CO_KEEPALIVE && state != HTTP_CO_CLOSED)
{
keepalive_queue.push_back([this, host, request, options, response_callback]()
{
this->send_request(host, request, options, response_callback);
});
keepalive_queue.emplace_back((http_call_t){ host, request, options, std::move(response_callback) });
stackout();
return;
}
if (state == HTTP_CO_KEEPALIVE && connected_host != host)
if (state == HTTP_CO_KEEPALIVE && (connected_host != host || ssl != options.ssl))
{
close_connection();
}
this->request_timeout = options.timeout < 0 ? 0 : (options.timeout == 0 ? DEFAULT_TIMEOUT : options.timeout);
this->want_streaming = options.want_streaming;
this->keepalive = options.keepalive;
this->ssl = options.ssl;
this->host = host;
this->request = request;
this->response = "";
@@ -190,7 +307,7 @@ void http_co_t::send_request(const std::string & host, const std::string & reque
else
{
close_connection();
parsed = { .error = "HTTP request timed out" };
parsed = { .error = "HTTP request timed out", .status_code = ETIMEDOUT };
run_cb_and_clear();
}
stackout();
@@ -204,6 +321,11 @@ void http_post_message(http_co_t *handler, uint8_t type, const std::string & msg
handler->post_message(type, msg);
}
void http_reply(http_co_t *handler, const std::string & reply)
{
handler->reply(reply);
}
void http_co_t::post_message(uint8_t type, const std::string & msg)
{
stackin();
@@ -213,7 +335,8 @@ void http_co_t::post_message(uint8_t type, const std::string & msg)
request += msg;
submit_send();
}
else if (state == HTTP_CO_KEEPALIVE || state == HTTP_CO_CHUNKED)
else if (state == HTTP_CO_KEEPALIVE || state == HTTP_CO_CHUNKED ||
state == HTTP_CO_SERVER || state == HTTP_CO_REQ_HDR_RECEIVED || state == HTTP_CO_REQUEST_RECEIVED)
{
throw std::runtime_error("Attempt to send websocket message on a regular HTTP connection");
}
@@ -225,12 +348,29 @@ void http_co_t::post_message(uint8_t type, const std::string & msg)
stackout();
}
void http_close(http_co_t *handler)
void http_co_t::reply(const std::string & reply)
{
stackin();
if (state != HTTP_CO_REQUEST_RECEIVED)
{
throw std::runtime_error("Attempt to send HTTP response in invalid connection state");
}
request += reply;
submit_send();
stackout();
}
void http_destroy(http_co_t *handler)
{
handler->end();
}
void http_response_t::parse_json_response(std::string & error, json11::Json & r) const
void http_close(http_co_t *handler)
{
handler->close_connection();
}
void http_message_t::parse_json_response(std::string & error, json11::Json & r) const
{
if (this->error != "")
{
@@ -277,6 +417,15 @@ void http_co_t::close_connection()
close(peer_fd);
peer_fd = -1;
}
#ifdef WITH_OPENSSL
if (ssl_cli)
{
// Frees client and bios at once
SSL_free(ssl_cli);
ssl_cli = NULL;
}
ssl_bio = NULL;
#endif
state = HTTP_CO_CLOSED;
connected_host = "";
response = "";
@@ -295,7 +444,7 @@ void http_co_t::start_ws_connection()
if (state != HTTP_CO_WEBSOCKET)
{
close_connection();
parsed = { .error = "Websocket connection timed out" };
parsed = { .error = "Websocket connection timed out", .status_code = ETIMEDOUT };
run_cb_and_clear();
}
stackout();
@@ -311,7 +460,7 @@ void http_co_t::start_connection()
if (!string_to_addr(host.c_str(), 1, 80, &addr))
{
close_connection();
parsed = { .error = "Invalid address: "+host };
parsed = { .error = "Invalid address: "+host, .status_code = EINVAL };
run_cb_and_clear();
stackout();
return;
@@ -320,19 +469,56 @@ void http_co_t::start_connection()
if (peer_fd < 0)
{
close_connection();
parsed = { .error = std::string("socket: ")+strerror(errno) };
parsed = { .error = std::string("socket: ")+strerror(errno), .status_code = errno };
run_cb_and_clear();
stackout();
return;
}
fcntl(peer_fd, F_SETFL, fcntl(peer_fd, F_GETFL, 0) | O_NONBLOCK);
epoll_events = 0;
#ifdef WITH_OPENSSL
// https://wiki.openssl.org/index.php/Hostname_validation
if (ssl)
{
if (!ctx)
goto init_err;
ssl_bio = BIO_new(BIO_s_socket());
if (!ssl_bio)
goto init_err;
if (!BIO_set_fd(ssl_bio, peer_fd, BIO_NOCLOSE))
goto init_err;
ssl_cli = SSL_new(ctx->ssl_ctx);
if (!ssl_cli)
goto init_err;
SSL_set_bio(ssl_cli, ssl_bio, ssl_bio);
if (!SSL_set_tlsext_host_name(ssl_cli, host.c_str()))
{
init_err:
if (ssl_cli)
{
SSL_free(ssl_cli);
ssl_cli = NULL;
}
else if (ssl_bio)
{
BIO_free(ssl_bio);
ssl_bio = NULL;
}
parsed = { .error = std::string("openssl initialization failed: ")+ERR_error_string(ERR_get_error(), NULL) };
response_callback(&parsed);
response_callback = NULL;
stackout();
return;
}
SSL_set_connect_state(ssl_cli);
}
#endif
// Finally call connect
int r = ::connect(peer_fd, (sockaddr*)&addr, sizeof(addr));
if (r < 0 && errno != EINPROGRESS)
{
close_connection();
parsed = { .error = std::string("connect: ")+strerror(errno) };
parsed = { .error = std::string("connect: ")+strerror(errno), .status_code = errno };
run_cb_and_clear();
stackout();
return;
@@ -367,6 +553,8 @@ void http_co_t::handle_events()
{
if (state == HTTP_CO_HEADERS_RECEIVED)
std::swap(parsed.body, response);
else if (state == HTTP_CO_SERVER || state == HTTP_CO_REQ_HDR_RECEIVED || state == HTTP_CO_REQUEST_RECEIVED)
parsed = { .error = "client has disconnected normally" };
close_connection();
run_cb_and_clear();
break;
@@ -388,7 +576,7 @@ void http_co_t::handle_connect_result()
if (result != 0)
{
close_connection();
parsed = { .error = std::string("connect: ")+strerror(result) };
parsed = { .error = std::string("connect: ")+strerror(result), .status_code = result };
run_cb_and_clear();
stackout();
return;
@@ -408,18 +596,44 @@ void http_co_t::handle_connect_result()
void http_co_t::submit_send()
{
stackin();
int res;
ssize_t res = 0;
again:
if (sent < request.size())
{
send_iov = (iovec){ .iov_base = (void*)(request.c_str()+sent), .iov_len = request.size()-sent };
send_msg.msg_iov = &send_iov;
send_msg.msg_iovlen = 1;
res = sendmsg(peer_fd, &send_msg, MSG_NOSIGNAL);
if (res < 0)
send_iov = (iovec){ .iov_base = (void*)(request.data()+sent), .iov_len = request.size()-sent };
#ifdef WITH_OPENSSL
if (!ssl)
#endif
{
res = -errno;
send_msg.msg_iov = &send_iov;
send_msg.msg_iovlen = 1;
res = sendmsg(peer_fd, &send_msg, MSG_NOSIGNAL);
if (res < 0)
res = -errno;
}
#ifdef WITH_OPENSSL
else
{
if (!do_ssl_handshake(false))
goto out;
int ok = SSL_write_ex(ssl_cli, send_iov.iov_base, send_iov.iov_len, (size_t*)&res);
if (!ok)
{
res = SSL_get_error(ssl_cli, ok);
if (res == SSL_ERROR_WANT_WRITE || res == 0)
res = 0;
else if (res == SSL_ERROR_WANT_READ)
goto out;
else if (res == SSL_ERROR_SYSCALL)
res = -errno;
else
{
on_ssl_error(res);
goto out;
}
}
}
#endif
if (res == -EAGAIN || res == -EINTR)
{
res = 0;
@@ -427,13 +641,33 @@ again:
else if (res < 0)
{
close_connection();
parsed = { .error = std::string("sendmsg: ")+strerror(errno) };
parsed = { .error = std::string("sendmsg: ")+strerror(errno), .status_code = errno };
run_cb_and_clear();
stackout();
return;
}
sent += res;
if (state == HTTP_CO_SENDING_REQUEST)
if (state == HTTP_CO_REQUEST_RECEIVED)
{
if (sent >= request.size())
{
if (!keepalive)
{
close_connection();
parsed = { .error = "connection is not keep-alive" };
run_cb_and_clear();
stackout();
return;
}
state = HTTP_CO_SERVER;
request = "";
sent = 0;
}
else
goto again;
handle_read();
}
else if (state == HTTP_CO_SENDING_REQUEST)
{
if (sent >= request.size())
state = HTTP_CO_REQUEST_SENT;
@@ -447,26 +681,53 @@ again:
goto again;
}
}
out:
stackout();
}
void http_co_t::submit_read(bool check_timeout)
{
stackin();
int res;
ssize_t res = 0;
again:
if (rbuf.size() != READ_BUFFER_SIZE)
{
rbuf.resize(READ_BUFFER_SIZE);
}
read_iov = { .iov_base = rbuf.data(), .iov_len = READ_BUFFER_SIZE };
read_msg.msg_iov = &read_iov;
read_msg.msg_iovlen = 1;
res = recvmsg(peer_fd, &read_msg, 0);
if (res < 0)
#ifdef WITH_OPENSSL
if (!ssl)
#endif
{
res = -errno;
read_msg.msg_iov = &read_iov;
read_msg.msg_iovlen = 1;
res = recvmsg(peer_fd, &read_msg, 0);
if (res < 0)
res = -errno;
}
#ifdef WITH_OPENSSL
else
{
if (!do_ssl_handshake(true))
goto out;
int ok = SSL_read_ex(ssl_cli, read_iov.iov_base, read_iov.iov_len, (size_t*)&res);
if (!ok)
{
res = SSL_get_error(ssl_cli, ok);
if (res == SSL_ERROR_WANT_READ)
res = -EAGAIN;
else if (res == SSL_ERROR_SYSCALL)
res = -errno;
else if (res == SSL_ERROR_ZERO_RETURN)
res = 0;
else
{
on_ssl_error(res);
goto out;
}
}
}
#endif
if (res == -EAGAIN || res == -EINTR)
{
if (check_timeout)
@@ -477,7 +738,7 @@ again:
{
// Timeout happened and there is no data to read
close_connection();
parsed = { .error = "HTTP request timed out" };
parsed = { .error = "HTTP request timed out", .status_code = ETIMEDOUT };
run_cb_and_clear();
}
}
@@ -492,23 +753,76 @@ again:
epoll_events = epoll_events & ~EPOLLIN;
if (state == HTTP_CO_HEADERS_RECEIVED)
std::swap(parsed.body, response);
close_connection();
if (res < 0)
parsed = { .error = std::string("recvmsg: ")+strerror(-res) };
parsed = { .error = std::string("recvmsg: ")+strerror(-res), .status_code = (int)-res };
else if (state == HTTP_CO_SERVER || state == HTTP_CO_REQ_HDR_RECEIVED || state == HTTP_CO_REQUEST_RECEIVED)
parsed = { .error = "client has disconnected normally" };
close_connection();
run_cb_and_clear();
}
else
{
response += std::string(rbuf.data(), res);
response += std::string((char*)rbuf.data(), res);
handle_read();
}
out:
stackout();
}
#ifdef WITH_OPENSSL
void http_co_t::on_ssl_error(int res)
{
close_connection();
if (res == SSL_ERROR_ZERO_RETURN)
{
// Client closed the connection
parsed = { .error = "peer closed the SSL connection" };
}
else
parsed = { .error = std::string("SSL error: ")+ERR_error_string(ERR_get_error(), NULL), .status_code = EIO };
run_cb_and_clear();
}
bool http_co_t::do_ssl_handshake(bool init_send)
{
if (SSL_is_init_finished(ssl_cli))
return true;
int r;
while (1)
{
r = SSL_do_handshake(ssl_cli);
if (r > 0)
{
// OK
if (init_send)
submit_send();
return true;
}
r = SSL_get_error(ssl_cli, r);
if (r == SSL_ERROR_WANT_READ)
{
break;
}
else
{
int errcode = ERR_get_error();
parsed = { .error = ERR_error_string(errcode, NULL), .status_code = EIO };
close_connection();
run_cb_and_clear();
return false;
}
}
return false;
}
#endif
bool http_co_t::handle_read()
{
stackin();
if (state == HTTP_CO_REQUEST_SENT)
if (state == HTTP_CO_REQUEST_RECEIVED)
{
}
else if (state == HTTP_CO_REQUEST_SENT)
{
int pos = response.find("\r\n\r\n");
if (pos >= 0)
@@ -520,7 +834,7 @@ bool http_co_t::handle_read()
timeout_id = -1;
}
state = HTTP_CO_HEADERS_RECEIVED;
parse_http_headers(response, &parsed);
parse_http_headers(response, &parsed, false);
if (parsed.status_code == 101 &&
parsed.headers.find("sec-websocket-accept") != parsed.headers.end() &&
parsed.headers["upgrade"] == "websocket" &&
@@ -544,7 +858,7 @@ bool http_co_t::handle_read()
{
// Sorry, unsupported response
close_connection();
parsed = { .error = "Response has neither Connection: close, nor Transfer-Encoding: chunked nor Content-Length headers" };
parsed = { .error = "Response has neither Connection: close, nor Transfer-Encoding: chunked nor Content-Length headers", .status_code = EINVAL };
run_cb_and_clear();
stackout();
return false;
@@ -556,14 +870,57 @@ bool http_co_t::handle_read()
}
}
}
if (state == HTTP_CO_HEADERS_RECEIVED && target_response_size > 0 && response.size() >= target_response_size)
else if (state == HTTP_CO_SERVER)
{
int pos = response.find("\r\n\r\n");
if (pos >= 0)
{
if (timeout_id >= 0)
{
// Timeout is cleared when headers are received
tfd->clear_timer(timeout_id);
timeout_id = -1;
}
state = HTTP_CO_REQ_HDR_RECEIVED;
parse_http_headers(response, &parsed, true);
auto conn_it = parsed.headers.find("connection");
keepalive = (conn_it != parsed.headers.end() && conn_it->second == "keep-alive");
auto enc_it = parsed.headers.find("transfer-encoding");
if (enc_it != parsed.headers.end())
{
// Sorry, unsupported request
close_connection();
parsed = { .error = "Chunked requests are not supported", .status_code = EINVAL };
run_cb_and_clear();
stackout();
return false;
}
auto len_it = parsed.headers.find("content-length");
target_response_size = stoull_full(len_it != parsed.headers.end() ? len_it->second : "");
if (!target_response_size)
{
state = HTTP_CO_REQUEST_RECEIVED;
response_callback(&parsed);
}
}
}
if ((state == HTTP_CO_HEADERS_RECEIVED || state == HTTP_CO_REQ_HDR_RECEIVED) &&
target_response_size > 0 && response.size() >= target_response_size)
{
std::swap(parsed.body, response);
if (!keepalive)
close_connection();
if (state == HTTP_CO_REQ_HDR_RECEIVED)
{
state = HTTP_CO_REQUEST_RECEIVED;
response_callback(&parsed);
}
else
state = HTTP_CO_KEEPALIVE;
run_cb_and_clear();
{
if (!keepalive)
close_connection();
else
state = HTTP_CO_KEEPALIVE;
run_cb_and_clear();
}
}
else if (state == HTTP_CO_CHUNKED && response.size() > 0)
{
@@ -626,26 +983,34 @@ void http_co_t::next_request()
{
if (keepalive_queue.size() > 0)
{
auto next = keepalive_queue[0];
keepalive_queue.erase(keepalive_queue.begin(), keepalive_queue.begin()+1);
next();
auto next = std::move(keepalive_queue[0]);
keepalive_queue.erase(keepalive_queue.begin());
send_request(next.host, next.request, next.options, next.cb);
}
}
static void parse_http_headers(std::string & res, http_response_t *parsed)
static void parse_http_headers(std::string & res, http_message_t *parsed, bool is_request)
{
int pos = res.find("\r\n");
pos = pos < 0 ? res.length() : pos+2;
std::string status_line = res.substr(0, pos);
int http_version;
char *status_text = NULL;
sscanf(status_line.c_str(), "HTTP/1.%d %d %ms", &http_version, &parsed->status_code, &status_text);
if (status_text)
if (!is_request)
{
parsed->status_line = status_text;
// %ms = allocate a buffer
free(status_text);
status_text = NULL;
sscanf(status_line.c_str(), "HTTP/1.%d %d %ms", &http_version, &parsed->status_code, &status_text);
if (status_text)
{
parsed->status_line = status_text;
// %ms = allocate a buffer
free(status_text);
status_text = NULL;
}
}
else
{
// Should be GET/POST / HTTP/1.1
parsed->status_line = status_line;
}
int prev = pos;
while ((pos = res.find("\r\n", prev)) >= prev)
+19 -5
View File
@@ -17,14 +17,19 @@
class timerfd_manager_t;
#pragma GCC visibility push(default)
struct http_options_t
{
int timeout;
bool want_streaming;
bool keepalive;
bool ssl;
};
struct http_response_t
struct http_context_t;
struct http_message_t
{
std::string error;
@@ -41,10 +46,19 @@ struct http_response_t
// Opened websocket or keepalive HTTP connection
struct http_co_t;
http_co_t* http_init(timerfd_manager_t *tfd);
http_co_t* open_websocket(timerfd_manager_t *tfd, const std::string & host, const std::string & path,
int timeout, std::function<void(const http_response_t *msg)> on_message);
http_context_t* http_context_init(const std::string & ssl_cert, const std::string & ssl_key,
const std::string & ssl_ca, bool verify_peer, std::string & error);
void http_context_destroy(http_context_t *ctx);
http_co_t* http_init(timerfd_manager_t *tfd, http_context_t *ctx = NULL);
void open_websocket(http_co_t *handler, const std::string & host, const std::string & path,
const http_options_t & options, std::function<void(http_message_t *msg)> on_message);
void http_request(http_co_t *handler, const std::string & host, const std::string & request,
const http_options_t & options, std::function<void(const http_response_t *response)> response_callback);
const http_options_t & options, std::function<void(http_message_t *response)> response_callback);
void http_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
+23 -2
View File
@@ -289,6 +289,16 @@ osd_messenger_t::~osd_messenger_t()
rdmacm_evch = NULL;
}
#endif
#ifdef WITH_OPENSSL
for (auto encrypt_ctx: encrypt_ctx_pool)
{
destroy_aes_xts_encrypt(encrypt_ctx);
}
for (auto decrypt_ctx: decrypt_ctx_pool)
{
destroy_aes_xts_decrypt(decrypt_ctx);
}
#endif
}
void osd_messenger_t::parse_config(const json11::Json & config)
@@ -323,6 +333,9 @@ void osd_messenger_t::parse_config(const json11::Json & config)
if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024)
this->rdma_max_msg = 129*1024;
#endif
this->max_aes_xts_pool_size = config["max_aes_xts_pool_size"].uint64_value();
if (!this->max_aes_xts_pool_size)
this->max_aes_xts_pool_size = 256;
if (!osd_num)
this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value();
else
@@ -502,7 +515,7 @@ void osd_messenger_t::try_connect_peer_tcp(osd_num_t peer_osd, const char *peer_
clients[peer_fd]->peer_state = PEER_CONNECTING;
clients[peer_fd]->connect_timeout_id = -1;
clients[peer_fd]->osd_num = peer_osd;
clients[peer_fd]->in_buf = malloc_or_die(receive_buffer_size);
clients[peer_fd]->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
tfd->set_fd_handler(peer_fd, true, [this](int peer_fd, int epoll_events)
{
// Either OUT (connected) or HUP
@@ -739,6 +752,14 @@ void osd_messenger_t::check_peer_config(osd_client_t *cl)
fprintf(stderr, "Connected to OSD %ju using RDMA\n", cl->osd_num);
}
cl->peer_state = PEER_RDMA;
tfd->set_fd_handler(cl->peer_fd, false, [this](int peer_fd, int epoll_events)
{
// Do not miss the disconnection!
if (epoll_events & EPOLLRDHUP)
{
handle_peer_epoll(peer_fd, epoll_events);
}
});
// Add the initial receive request
init_recv_rdma(cl);
}
@@ -773,7 +794,7 @@ void osd_messenger_t::accept_connections(int listen_fd)
cl->peer_port = ntohs(((sockaddr_in*)&addr)->sin_port);
cl->peer_fd = peer_fd;
cl->peer_state = PEER_CONNECTED;
cl->in_buf = malloc_or_die(receive_buffer_size);
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
// Add FD to epoll
tfd->set_fd_handler(peer_fd, false, [this](int peer_fd, int epoll_events)
{
+45 -12
View File
@@ -34,9 +34,6 @@
#define DEFAULT_MIN_ZEROCOPY_SEND_SIZE 32*1024
#define MSGR_SENDP_HDR 1
#define MSGR_SENDP_FREE 2
struct msgr_sendp_t
{
osd_op_t *op;
@@ -48,6 +45,13 @@ struct msgr_rdma_connection_t;
struct msgr_rdma_context_t;
#endif
#ifdef WITH_OPENSSL
struct op_aes_xts_encrypt_t;
struct op_aes_xts_decrypt_t;
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
#endif
struct osd_client_t
{
int refs = 0;
@@ -63,20 +67,23 @@ struct osd_client_t
osd_num_t in_osd_num = 0;
bool is_incoming = false;
void *in_buf = NULL;
uint8_t *in_buf = NULL;
#ifdef WITH_RDMA
msgr_rdma_connection_t *rdma_conn = NULL;
#endif
// Read state
op_aes_xts_decrypt_t *decrypt_ctx = NULL;
int read_ready = 0;
osd_op_t *read_op = NULL;
size_t read_op_size = 0;
size_t read_op_pos = 0;
size_t read_op_inline_decrypt_pos = 0;
iovec read_iov = { 0 };
msghdr read_msg = { 0 };
int read_remaining = 0;
int read_state = 0;
osd_op_buf_list_t recv_list;
std::vector<iovec> recv_list;
size_t recv_list_size = 0;
uint64_t read_op_id = 1;
bool check_sequencing = false;
bool enable_pg_locks = false;
@@ -92,10 +99,15 @@ struct osd_client_t
std::set<pool_pg_num_t> dirty_pgs;
// Write state
op_aes_xts_encrypt_t *encrypt_ctx = NULL;
std::deque<osd_op_t *> write_ops;
osd_op_t *write_op = NULL;
size_t write_op_pos = 0;
msghdr write_msg = { 0 };
int write_state = 0;
std::vector<iovec> send_list, next_send_list;
std::vector<msgr_sendp_t> outbox, next_outbox;
std::vector<iovec> send_list;
size_t send_list_size = 0;
std::deque<osd_op_t*> send_free_ops;
std::vector<osd_op_t*> zc_free_list;
~osd_client_t();
@@ -188,6 +200,7 @@ protected:
bool use_sync_send_recv = false;
int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE;
int iothread_count = 0;
int max_aes_xts_pool_size = 256;
#ifdef WITH_RDMA
bool use_rdma = true;
@@ -209,7 +222,12 @@ protected:
std::vector<int> read_ready_clients;
std::vector<int> write_ready_clients;
// We don't use ringloop->set_immediate here because we may have no ringloop in client :)
std::deque<osd_op_t*> set_immediate_ops;
std::vector<osd_op_t*> set_immediate_ops;
#ifdef WITH_OPENSSL
std::vector<op_aes_xts_encrypt_t*> encrypt_ctx_pool;
std::vector<op_aes_xts_decrypt_t*> decrypt_ctx_pool;
#endif
public:
timerfd_manager_t *tfd = NULL;
@@ -274,9 +292,23 @@ protected:
bool try_send(osd_client_t *cl);
void handle_send(int result, bool prev, bool more, osd_client_t *cl);
bool op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done);
size_t op_copy_to(osd_client_t *cl, uint8_t *dst, size_t dst_len);
void op_get_write_buffers(osd_client_t *cl, std::vector<iovec> & lst);
void handle_read(int result, osd_client_t *cl);
bool handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize);
bool handle_hdr(osd_client_t *cl);
bool allocate_op_buffers(osd_client_t *cl);
bool allocate_reply_buffers(osd_client_t *cl, osd_op_t *op);
size_t op_copy_from(osd_client_t *cl, uint8_t *src, size_t src_len, size_t & done);
bool op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done);
void op_decrypt_start(osd_client_t* cl);
void op_decrypt_inline(osd_client_t* cl);
void op_decrypt_free(osd_client_t* cl);
size_t op_get_read_buffers(osd_client_t *cl, std::vector<iovec> & lst);
void handle_finished_op(osd_client_t *cl);
bool handle_read(int result, osd_client_t *cl);
bool handle_read_buffer(osd_client_t *cl, void *curbuf, int remain);
bool handle_finished_read(osd_client_t *cl);
void handle_op_hdr(osd_client_t *cl);
bool handle_reply_hdr(osd_client_t *cl);
@@ -286,6 +318,7 @@ protected:
#ifdef WITH_RDMA
void try_send_rdma(osd_client_t *cl);
int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len);
bool init_recv_rdma(osd_client_t *cl);
void handle_rdma_events(msgr_rdma_context_t *rdma_context);
msgr_rdma_context_t* choose_rdma_context(osd_client_t *cl);
+328
View File
@@ -0,0 +1,328 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#define _XOPEN_SOURCE
#include <limits.h>
#include <assert.h>
#include "etcd_state_client.h"
#include "messenger.h"
#include "msgr_encrypt.h"
// FIXME Fuck, no streaming...
op_aes_xts_encrypt_t::op_aes_xts_encrypt_t()
{
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
abort();
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
}
op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t()
{
EVP_CIPHER_CTX_free(ctx);
}
void op_aes_xts_encrypt_t::start(const uint8_t *key, uint64_t start_offset, size_t block_size)
{
this->start_offset = start_offset;
this->key = key;
this->block_size = block_size;
this->offset = 0;
}
void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
{
if (max_in > block_size - offset%block_size)
max_in = block_size - offset%block_size;
size_t insize = max_in;
size_t outsize = ((offset+insize)/16 - offset/16) * 16;
if (outsize > max_out)
{
// encrypt is used to send data through temporary buffer(s),
// so we don't care to support fragmenting output into < 16 b parts
insize = (max_out < 16 ? 0 : (max_out & ~15) - offset%16);
outsize = ((offset+insize)/16 - offset/16) * 16;
}
assert(insize <= max_in);
assert(outsize <= max_out);
if (!(offset % block_size))
{
uint8_t iv[16] = { 0 };
*((uint64_t*)iv) = start_offset + offset;
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
}
int actual_out = 0;
if (EVP_EncryptUpdate(ctx, out, &actual_out, in, insize) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == outsize);
done_in += insize;
done_out += outsize;
offset += insize;
}
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx)
{
delete encrypt_ctx;
}
op_aes_xts_decrypt_t::op_aes_xts_decrypt_t()
{
if (!(ctx = EVP_CIPHER_CTX_new()))
{
ERR_print_errors_fp(stderr);
abort();
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
}
op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t()
{
EVP_CIPHER_CTX_free(ctx);
}
void op_aes_xts_decrypt_t::start(const uint8_t *key, uint64_t start_offset, size_t block_size)
{
this->start_offset = start_offset;
this->key = key;
this->block_size = block_size;
this->in_offset = 0;
this->tmp_pos = 16;
}
void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out)
{
if (max_in > block_size - in_offset%block_size)
max_in = block_size - in_offset%block_size;
int actual_out = 0;
// Write previously buffered block to support small output buffers
if (tmp_pos < 16)
{
size_t tmp_size = 16-tmp_pos;
if (tmp_size > max_out)
tmp_size = max_out;
memcpy(out, tmp_buf+tmp_pos, tmp_size);
tmp_pos += tmp_size;
done_out += tmp_size;
out += tmp_size;
max_out -= tmp_size;
if (!max_out)
return;
assert(tmp_pos == 16);
}
if (!(in_offset % block_size))
{
uint8_t iv[16] = { 0 };
*((uint64_t*)iv) = start_offset+in_offset;
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
}
size_t insize = max_in;
size_t outsize = ((in_offset+insize)/16 - in_offset/16) * 16;
if (outsize > max_out)
{
if (max_out < 16)
{
// We can only decrypt a partial block
insize = 16 - in_offset%16;
if (EVP_DecryptUpdate(ctx, tmp_buf, &actual_out, in, insize) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == 16);
in_offset += insize;
in += insize;
max_in -= insize;
tmp_pos = 0;
return;
}
// Otherwise, we can decrypt at least some data into <out> directly
insize = (max_out & ~15) - (in_offset % 16);
outsize = (max_out & ~15);
assert(insize < max_in);
}
if (EVP_DecryptUpdate(ctx, out, &actual_out, in, insize) != 1)
{
ERR_print_errors_fp(stderr);
abort();
}
assert(actual_out == outsize);
in_offset += insize;
done_in += insize;
done_out += actual_out;
}
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx)
{
delete decrypt_ctx;
}
bool osd_messenger_t::op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done)
{
auto op = cl->write_op;
auto & op_pos = cl->write_op_pos;
assert(op->req.hdr.opcode == OSD_OP_WRITE);
if (!from)
{
if (!cl->encrypt_ctx)
{
if (encrypt_ctx_pool.size())
{
cl->encrypt_ctx = encrypt_ctx_pool.back();
encrypt_ctx_pool.pop_back();
}
else
cl->encrypt_ctx = new op_aes_xts_encrypt_t();
}
assert(op->enc->key.size() == 512/8);
cl->encrypt_ctx->start(op->enc->key.data(), op->req.rw.offset, op->enc->bitmap_granularity);
}
for (int i = 0; i < op->iov.count; i++)
{
uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base;
size_t plain_len = op->iov.buf[i].iov_len;
while (from < plain_len)
{
size_t done_in = 0;
size_t done_out = 0;
cl->encrypt_ctx->update(plain+from, plain_len-from, enc_buf+done, enc_len-done, done_in, done_out);
done += done_out;
op_pos += done_in;
from += done_in;
if (!done_in)
return false;
}
from -= plain_len;
}
if (cl->encrypt_ctx)
{
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->encrypt_ctx;
else
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
cl->encrypt_ctx = NULL;
}
return true;
}
bool osd_messenger_t::op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done)
{
op_decrypt_start(cl);
auto op = cl->read_op;
auto & op_pos = cl->read_op_pos;
assert(op->req.hdr.opcode == OSD_OP_READ);
uint64_t offset = from;
for (int i = 0; i < op->iov.count; i++)
{
uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base;
size_t plain_len = op->iov.buf[i].iov_len;
while (from < plain_len)
{
size_t done_in = 0;
size_t done_out = 0;
cl->decrypt_ctx->update(enc_buf+done, enc_len-done, plain+from, plain_len-from, done_in, done_out);
done += done_in;
offset += done_in;
op_pos += done_out;
from += done_out;
if (!done_in)
return false;
}
from -= plain_len;
}
op_decrypt_free(cl);
return true;
}
void osd_messenger_t::op_decrypt_start(osd_client_t* cl)
{
if (!cl->decrypt_ctx)
{
if (decrypt_ctx_pool.size())
{
cl->decrypt_ctx = decrypt_ctx_pool.back();
decrypt_ctx_pool.pop_back();
}
else
cl->decrypt_ctx = new op_aes_xts_decrypt_t();
assert(cl->read_op->enc->key.size() == 512/8);
cl->decrypt_ctx->start(cl->read_op->enc->key.data(), cl->read_op->req.rw.offset, cl->read_op->enc->bitmap_granularity);
}
}
void osd_messenger_t::op_decrypt_inline(osd_client_t* cl)
{
op_decrypt_start(cl);
osd_op_t *op = cl->read_op;
size_t from_in = cl->read_op_inline_decrypt_pos - OSD_PACKET_SIZE - op->reply.rw.bitmap_len;
int i = 0;
while (i < op->iov.count && from_in >= op->iov.buf[i].iov_len)
{
from_in -= op->iov.buf[i].iov_len;
i++;
}
size_t from_out = from_in;
int j = i;
while (i < op->iov.count && j < op->iov.count)
{
uint8_t *in = (uint8_t*)op->iov.buf[i].iov_base + from_in;
size_t in_len = op->iov.buf[i].iov_len - from_in;
uint8_t *out = (uint8_t*)op->iov.buf[j].iov_base + from_out;
size_t out_len = op->iov.buf[j].iov_len - from_out;
size_t done_in = 0;
size_t done_out = 0;
cl->decrypt_ctx->update(in, in_len, out, out_len, done_in, done_out);
if (done_in >= in_len)
{
i++;
from_in = 0;
}
else
from_in += done_in;
if (done_out >= out_len)
{
j++;
from_out = 0;
}
else
from_out += done_out;
}
assert(j >= op->iov.count);
op_decrypt_free(cl);
}
void osd_messenger_t::op_decrypt_free(osd_client_t* cl)
{
if (cl->decrypt_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
delete cl->decrypt_ctx;
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
}
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) Vitaliy Filippov, 2026+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdint.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
struct op_aes_xts_encrypt_t
{
EVP_CIPHER_CTX *ctx = NULL;
uint64_t start_offset = 0;
const uint8_t *key = NULL;
size_t offset = 0;
size_t block_size = 0;
op_aes_xts_encrypt_t();
~op_aes_xts_encrypt_t();
void start(const uint8_t *key, uint64_t start_offset, size_t block_size);
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
};
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
struct op_aes_xts_decrypt_t
{
EVP_CIPHER_CTX *ctx = NULL;
uint64_t start_offset = 0;
const uint8_t *key = NULL;
uint8_t tmp_buf[16];
size_t tmp_pos = 16;
size_t in_offset = 0;
size_t block_size = 0;
op_aes_xts_decrypt_t();
~op_aes_xts_decrypt_t();
void start(const uint8_t *key, uint64_t start_offset, size_t block_size);
void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out);
};
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
+4
View File
@@ -23,6 +23,10 @@ osd_op_t::~osd_op_t()
// So we don't reuse it, but free it every time
free(buf);
}
if (enc_buf)
{
free(enc_buf);
}
}
bool osd_op_t::is_recovery_related()
+6
View File
@@ -3,6 +3,8 @@
#pragma once
#include <memory>
#include <sys/uio.h>
#include <stdint.h>
#include <stdio.h>
@@ -152,6 +154,8 @@ struct blockstore_op_t;
struct osd_primary_op_data_t;
struct inode_enc_t;
struct __attribute__((visibility("default"))) osd_op_t
{
timespec tv_begin = { 0 }, tv_end = { 0 };
@@ -167,6 +171,8 @@ struct __attribute__((visibility("default"))) osd_op_t
unsigned bmp_data = 0;
void *bitmap_buf = NULL;
void *rmw_buf = NULL;
std::shared_ptr<inode_enc_t> enc;
uint8_t *enc_buf = NULL;
osd_primary_op_data_t* op_data = NULL;
std::function<void(osd_op_t*)> callback;
+24 -56
View File
@@ -555,23 +555,28 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge)
cl->rdma_conn->cur_send++;
}
static int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len)
int osd_messenger_t::try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len)
{
auto rc = cl->rdma_conn;
int total_dst_len = dst_len;
while (dst_len > 0 && rc->send_pos < cl->send_list.size())
while (dst_len > 0 && cl->write_ops.size())
{
iovec & iov = cl->send_list[rc->send_pos];
uint32_t len = (uint32_t)(iov.iov_len-rc->send_buf_pos < dst_len
? iov.iov_len-rc->send_buf_pos : dst_len);
memcpy(dst, (uint8_t*)iov.iov_base+rc->send_buf_pos, len);
dst += len;
dst_len -= len;
rc->send_buf_pos += len;
if (rc->send_buf_pos >= iov.iov_len)
if (!cl->write_op)
{
rc->send_pos++;
rc->send_buf_pos = 0;
cl->write_op = cl->write_ops.front();
cl->write_ops.pop_front();
}
osd_op_t *op = cl->write_op;
size_t copied = op_copy_to(cl, dst, dst_len);
if (!copied)
{
break;
}
dst += copied;
dst_len -= copied;
if (!cl->write_op && op->op_type == OSD_OP_IN)
{
// this is a reply, free the op after sending it
cl->send_free_ops.push_back(op);
}
}
return total_dst_len-dst_len;
@@ -617,7 +622,7 @@ void osd_messenger_t::try_send_rdma(osd_client_t *cl)
.lkey = rc->send_out.mr->lkey,
};
try_send_rdma_wr(cl, &sge, 1);
rc->send_sizes.push_back(copied);
cl->send_free_ops.push_back(NULL); // end marker
}
}
}
@@ -696,10 +701,6 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
continue;
}
osd_client_t *cl = cl_it->second;
if (cl->peer_state == PEER_STOPPED)
{
continue;
}
auto rc = cl->rdma_conn;
if (wc[i].status != IBV_WC_SUCCESS)
{
@@ -731,51 +732,18 @@ void osd_messenger_t::handle_rdma_events(msgr_rdma_context_t *rdma_context)
else
{
rc->cur_send--;
uint64_t sent_size = rc->send_sizes.at(0);
rc->send_sizes.erase(rc->send_sizes.begin(), rc->send_sizes.begin()+1);
uint64_t sent_size = wc[i].byte_len;
rc->send_done_pos += sent_size;
rc->send_out_full = false;
if (rc->send_done_pos == rc->send_out_size)
rc->send_done_pos = 0;
assert(rc->send_done_pos < rc->send_out_size);
int send_pos = 0, send_buf_pos = 0;
while (sent_size > 0)
while (cl->send_free_ops.front())
{
if (sent_size >= cl->send_list.at(send_pos).iov_len)
{
sent_size -= cl->send_list[send_pos].iov_len;
send_pos++;
}
else
{
send_buf_pos = sent_size;
sent_size = 0;
}
}
assert(rc->send_pos >= send_pos);
if (rc->send_pos == send_pos)
{
rc->send_buf_pos -= send_buf_pos;
}
rc->send_pos -= send_pos;
for (int i = 0; i < send_pos; i++)
{
if (cl->outbox[i].flags & MSGR_SENDP_FREE)
{
// Reply fully sent
delete cl->outbox[i].op;
}
}
if (send_pos > 0)
{
cl->send_list.erase(cl->send_list.begin(), cl->send_list.begin()+send_pos);
cl->outbox.erase(cl->outbox.begin(), cl->outbox.begin()+send_pos);
}
if (send_buf_pos > 0)
{
cl->send_list[0].iov_base = (uint8_t*)cl->send_list[0].iov_base + send_buf_pos;
cl->send_list[0].iov_len -= send_buf_pos;
delete cl->send_free_ops.front();
cl->send_free_ops.pop_front();
}
cl->send_free_ops.pop_front();
try_send_rdma(cl);
}
}
+3 -2
View File
@@ -10,6 +10,8 @@
#include <vector>
#include "addr_util.h"
struct osd_op_t;
struct msgr_rdma_address_t
{
ibv_gid gid;
@@ -72,9 +74,8 @@ struct msgr_rdma_connection_t
int cur_send = 0, cur_recv = 0;
int send_pos = 0, send_buf_pos = 0;
int next_recv_buf = 0;
std::vector<void*> recv_buffers;
std::vector<uint8_t*> recv_buffers;
msgr_rdma_buf_t recv_buf;
std::vector<uint64_t> send_sizes;
msgr_rdma_buf_t send_out;
int send_out_pos = 0, send_done_pos = 0, send_out_size = 0;
bool send_out_full = false;
+1 -1
View File
@@ -515,7 +515,7 @@ void osd_messenger_t::rdmacm_established(rdma_cm_event *ev)
cl->peer_state = PEER_RDMA;
cl->connect_timeout_id = -1;
cl->osd_num = peer_osd;
cl->in_buf = malloc_or_die(receive_buffer_size);
cl->in_buf = (uint8_t*)malloc_or_die(receive_buffer_size);
cl->rdma_conn = rc;
clients[conn->peer_fd] = cl;
if (conn->timeout_id >= 0)
+379 -224
View File
@@ -1,6 +1,8 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#define _XOPEN_SOURCE
#include <limits.h>
#include "messenger.h"
void osd_messenger_t::read_requests()
@@ -9,13 +11,16 @@ void osd_messenger_t::read_requests()
{
int peer_fd = read_ready_clients[i];
auto cl_it = clients.find(peer_fd);
if (cl_it == clients.end() || !cl_it->second || cl_it->second->read_msg.msg_iovlen ||
cl_it->second->peer_state == PEER_RDMA || cl_it->second->peer_state == PEER_RDMA_CONNECTING)
if (cl_it == clients.end() || !cl_it->second || cl_it->second->read_msg.msg_iovlen)
{
continue;
}
auto cl = cl_it->second;
if (cl->read_remaining < receive_buffer_size)
if (cl->read_op && cl->read_op_size-(cl->read_op_pos-OSD_PACKET_SIZE) >= receive_buffer_size)
{
op_get_read_buffers(cl, cl->recv_list);
}
if (!cl->recv_list.size())
{
cl->read_iov.iov_base = cl->in_buf;
cl->read_iov.iov_len = receive_buffer_size;
@@ -25,10 +30,11 @@ void osd_messenger_t::read_requests()
else
{
cl->read_iov.iov_base = 0;
cl->read_iov.iov_len = cl->read_remaining;
cl->read_msg.msg_iov = cl->recv_list.get_iovec();
cl->read_msg.msg_iovlen = cl->recv_list.get_size();
cl->read_iov.iov_len = 0;
cl->read_msg.msg_iov = cl->recv_list.data();
cl->read_msg.msg_iovlen = cl->recv_list.size();
}
assert(!cl->read_op || cl->read_op_pos < OSD_PACKET_SIZE || cl->read_op_size >= (cl->read_op_pos-OSD_PACKET_SIZE));
cl->refs++;
if (ringloop && !use_sync_send_recv)
{
@@ -50,7 +56,7 @@ void osd_messenger_t::read_requests()
}
ring_data_t* data = ((ring_data_t*)sqe->user_data);
data->callback = [this, cl](ring_data_t *data) { handle_read(data->res, cl); };
io_uring_prep_recvmsg(sqe, peer_fd, &cl->read_msg, 0);
io_uring_prep_recvmsg(sqe, peer_fd, &cl->read_msg, cl->recv_list.size() ? MSG_WAITALL : 0);
if (iothread)
{
iothread->add_sqe(sqe_local);
@@ -70,23 +76,17 @@ void osd_messenger_t::read_requests()
read_ready_clients.clear();
}
bool osd_messenger_t::handle_read(int result, osd_client_t *cl)
void osd_messenger_t::handle_read(int result, osd_client_t *cl)
{
bool ret = false;
int peer_fd = cl->peer_fd;
cl->read_msg.msg_iovlen = 0;
cl->refs--;
if (cl->peer_state == PEER_RDMA)
{
return true;
}
if (cl->peer_state == PEER_STOPPED)
{
if (cl->refs <= 0)
{
delete cl;
}
return false;
return;
}
if (result <= 0 && result != -EAGAIN && result != -EINTR)
{
@@ -96,9 +96,51 @@ bool osd_messenger_t::handle_read(int result, osd_client_t *cl)
fprintf(stderr, "Client %d socket read error: %d (%s). Disconnecting client\n", cl->peer_fd, -result, strerror(-result));
}
stop_client(cl->peer_fd);
return false;
return;
}
if (result == -EAGAIN || result == -EINTR || result < cl->read_iov.iov_len)
bool full_read = false;
if (result > 0)
{
if (cl->read_iov.iov_base == cl->in_buf)
{
full_read = result >= cl->read_iov.iov_len;
if (!handle_read_buffer(cl, cl->in_buf, result))
{
clear_immediate_ops(peer_fd);
handle_immediate_ops();
return;
}
}
else
{
// Reset OSD ping state
cl->ping_time_remaining = 0;
cl->idle_time_remaining = osd_idle_timeout;
// Long data
size_t i = 0;
while (i < cl->recv_list.size() && result >= cl->recv_list[i].iov_len)
{
result -= cl->recv_list[i].iov_len;
i++;
}
if (i < cl->recv_list.size())
{
cl->recv_list[i].iov_base += result;
cl->recv_list[i].iov_len -= result;
}
else
{
full_read = true;
}
cl->recv_list.erase(cl->recv_list.begin(), cl->recv_list.begin()+i);
if (!cl->recv_list.size())
{
handle_finished_op(cl);
}
}
}
cl->read_msg.msg_iovlen = 0;
if (result == -EAGAIN || result == -EINTR || !full_read)
{
cl->read_ready--;
if (cl->read_ready > 0)
@@ -108,39 +150,7 @@ bool osd_messenger_t::handle_read(int result, osd_client_t *cl)
{
read_ready_clients.push_back(cl->peer_fd);
}
if (result > 0)
{
if (cl->read_iov.iov_base == cl->in_buf)
{
if (!handle_read_buffer(cl, cl->in_buf, result))
{
clear_immediate_ops(peer_fd);
handle_immediate_ops();
return false;
}
}
else
{
// Long data
cl->read_remaining -= result;
cl->recv_list.eat(result);
if (cl->recv_list.done >= cl->recv_list.count)
{
if (!handle_finished_read(cl))
{
clear_immediate_ops(peer_fd);
handle_immediate_ops();
return false;
}
}
}
if (result >= cl->read_iov.iov_len)
{
ret = true;
}
}
handle_immediate_ops();
return ret;
}
void osd_messenger_t::clear_immediate_ops(int peer_fd)
@@ -165,10 +175,8 @@ void osd_messenger_t::clear_immediate_ops(int peer_fd)
void osd_messenger_t::handle_immediate_ops()
{
while (set_immediate_ops.size())
for (auto op: set_immediate_ops)
{
auto op = set_immediate_ops.front();
set_immediate_ops.pop_front();
if (op->op_type == OSD_OP_IN)
{
exec_op(op);
@@ -179,115 +187,98 @@ void osd_messenger_t::handle_immediate_ops()
std::function<void(osd_op_t*)>(op->callback)(op);
}
}
set_immediate_ops.clear();
}
bool osd_messenger_t::handle_read_buffer(osd_client_t *cl, void *curbuf, int remain)
bool osd_messenger_t::handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size_t bufsize)
{
// Reset OSD ping state
cl->ping_time_remaining = 0;
cl->idle_time_remaining = osd_idle_timeout;
// Compose operation(s) from the buffer
while (remain > 0)
size_t done = 0;
while (done < bufsize)
{
if (!cl->read_op)
{
cl->read_op = new osd_op_t;
cl->read_op->peer_fd = cl->peer_fd;
cl->read_op->op_type = OSD_OP_IN;
cl->recv_list.push_back(cl->read_op->req.buf, OSD_PACKET_SIZE);
cl->read_remaining = OSD_PACKET_SIZE;
cl->read_state = CL_READ_HDR;
cl->read_op_pos = 0;
cl->read_op_size = 0;
cl->read_op_inline_decrypt_pos = (size_t)-1;
}
while (cl->recv_list.done < cl->recv_list.count && remain > 0)
if (cl->read_op_pos < OSD_PACKET_SIZE)
{
iovec* cur = cl->recv_list.get_iovec();
if (cur->iov_len > remain)
{
memcpy(cur->iov_base, curbuf, remain);
cl->read_remaining -= remain;
cur->iov_len -= remain;
cur->iov_base = (uint8_t*)cur->iov_base + remain;
remain = 0;
}
else
{
memcpy(cur->iov_base, curbuf, cur->iov_len);
curbuf = (uint8_t*)curbuf + cur->iov_len;
cl->read_remaining -= cur->iov_len;
remain -= cur->iov_len;
cur->iov_len = 0;
cl->recv_list.done++;
}
}
if (cl->recv_list.done >= cl->recv_list.count)
{
if (!handle_finished_read(cl))
int len = OSD_PACKET_SIZE - cl->read_op_pos;
if (len > bufsize-done)
len = bufsize-done;
memcpy(cl->read_op->req.buf + cl->read_op_pos, curbuf+done, len);
done += len;
cl->read_op_pos += len;
if (cl->read_op_pos < OSD_PACKET_SIZE)
return true;
if (!handle_hdr(cl))
{
stop_client(cl->peer_fd);
return false;
}
}
op_copy_from(cl, curbuf, bufsize, done);
}
return true;
}
bool osd_messenger_t::handle_finished_read(osd_client_t *cl)
bool osd_messenger_t::handle_hdr(osd_client_t *cl)
{
// Reset OSD ping state
cl->ping_time_remaining = 0;
cl->idle_time_remaining = osd_idle_timeout;
cl->recv_list.reset();
if (cl->read_state == CL_READ_HDR)
if (cl->read_op->req.hdr.magic == SECONDARY_OSD_REPLY_MAGIC)
{
if (cl->read_op->req.hdr.magic == SECONDARY_OSD_REPLY_MAGIC)
return handle_reply_hdr(cl);
else if (cl->read_op->req.hdr.magic == SECONDARY_OSD_OP_MAGIC)
auto req_it = cl->sent_ops.find(cl->read_op->req.hdr.id);
if (req_it == cl->sent_ops.end())
{
if (cl->check_sequencing)
{
if (cl->read_op->req.hdr.id != cl->read_op_id)
{
fprintf(stderr, "Warning: operation sequencing is broken on client %d: expected num %ju, got %ju, stopping client\n", cl->peer_fd, cl->read_op_id, cl->read_op->req.hdr.id);
stop_client(cl->peer_fd);
return false;
}
cl->read_op_id++;
}
handle_op_hdr(cl);
// Command out of sync. Drop connection
fprintf(stderr, "Client %d command out of sync: id %ju\n", cl->peer_fd, cl->read_op->req.hdr.id);
return false;
}
else
osd_op_t *op = req_it->second;
memcpy(op->reply.buf, cl->read_op->req.buf, OSD_PACKET_SIZE);
if (!allocate_reply_buffers(cl, op))
{
return false;
}
cl->sent_ops.erase(req_it);
delete cl->read_op;
cl->read_op = op;
}
else if (cl->read_op->req.hdr.magic == SECONDARY_OSD_OP_MAGIC)
{
if (cl->check_sequencing)
{
if (cl->read_op->req.hdr.id != cl->read_op_id)
{
fprintf(stderr, "Warning: operation sequencing is broken on client %d: expected num %ju, got %ju, stopping client\n", cl->peer_fd, cl->read_op_id, cl->read_op->req.hdr.id);
return false;
}
cl->read_op_id++;
}
if (!allocate_op_buffers(cl))
{
fprintf(stderr, "Received garbage: magic=%jx id=%ju opcode=%jx from %d\n", cl->read_op->req.hdr.magic, cl->read_op->req.hdr.id, cl->read_op->req.hdr.opcode, cl->peer_fd);
stop_client(cl->peer_fd);
return false;
}
}
else if (cl->read_state == CL_READ_DATA)
{
// Operation is ready
cl->received_ops.push_back(cl->read_op);
set_immediate_ops.push_back(cl->read_op);
cl->read_op = NULL;
cl->read_state = 0;
}
else if (cl->read_state == CL_READ_REPLY_DATA)
{
// Reply is ready
handle_reply_ready(cl->read_op);
cl->read_op = NULL;
cl->read_state = 0;
}
else
{
assert(0);
fprintf(stderr, "Received garbage: magic=%jx id=%ju opcode=%jx from %d\n", cl->read_op->req.hdr.magic, cl->read_op->req.hdr.id, cl->read_op->req.hdr.opcode, cl->peer_fd);
return false;
}
return true;
}
void osd_messenger_t::handle_op_hdr(osd_client_t *cl)
bool osd_messenger_t::allocate_op_buffers(osd_client_t *cl)
{
osd_op_t *cur_op = cl->read_op;
if (cur_op->req.hdr.opcode == OSD_OP_SEC_READ)
{
cl->read_remaining = 0;
}
else if (cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
cl->read_op_size = 0;
if (cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
{
if (cur_op->req.sec_rw.attr_len > 0)
@@ -296,14 +287,12 @@ void osd_messenger_t::handle_op_hdr(osd_client_t *cl)
cur_op->bitmap = cur_op->rmw_buf = malloc_or_die(cur_op->req.sec_rw.attr_len);
else
cur_op->bitmap = &cur_op->bmp_data;
cl->recv_list.push_back(cur_op->bitmap, cur_op->req.sec_rw.attr_len);
}
if (cur_op->req.sec_rw.len > 0)
{
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_rw.len);
cl->recv_list.push_back(cur_op->buf, cur_op->req.sec_rw.len);
}
cl->read_remaining = cur_op->req.sec_rw.len + cur_op->req.sec_rw.attr_len;
cl->read_op_size = cur_op->req.sec_rw.len + cur_op->req.sec_rw.attr_len;
}
else if (cur_op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK)
@@ -311,27 +300,24 @@ void osd_messenger_t::handle_op_hdr(osd_client_t *cl)
if (cur_op->req.sec_stab.len > 0)
{
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_stab.len);
cl->recv_list.push_back(cur_op->buf, cur_op->req.sec_stab.len);
}
cl->read_remaining = cur_op->req.sec_stab.len;
cl->read_op_size = cur_op->req.sec_stab.len;
}
else if (cur_op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
{
if (cur_op->req.sec_read_bmp.len > 0)
{
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.sec_read_bmp.len);
cl->recv_list.push_back(cur_op->buf, cur_op->req.sec_read_bmp.len);
}
cl->read_remaining = cur_op->req.sec_read_bmp.len;
cl->read_op_size = cur_op->req.sec_read_bmp.len;
}
else if (cur_op->req.hdr.opcode == OSD_OP_WRITE)
{
if (cur_op->req.rw.len > 0)
{
cur_op->buf = memalign_or_die(MEM_ALIGNMENT, cur_op->req.rw.len);
cl->recv_list.push_back(cur_op->buf, cur_op->req.rw.len);
}
cl->read_remaining = cur_op->req.rw.len;
cl->read_op_size = cur_op->req.rw.len;
}
else if (cur_op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)
{
@@ -339,44 +325,15 @@ void osd_messenger_t::handle_op_hdr(osd_client_t *cl)
{
cur_op->buf = malloc_or_die(cur_op->req.show_conf.json_len+1);
((uint8_t*)cur_op->buf)[cur_op->req.show_conf.json_len] = 0;
cl->recv_list.push_back(cur_op->buf, cur_op->req.show_conf.json_len);
}
cl->read_remaining = cur_op->req.show_conf.json_len;
}
/*else if (cur_op->req.hdr.opcode == OSD_OP_READ ||
cur_op->req.hdr.opcode == OSD_OP_SCRUB ||
cur_op->req.hdr.opcode == OSD_OP_DESCRIBE)
{
cl->read_remaining = 0;
}*/
if (cl->read_remaining > 0)
{
// Read data
cl->read_state = CL_READ_DATA;
}
else
{
// Operation is ready
cl->received_ops.push_back(cur_op);
set_immediate_ops.push_back(cur_op);
cl->read_op = NULL;
cl->read_state = 0;
cl->read_op_size = cur_op->req.show_conf.json_len;
}
return true;
}
bool osd_messenger_t::handle_reply_hdr(osd_client_t *cl)
bool osd_messenger_t::allocate_reply_buffers(osd_client_t *cl, osd_op_t *op)
{
auto req_it = cl->sent_ops.find(cl->read_op->req.hdr.id);
if (req_it == cl->sent_ops.end())
{
// Command out of sync. Drop connection
fprintf(stderr, "Client %d command out of sync: id %ju\n", cl->peer_fd, cl->read_op->req.hdr.id);
stop_client(cl->peer_fd);
return false;
}
osd_op_t *op = req_it->second;
memcpy(op->reply.buf, cl->read_op->req.buf, OSD_PACKET_SIZE);
cl->sent_ops.erase(req_it);
cl->read_op_size = 0;
if (op->reply.hdr.opcode == OSD_OP_SEC_READ || op->reply.hdr.opcode == OSD_OP_READ)
{
// Read data. In this case we assume that the buffer is preallocated by the caller (!)
@@ -387,97 +344,295 @@ bool osd_messenger_t::handle_reply_hdr(osd_client_t *cl)
// Check reply length to not overflow the buffer
fprintf(stderr, "Client %d read reply of different length: expected %u+%u, got %jd+%u\n",
cl->peer_fd, expected_size, op->bitmap_len, op->reply.hdr.retval, bmp_len);
cl->sent_ops[op->req.hdr.id] = op;
stop_client(cl->peer_fd);
return false;
}
if (bmp_len > 0)
{
assert(op->bitmap);
cl->recv_list.push_back(op->bitmap, bmp_len);
cl->read_remaining += bmp_len;
cl->read_op_size += bmp_len;
}
if (op->reply.hdr.retval > 0)
{
assert(op->iov.count > 0);
cl->recv_list.append(op->iov);
cl->read_remaining += op->reply.hdr.retval;
cl->read_op_size += op->reply.hdr.retval;
}
if (cl->read_remaining == 0)
{
goto reuse;
}
delete cl->read_op;
cl->read_op = op;
cl->read_state = CL_READ_REPLY_DATA;
}
else if (op->reply.hdr.opcode == OSD_OP_SEC_LIST && op->reply.hdr.retval > 0)
{
assert(!op->iov.count);
delete cl->read_op;
cl->read_op = op;
cl->read_state = CL_READ_REPLY_DATA;
cl->read_remaining = sizeof(obj_ver_id) * op->reply.hdr.retval;
op->buf = memalign_or_die(MEM_ALIGNMENT, cl->read_remaining);
cl->recv_list.push_back(op->buf, cl->read_remaining);
cl->read_op_size = sizeof(obj_ver_id) * op->reply.hdr.retval;
op->buf = memalign_or_die(MEM_ALIGNMENT, cl->read_op_size);
}
else if (op->reply.hdr.opcode == OSD_OP_SEC_READ_BMP && op->reply.hdr.retval > 0)
{
assert(!op->iov.count);
delete cl->read_op;
cl->read_op = op;
cl->read_state = CL_READ_REPLY_DATA;
cl->read_remaining = op->reply.hdr.retval;
cl->read_op_size = op->reply.hdr.retval;
free(op->buf);
op->buf = memalign_or_die(MEM_ALIGNMENT, cl->read_remaining);
cl->recv_list.push_back(op->buf, cl->read_remaining);
op->buf = memalign_or_die(MEM_ALIGNMENT, cl->read_op_size);
}
else if (op->reply.hdr.opcode == OSD_OP_SHOW_CONFIG && op->reply.hdr.retval > 0)
{
delete cl->read_op;
cl->read_op = op;
cl->read_state = CL_READ_REPLY_DATA;
cl->read_remaining = op->reply.hdr.retval;
cl->read_op_size = op->reply.hdr.retval;
free(op->buf);
op->buf = malloc_or_die(op->reply.hdr.retval);
cl->recv_list.push_back(op->buf, op->reply.hdr.retval);
}
else if (op->reply.hdr.opcode == OSD_OP_DESCRIBE && op->reply.describe.result_bytes > 0)
{
delete cl->read_op;
cl->read_op = op;
cl->read_state = CL_READ_REPLY_DATA;
cl->read_remaining = op->reply.describe.result_bytes;
cl->read_op_size = op->reply.describe.result_bytes;
free(op->buf);
op->buf = malloc_or_die(op->reply.describe.result_bytes);
cl->recv_list.push_back(op->buf, op->reply.describe.result_bytes);
}
else
{
reuse:
// It's fine to reuse cl->read_op for the next reply
handle_reply_ready(op);
cl->recv_list.push_back(cl->read_op->req.buf, OSD_PACKET_SIZE);
cl->read_remaining = OSD_PACKET_SIZE;
cl->read_state = CL_READ_HDR;
}
return true;
}
void osd_messenger_t::handle_reply_ready(osd_op_t *op)
size_t osd_messenger_t::op_copy_from(osd_client_t *cl, uint8_t *src, size_t src_len, size_t & done)
{
// Measure subop latency
timespec tv_end;
clock_gettime(CLOCK_REALTIME, &tv_end);
stats.subop_stat_count[op->req.hdr.opcode]++;
if (!stats.subop_stat_count[op->req.hdr.opcode])
osd_op_t *op = cl->read_op;
size_t from = cl->read_op_pos-OSD_PACKET_SIZE;
auto op_read_buf = [&](uint8_t *dst, size_t dst_len)
{
stats.subop_stat_count[op->req.hdr.opcode]++;
stats.subop_stat_sum[op->req.hdr.opcode] = 0;
if (from < dst_len)
{
size_t n = dst_len-from;
if (n > src_len-done)
n = src_len-done;
memcpy(dst+from, src+done, n);
done += n;
cl->read_op_pos += n;
from += n;
if (from < dst_len)
return false;
from = 0;
}
else
from -= dst_len;
return true;
};
if (op->op_type == OSD_OP_IN)
{
if (op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
{
if (!op_read_buf((uint8_t*)op->bitmap, op->req.sec_rw.attr_len))
return done;
if (!op_read_buf((uint8_t*)op->buf, op->req.sec_rw.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.sec_stab.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.sec_read_bmp.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_WRITE)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.rw.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.show_conf.json_len))
return done;
}
}
stats.subop_stat_sum[op->req.hdr.opcode] += (
(tv_end.tv_sec - op->tv_begin.tv_sec)*1000000 +
(tv_end.tv_nsec - op->tv_begin.tv_nsec)/1000
);
set_immediate_ops.push_back(op);
else
{
if (op->reply.hdr.opcode == OSD_OP_SEC_READ)
{
if (op->reply.sec_rw.attr_len > 0)
{
if (!op_read_buf((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len))
return done;
}
if (op->reply.hdr.retval > 0)
{
for (int i = 0; i < op->iov.count; i++)
if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len))
return done;
}
}
else if (op->reply.hdr.opcode == OSD_OP_READ)
{
if (op->reply.rw.bitmap_len > 0)
{
if (!op_read_buf((uint8_t*)op->bitmap, op->reply.rw.bitmap_len))
return done;
}
if (op->reply.hdr.retval > 0)
{
if (op->enc)
{
if (!op_decrypted_copy_data_from(cl, src, src_len, from, done))
return done;
}
else
{
for (int i = 0; i < op->iov.count; i++)
if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len))
return done;
}
}
}
else if (op->reply.hdr.opcode == OSD_OP_SEC_LIST && op->reply.hdr.retval > 0)
{
if (!op_read_buf((uint8_t*)op->buf, sizeof(obj_ver_id) * op->reply.hdr.retval))
return done;
}
else if ((op->reply.hdr.opcode == OSD_OP_SEC_READ_BMP ||
op->reply.hdr.opcode == OSD_OP_SHOW_CONFIG) && op->reply.hdr.retval > 0)
{
if (!op_read_buf((uint8_t*)op->buf, op->reply.hdr.retval))
return done;
}
else if (op->reply.hdr.opcode == OSD_OP_DESCRIBE && op->reply.describe.result_bytes > 0)
{
if (!op_read_buf((uint8_t*)op->buf, op->reply.describe.result_bytes))
return done;
}
}
handle_finished_op(cl);
return done;
}
size_t osd_messenger_t::op_get_read_buffers(osd_client_t *cl, std::vector<iovec> & lst)
{
osd_op_t *op = cl->read_op;
size_t from = cl->read_op_pos-OSD_PACKET_SIZE;
size_t done = 0;
auto op_read_buf = [&](uint8_t *dst, size_t dst_len)
{
if (lst.size() >= IOV_MAX)
return false;
if (from < dst_len)
{
lst.push_back((iovec){ .iov_base = dst+from, .iov_len = dst_len-from });
cl->read_op_pos += dst_len-from;
done += dst_len-from;
from = 0;
}
else
from -= dst_len;
return true;
};
if (op->op_type == OSD_OP_IN)
{
if (op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE)
{
if (!op_read_buf((uint8_t*)op->bitmap, op->req.sec_rw.attr_len))
return done;
if (!op_read_buf((uint8_t*)op->buf, op->req.sec_rw.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.sec_stab.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.sec_read_bmp.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_WRITE)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.rw.len))
return done;
}
else if (op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)
{
if (!op_read_buf((uint8_t*)op->buf, op->req.show_conf.json_len))
return done;
}
}
else
{
if (op->reply.hdr.opcode == OSD_OP_SEC_READ)
{
if (op->reply.sec_rw.attr_len > 0)
{
if (!op_read_buf((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len))
return done;
}
if (op->reply.hdr.retval > 0)
{
for (int i = 0; i < op->iov.count; i++)
if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len))
return done;
}
}
else if (op->reply.hdr.opcode == OSD_OP_READ)
{
if (op->reply.rw.bitmap_len > 0)
{
if (!op_read_buf((uint8_t*)op->bitmap, op->reply.rw.bitmap_len))
return done;
}
if (op->reply.hdr.retval > 0)
{
if (op->enc)
cl->read_op_inline_decrypt_pos = cl->read_op_pos;
for (int i = 0; i < op->iov.count; i++)
if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len))
return done;
}
}
else if (op->reply.hdr.opcode == OSD_OP_SEC_LIST && op->reply.hdr.retval > 0)
{
if (!op_read_buf((uint8_t*)op->buf, sizeof(obj_ver_id) * op->reply.hdr.retval))
return done;
}
else if ((op->reply.hdr.opcode == OSD_OP_SEC_READ_BMP ||
op->reply.hdr.opcode == OSD_OP_SHOW_CONFIG) && op->reply.hdr.retval > 0)
{
if (!op_read_buf((uint8_t*)op->buf, op->reply.hdr.retval))
return done;
}
else if (op->reply.hdr.opcode == OSD_OP_DESCRIBE && op->reply.describe.result_bytes > 0)
{
if (!op_read_buf((uint8_t*)op->buf, op->reply.describe.result_bytes))
return done;
}
}
return done;
}
void osd_messenger_t::handle_finished_op(osd_client_t *cl)
{
osd_op_t *op = cl->read_op;
if (op->op_type == OSD_OP_IN)
{
// Operation is ready
cl->received_ops.push_back(op);
}
else
{
// Inline decryption
if (cl->read_op_inline_decrypt_pos != (size_t)-1)
{
op_decrypt_inline(cl);
cl->read_op_inline_decrypt_pos = (size_t)-1;
}
// Measure subop (outbound op) latency
timespec tv_end;
clock_gettime(CLOCK_REALTIME, &tv_end);
stats.subop_stat_count[op->req.hdr.opcode]++;
if (!stats.subop_stat_count[op->req.hdr.opcode])
{
stats.subop_stat_count[op->req.hdr.opcode]++;
stats.subop_stat_sum[op->req.hdr.opcode] = 0;
}
stats.subop_stat_sum[op->req.hdr.opcode] += (
(tv_end.tv_sec - op->tv_begin.tv_sec)*1000000 +
(tv_end.tv_nsec - op->tv_begin.tv_nsec)/1000
);
}
set_immediate_ops.push_back(op);
cl->read_op = NULL;
}
+208 -123
View File
@@ -15,6 +15,7 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
{
clock_gettime(CLOCK_REALTIME, &cur_op->tv_begin);
cur_op->req.hdr.id = ++cl->send_op_id;
cl->sent_ops[cur_op->req.hdr.id] = cur_op;
}
else
{
@@ -35,77 +36,9 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
delete cur_op;
return;
}
}
auto & to_send_list = cl->write_msg.msg_iovlen ? cl->next_send_list : cl->send_list;
auto & to_outbox = cl->write_msg.msg_iovlen ? cl->next_outbox : cl->outbox;
if (cur_op->op_type == OSD_OP_IN)
{
measure_exec(cur_op);
to_send_list.push_back((iovec){ .iov_base = cur_op->reply.buf, .iov_len = OSD_PACKET_SIZE });
}
else
{
to_send_list.push_back((iovec){ .iov_base = cur_op->req.buf, .iov_len = OSD_PACKET_SIZE });
cl->sent_ops[cur_op->req.hdr.id] = cur_op;
}
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = MSGR_SENDP_HDR });
// Bitmap
if (cur_op->op_type == OSD_OP_IN &&
cur_op->req.hdr.opcode == OSD_OP_SEC_READ &&
cur_op->reply.sec_rw.attr_len > 0)
{
to_send_list.push_back((iovec){
.iov_base = cur_op->bitmap,
.iov_len = cur_op->reply.sec_rw.attr_len,
});
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
}
else if (cur_op->op_type == OSD_OP_OUT &&
(cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE || cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE) &&
cur_op->req.sec_rw.attr_len > 0)
{
to_send_list.push_back((iovec){
.iov_base = cur_op->bitmap,
.iov_len = cur_op->req.sec_rw.attr_len,
});
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
}
// Operation data
if ((cur_op->op_type == OSD_OP_IN
? (cur_op->req.hdr.opcode == OSD_OP_READ ||
cur_op->req.hdr.opcode == OSD_OP_SEC_READ ||
cur_op->req.hdr.opcode == OSD_OP_SEC_LIST ||
cur_op->req.hdr.opcode == OSD_OP_SHOW_CONFIG ||
cur_op->req.hdr.opcode == OSD_OP_DESCRIBE)
: (cur_op->req.hdr.opcode == OSD_OP_WRITE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
cur_op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK ||
cur_op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)) && cur_op->iov.count > 0)
{
for (int i = 0; i < cur_op->iov.count; i++)
{
if (cur_op->iov.buf[i].iov_len > 0)
{
assert(cur_op->iov.buf[i].iov_base);
to_send_list.push_back(cur_op->iov.buf[i]);
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
}
}
}
if (cur_op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
{
if (cur_op->op_type == OSD_OP_IN && cur_op->reply.hdr.retval > 0)
to_send_list.push_back((iovec){ .iov_base = cur_op->buf, .iov_len = (size_t)cur_op->reply.hdr.retval });
else if (cur_op->op_type == OSD_OP_OUT && cur_op->req.sec_read_bmp.len > 0)
to_send_list.push_back((iovec){ .iov_base = cur_op->buf, .iov_len = (size_t)cur_op->req.sec_read_bmp.len });
to_outbox.push_back((msgr_sendp_t){ .op = cur_op, .flags = 0 });
}
if (cur_op->op_type == OSD_OP_IN)
{
to_outbox[to_outbox.size()-1].flags |= MSGR_SENDP_FREE;
}
cl->write_ops.push_back(cur_op);
#ifdef WITH_RDMA
if (cl->peer_state == PEER_RDMA)
{
@@ -116,7 +49,7 @@ void osd_messenger_t::outbox_push(osd_op_t *cur_op)
if (!ringloop)
{
// FIXME: It's worse because it doesn't allow batching
while (cl->outbox.size())
while (cl->write_ops.size())
{
try_send(cl);
}
@@ -184,11 +117,25 @@ void osd_messenger_t::measure_exec(osd_op_t *cur_op)
bool osd_messenger_t::try_send(osd_client_t *cl)
{
int peer_fd = cl->peer_fd;
if (!cl->send_list.size() || cl->write_msg.msg_iovlen > 0)
if (!cl->write_op && !cl->write_ops.size() || cl->write_msg.msg_iovlen > 0)
{
return true;
}
assert(cl->peer_state != PEER_RDMA);
while ((cl->write_op || cl->write_ops.size()) && cl->send_list.size() < IOV_MAX)
{
if (!cl->write_op)
{
cl->write_op = cl->write_ops.front();
cl->write_ops.pop_front();
}
osd_op_t *op = cl->write_op;
op_get_write_buffers(cl, cl->send_list);
if (!cl->write_op && op->op_type == OSD_OP_IN)
{
cl->send_free_ops.push_back(op);
}
}
if (ringloop && !use_sync_send_recv)
{
auto iothread = iothreads.size() ? iothreads[peer_fd % iothreads.size()] : NULL;
@@ -201,20 +148,24 @@ bool osd_messenger_t::try_send(osd_client_t *cl)
data_local = {};
}
if (!sqe)
{
return false;
}
cl->send_list_size = 0;
for (auto & iov: cl->send_list)
{
cl->send_list_size += iov.iov_len;
}
cl->write_msg.msg_iov = cl->send_list.data();
cl->write_msg.msg_iovlen = cl->send_list.size() < IOV_MAX ? cl->send_list.size() : IOV_MAX;
cl->refs++;
ring_data_t* data = ((ring_data_t*)sqe->user_data);
data->callback = [this, cl](ring_data_t *data) { handle_send(data->res, data->prev, data->more, cl); };
bool use_zc = has_sendmsg_zc && min_zerocopy_send_size >= 0;
if (use_zc && min_zerocopy_send_size > 0)
if (use_zc && min_zerocopy_send_size > 0 &&
cl->send_list_size/cl->write_msg.msg_iovlen < min_zerocopy_send_size)
{
size_t avg_size = 0;
for (size_t i = 0; i < cl->write_msg.msg_iovlen; i++)
avg_size += cl->write_msg.msg_iov[i].iov_len;
if (avg_size/cl->write_msg.msg_iovlen < min_zerocopy_send_size)
use_zc = false;
use_zc = false;
}
if (use_zc)
{
@@ -251,7 +202,7 @@ void osd_messenger_t::send_replies()
{
int peer_fd = write_ready_clients[i];
auto cl_it = clients.find(peer_fd);
if (cl_it != clients.end() && cl_it->second->peer_state != PEER_RDMA && !try_send(cl_it->second))
if (cl_it != clients.end() && !try_send(cl_it->second))
{
write_ready_clients.erase(write_ready_clients.begin(), write_ready_clients.begin() + i);
return;
@@ -265,6 +216,7 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
if (!prev)
{
cl->write_msg.msg_iovlen = 0;
cl->send_list.clear();
}
if (!more)
{
@@ -297,64 +249,42 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
cl->zc_free_list.erase(cl->zc_free_list.begin(), cl->zc_free_list.begin()+i+1);
return;
}
int done = 0;
while (result > 0 && done < cl->send_list.size())
if (cl->send_list_size > result)
{
iovec & iov = cl->send_list[done];
if (iov.iov_len <= result)
{
if (cl->outbox[done].flags & MSGR_SENDP_FREE)
{
// Reply fully sent
if (more)
cl->zc_free_list.push_back(cl->outbox[done].op);
else
delete cl->outbox[done].op;
}
result -= iov.iov_len;
done++;
}
fprintf(stderr, "Client %d socket write error: expected to send "
"%zu bytes with MSG_WAITALL but sent %u. Disconnecting client\n", cl->peer_fd, cl->send_list_size, result);
stop_client(cl->peer_fd);
return;
}
for (auto op: cl->send_free_ops)
{
if (more)
cl->zc_free_list.push_back(op);
else
{
iov.iov_len -= result;
iov.iov_base = (uint8_t*)iov.iov_base + result;
break;
}
delete op;
}
if (more)
{
int expected = cl->send_list.size() < IOV_MAX ? cl->send_list.size() : IOV_MAX;
if (done != expected)
{
fprintf(stderr, "Client %d socket write error: expected to send "
"%d iovecs with MSG_WAITALL but sent %d. Disconnecting client\n", cl->peer_fd, expected, done);
stop_client(cl->peer_fd);
return;
}
cl->zc_free_list.push_back(NULL); // end marker
}
if (done > 0)
{
cl->send_list.erase(cl->send_list.begin(), cl->send_list.begin()+done);
cl->outbox.erase(cl->outbox.begin(), cl->outbox.begin()+done);
}
if (cl->next_send_list.size())
{
cl->send_list.insert(cl->send_list.end(), cl->next_send_list.begin(), cl->next_send_list.end());
cl->outbox.insert(cl->outbox.end(), cl->next_outbox.begin(), cl->next_outbox.end());
cl->next_send_list.clear();
cl->next_outbox.clear();
}
cl->write_state = cl->outbox.size() > 0 ? CL_WRITE_READY : 0;
cl->send_free_ops.clear();
cl->write_state = cl->write_op || cl->write_ops.size() ? CL_WRITE_READY : 0;
#ifdef WITH_RDMA
if (cl->rdma_conn && !cl->outbox.size() && cl->peer_state == PEER_RDMA_CONNECTING)
if (cl->rdma_conn && !cl->write_op && !cl->write_ops.size() && cl->peer_state == PEER_RDMA_CONNECTING)
{
// FIXME: Do something better than just forgetting the FD
// FIXME: Ignore pings during RDMA state transition
if (log_level > 0)
{
fprintf(stderr, "Successfully connected with client %d using RDMA\n", cl->peer_fd);
}
cl->peer_state = PEER_RDMA;
tfd->set_fd_handler(cl->peer_fd, false, [this](int peer_fd, int epoll_events)
{
// Do not miss the disconnection!
if (epoll_events & EPOLLRDHUP)
{
handle_peer_epoll(peer_fd, epoll_events);
}
});
// Add the initial receive request
init_recv_rdma(cl);
}
@@ -365,3 +295,158 @@ void osd_messenger_t::handle_send(int result, bool prev, bool more, osd_client_t
write_ready_clients.push_back(cl->peer_fd);
}
}
static inline bool op_write_headers(osd_op_t *op, std::function<bool(uint8_t*, size_t)> op_write_buf)
{
// Header
if (!op_write_buf((op->op_type == OSD_OP_IN ? op->reply.buf : op->req.buf), OSD_PACKET_SIZE))
return false;
// Bitmap
if (op->op_type == OSD_OP_IN &&
op->req.hdr.opcode == OSD_OP_SEC_READ &&
op->reply.sec_rw.attr_len > 0)
{
if (!op_write_buf((uint8_t*)op->bitmap, op->reply.sec_rw.attr_len))
return false;
}
else if (op->op_type == OSD_OP_OUT &&
(op->req.hdr.opcode == OSD_OP_SEC_WRITE || op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE) &&
op->req.sec_rw.attr_len > 0)
{
if (!op_write_buf((uint8_t*)op->bitmap, op->req.sec_rw.attr_len))
return false;
}
if (op->req.hdr.opcode == OSD_OP_SEC_READ_BMP)
{
if (op->op_type == OSD_OP_IN && op->reply.hdr.retval > 0)
{
if (!op_write_buf((uint8_t*)op->buf, (size_t)op->reply.hdr.retval))
return false;
}
else if (op->op_type == OSD_OP_OUT && op->req.sec_read_bmp.len > 0)
{
if (!op_write_buf((uint8_t*)op->buf, (size_t)op->req.sec_read_bmp.len))
return false;
}
}
return true;
}
static inline bool op_has_data(osd_op_t *op)
{
return (op->op_type == OSD_OP_IN
? (op->req.hdr.opcode == OSD_OP_READ ||
op->req.hdr.opcode == OSD_OP_SEC_READ ||
op->req.hdr.opcode == OSD_OP_SEC_LIST ||
op->req.hdr.opcode == OSD_OP_SHOW_CONFIG ||
op->req.hdr.opcode == OSD_OP_DESCRIBE)
: (op->req.hdr.opcode == OSD_OP_WRITE ||
op->req.hdr.opcode == OSD_OP_SEC_WRITE ||
op->req.hdr.opcode == OSD_OP_SEC_WRITE_STABLE ||
op->req.hdr.opcode == OSD_OP_SEC_STABILIZE ||
op->req.hdr.opcode == OSD_OP_SEC_ROLLBACK ||
op->req.hdr.opcode == OSD_OP_SHOW_CONFIG)) && op->iov.count > 0;
}
size_t osd_messenger_t::op_copy_to(osd_client_t *cl, uint8_t *dst, size_t dst_len)
{
size_t done = 0;
size_t from = cl->write_op_pos;
auto op_write_buf = [&](uint8_t *src, size_t src_len)
{
if (from < src_len)
{
size_t n = src_len-from;
if (n > dst_len-done)
n = dst_len-done;
memcpy(dst+done, src+from, n);
done += n;
cl->write_op_pos += n;
from += n;
if (from < src_len)
return false;
from = 0;
}
else
from -= src_len;
return true;
};
if (!op_write_headers(cl->write_op, op_write_buf))
{
return done;
}
// Operation data
if (op_has_data(cl->write_op))
{
if (cl->write_op->enc)
{
if (!op_encrypted_copy_data_to(cl, dst, dst_len, from, done))
{
return done;
}
}
else
{
for (int i = 0; i < cl->write_op->iov.count; i++)
{
if (!op_write_buf((uint8_t*)cl->write_op->iov.buf[i].iov_base, cl->write_op->iov.buf[i].iov_len))
return done;
}
}
}
cl->write_op = NULL;
cl->write_op_pos = 0;
return done;
}
void osd_messenger_t::op_get_write_buffers(osd_client_t *cl, std::vector<iovec> & lst)
{
size_t from = cl->write_op_pos;
auto op_write_buf = [&](uint8_t *src, size_t src_len)
{
if (lst.size() >= IOV_MAX)
return false;
if (from < src_len)
{
lst.push_back((iovec){ .iov_base = src+from, .iov_len = src_len-from });
cl->write_op_pos += src_len-from;
from = 0;
}
else
from -= src_len;
return true;
};
if (!op_write_headers(cl->write_op, op_write_buf))
{
return;
}
// Operation data
if (op_has_data(cl->write_op))
{
if (cl->write_op->enc)
{
if (lst.size() >= IOV_MAX)
return;
// No way except to allocate a temporary buffer and encrypt data to it
assert(cl->write_op->req.hdr.opcode == OSD_OP_WRITE);
size_t remsize = cl->write_op->req.rw.len - from + (from % 16);
assert(remsize > 0);
assert(!cl->write_op->enc_buf);
cl->write_op->enc_buf = (uint8_t*)malloc_or_die(remsize);
size_t done = 0;
bool end = op_encrypted_copy_data_to(cl, cl->write_op->enc_buf, remsize, from, done);
assert(end);
lst.push_back((iovec){ .iov_base = cl->write_op->enc_buf, .iov_len = remsize });
}
else
{
for (int i = 0; i < cl->write_op->iov.count; i++)
{
if (!op_write_buf((uint8_t*)cl->write_op->iov.buf[i].iov_base, cl->write_op->iov.buf[i].iov_len))
return;
}
}
}
cl->write_op = NULL;
cl->write_op_pos = 0;
}
+23 -1
View File
@@ -57,7 +57,6 @@ void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete)
{
return;
}
clear_immediate_ops(peer_fd);
if (log_level > 0)
{
if (cl->osd_num)
@@ -73,6 +72,22 @@ void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete)
fprintf(stderr, "[OSD %ju] Stopping client %d (regular client)\n", osd_num, peer_fd);
}
}
if (cl->encrypt_ctx)
{
if (encrypt_ctx_pool.size() > max_aes_xts_pool_size)
destroy_aes_xts_encrypt(cl->encrypt_ctx);
else
encrypt_ctx_pool.push_back(cl->encrypt_ctx);
cl->encrypt_ctx = NULL;
}
if (cl->decrypt_ctx)
{
if (decrypt_ctx_pool.size() > max_aes_xts_pool_size)
destroy_aes_xts_decrypt(cl->decrypt_ctx);
else
decrypt_ctx_pool.push_back(cl->decrypt_ctx);
cl->decrypt_ctx = NULL;
}
// First set state to STOPPED so another stop_client() call doesn't try to free it again
cl->refs++;
int prev_state = cl->peer_state;
@@ -169,6 +184,13 @@ osd_client_t::~osd_client_t()
}
// Cancel outbound ops
cancel_ops();
for (osd_op_t *op: send_free_ops)
{
if (op)
{
delete op;
}
}
for (osd_op_t *op: zc_free_list)
{
if (op)
-24
View File
@@ -20,15 +20,6 @@ typedef uint64_t inode_t;
// Pool ID is 16 bits long
typedef uint32_t pool_id_t;
typedef uint64_t osd_num_t;
typedef uint32_t pg_num_t;
struct pool_pg_num_t
{
pool_id_t pool_id;
pg_num_t pg_num;
};
// 16 bytes per object/stripe id
// stripe = (start of the parity stripe + peer role)
// i.e. for example (256KB + one of 0,1,2)
@@ -70,21 +61,6 @@ inline bool operator < (const obj_ver_id & a, const obj_ver_id & b)
return a.oid < b.oid || a.oid == b.oid && a.version < b.version;
}
inline bool operator < (const pool_pg_num_t & a, const pool_pg_num_t & b)
{
return a.pool_id < b.pool_id || a.pool_id == b.pool_id && a.pg_num < b.pg_num;
}
inline bool operator == (const pool_pg_num_t & a, const pool_pg_num_t & b)
{
return a.pool_id == b.pool_id && a.pg_num == b.pg_num;
}
inline bool operator != (const pool_pg_num_t & a, const pool_pg_num_t & b)
{
return a.pool_id != b.pool_id || a.pg_num != b.pg_num;
}
namespace std
{
template<> struct hash<object_id>
+1
View File
@@ -4,6 +4,7 @@
#pragma once
#include "object_id.h"
#include "osd_id.h"
// Magic numbers
#define SECONDARY_OSD_OP_MAGIC 0x2bd7b10325434553l
+1 -1
View File
@@ -6,7 +6,7 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: Vitastor
Description: Vitastor client library
Version: 3.0.6
Version: 3.0.3
Libs: -L${libdir} -lvitastor_client
Cflags: -I${includedir}
+12
View File
@@ -2,6 +2,15 @@ cmake_minimum_required(VERSION 2.8.12)
project(vitastor)
set(OPENAPI_JSON_H "${CMAKE_CURRENT_BINARY_DIR}/openapi.json.h")
add_custom_command(
OUTPUT ${OPENAPI_JSON_H}
COMMAND ${CMAKE_COMMAND} -E echo const char* openapi_description = R\\\"json\\\( > ${OPENAPI_JSON_H}
COMMAND ${CMAKE_COMMAND} -E cat ${CMAKE_CURRENT_SOURCE_DIR}/openapi.json >> ${OPENAPI_JSON_H}
COMMAND ${CMAKE_COMMAND} -E echo "\\)json\\\"\\;" >> ${OPENAPI_JSON_H}
DEPENDS openapi.json
)
# libvitastor_cli.a
add_library(vitastor_cli STATIC
cli_common.cpp
@@ -27,8 +36,11 @@ add_library(vitastor_cli STATIC
cli_pool_ls.cpp
cli_pool_modify.cpp
cli_pool_rm.cpp
cli_serve.cpp
${OPENAPI_JSON_H}
)
target_compile_options(vitastor_cli PUBLIC -fPIC)
target_include_directories(vitastor_cli PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# vitastor-cli
add_executable(vitastor-cli
+70 -34
View File
@@ -37,6 +37,7 @@ static const char* help_text =
" --sort FIELD Sort by specified field (name, size, used_size, <read|write|delete>_<iops|bps|lat|queue>)\n"
" -r|--reverse Sort in descending order\n"
" -n|--count N Only list first N items\n"
" --ids ID1,ID2 Only list images with specified full IDs\n"
" --tree Show image snapshot/clone tree\n"
"\n"
"vitastor-cli create -s|--size <size> [-p|--pool <id|name>] [--parent <parent_name>[@<snapshot>]] <name>\n"
@@ -217,7 +218,7 @@ static const char* help_text =
"vitastor-cli rm-pool|pool-rm [--force] <id|name>\n"
" Remove a pool. Refuses to remove pools with images without --force.\n"
"\n"
"vitastor-cli ls-pools|pool-ls|ls-pool|pools [-l] [--detail] [--sort FIELD] [-r] [-n N] [--stats] [<glob> ...]\n"
"vitastor-cli ls-pools|pool-ls|ls-pool|pools [-l] [--detail] [--sort FIELD] [-r] [-n N] [<glob> ...]\n"
" List pools (only matching <glob> patterns if passed).\n"
" -l|--long Also report I/O statistics\n"
" --detail Use list format (not table), show all details\n"
@@ -225,6 +226,14 @@ static const char* help_text =
" -r|--reverse Sort in descending order\n"
" -n|--count N Only list first N items\n"
"\n"
"vitastor-cli serve\n"
" Start HTTP server able to handle CLI commands over a REST API. Options:\n"
" --bind_address ADDR Specify server IP address or addresses, separated by space. Default is 127.0.0.1.\n"
" --port 8080 Specify server port.\n"
" --ssl_cert FILE Path to server SSL certificate file (PEM format).\n"
" --ssl_key FILE Path to server SSL private key file.\n"
" --ssl_ca FILE Path to file with SSL CA certificates used to validate client connections.\n"
"\n"
"Use vitastor-cli --help <command> for command details or vitastor-cli --help --all for all details.\n"
"\n"
"GLOBAL OPTIONS:\n"
@@ -319,27 +328,24 @@ static json11::Json::object parse_args(int narg, const char *args[])
return cfg;
}
static int run(cli_tool_t *p, json11::Json::object cfg)
std::function<bool(cli_result_t &)> cli_tool_t::start(json11::Json::object cfg, cli_result_t & result)
{
cli_result_t result = {};
p->is_command_line = true;
p->parse_config(cfg);
json11::Json::array cmd = cfg["command"].array_items();
cfg.erase("command");
std::function<bool(cli_result_t &)> action_cb;
if (!cmd.size())
{
result = { .err = EINVAL, .text = "command is missing" };
result = { .err = EOPNOTSUPP, .text = "command is missing" };
}
else if (cmd[0] == "status")
{
// Show cluster status
action_cb = p->start_status(cfg);
action_cb = start_status(cfg);
}
else if (cmd[0] == "df")
{
// Show pool space stats
action_cb = p->start_pool_ls(cfg);
action_cb = start_pool_ls(cfg);
}
else if (cmd[0] == "ls")
{
@@ -349,7 +355,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["names"] = cmd;
}
action_cb = p->start_ls(cfg);
action_cb = start_ls(cfg);
}
else if (cmd[0] == "snap-create")
{
@@ -364,7 +370,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = name.substr(0, pos);
cfg["snapshot"] = name.substr(pos + 1);
action_cb = p->start_create(cfg);
action_cb = start_create(cfg);
}
}
else if (cmd[0] == "create")
@@ -374,7 +380,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = cmd[1];
}
action_cb = p->start_create(cfg);
action_cb = start_create(cfg);
}
else if (cmd[0] == "modify")
{
@@ -383,12 +389,12 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = cmd[1];
}
action_cb = p->start_modify(cfg);
action_cb = start_modify(cfg);
}
else if (cmd[0] == "rm-data")
{
// Delete inode data
action_cb = p->start_rm_data(cfg);
action_cb = start_rm_data(cfg);
}
else if (cmd[0] == "rm-osd")
{
@@ -398,7 +404,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["osd_id"] = cmd;
}
action_cb = p->start_rm_osd(cfg);
action_cb = start_rm_osd(cfg);
}
else if (cmd[0] == "merge-data")
{
@@ -409,7 +415,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
if (cmd.size() > 2)
cfg["to"] = cmd[2];
}
action_cb = p->start_merge(cfg);
action_cb = start_merge(cfg);
}
else if (cmd[0] == "flatten")
{
@@ -418,7 +424,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["image"] = cmd[1];
}
action_cb = p->start_flatten(cfg);
action_cb = start_flatten(cfg);
}
else if (cmd[0] == "dd")
{
@@ -432,16 +438,31 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cfg[arg.substr(0, p)] = arg.substr(p+1);
}
}
action_cb = p->start_dd(cfg);
action_cb = start_dd(cfg);
}
else if (cmd[0] == "rm")
{
// Remove multiple snapshots and rebase their children
if (cfg["exact"].bool_value() || cfg["matching"].bool_value())
if (cfg["names"].is_array())
{
cfg["globs"] = cfg["names"];
cfg.erase("names");
cfg["exact"] = true;
cfg["matching"] = false;
action_cb = start_rm_wildcard(cfg);
}
else if (cfg["matching"].is_array())
{
cfg["globs"] = cfg["matching"];
cfg["exact"] = false;
cfg["matching"] = true;
action_cb = start_rm_wildcard(cfg);
}
else if (cfg["exact"].bool_value() || cfg["matching"].bool_value())
{
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["globs"] = cmd;
action_cb = p->start_rm_wildcard(cfg);
action_cb = start_rm_wildcard(cfg);
}
else
{
@@ -451,41 +472,41 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
if (cmd.size() > 2)
cfg["to"] = cmd[2];
}
action_cb = p->start_rm(cfg);
action_cb = start_rm(cfg);
}
}
else if (cmd[0] == "describe")
{
// Describe unclean objects
action_cb = p->start_describe(cfg);
action_cb = start_describe(cfg);
}
else if (cmd[0] == "fix")
{
// Fix inconsistent objects (by deleting some copies)
action_cb = p->start_fix(cfg);
action_cb = start_fix(cfg);
}
else if (cmd[0] == "alloc-osd")
{
// Allocate a new OSD number
action_cb = p->start_alloc_osd(cfg);
action_cb = start_alloc_osd(cfg);
}
else if (cmd[0] == "osd-tree")
{
// Print OSD tree
action_cb = p->start_osd_tree(cfg);
cfg["as_tree"] = true;
action_cb = start_osd_tree(cfg);
}
else if (cmd[0] == "osds" || cmd[0] == "ls-osds" || cmd[0] == "ls-osd" || cmd[0] == "osd-ls")
{
// Print OSD list
cfg["flat"] = true;
action_cb = p->start_osd_tree(cfg);
action_cb = start_osd_tree(cfg);
}
else if (cmd[0] == "modify-osd")
{
// Modify OSD configuration
if (cmd.size() > 1)
cfg["osd_num"] = cmd[1];
action_cb = p->start_modify_osd(cfg);
action_cb = start_modify_osd(cfg);
}
else if (cmd[0] == "pg-list" || cmd[0] == "pg-ls" || cmd[0] == "list-pg" || cmd[0] == "ls-pg" || cmd[0] == "ls-pgs" || cmd[0] == "pgs")
{
@@ -495,7 +516,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["pg_state"] = cmd;
}
action_cb = p->start_pg_list(cfg);
action_cb = start_pg_list(cfg);
}
else if (cmd[0] == "create-pool" || cmd[0] == "pool-create")
{
@@ -504,16 +525,16 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["name"] = cmd[1];
}
action_cb = p->start_pool_create(cfg);
action_cb = start_pool_create(cfg);
}
else if (cmd[0] == "modify-pool" || cmd[0] == "pool-modify")
{
// Modify existing pool
if (cmd.size() > 1)
{
cfg["old_name"] = cmd[1];
cfg["pool"] = cmd[1];
}
action_cb = p->start_pool_modify(cfg);
action_cb = start_pool_modify(cfg);
}
else if (cmd[0] == "rm-pool" || cmd[0] == "pool-rm")
{
@@ -522,7 +543,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
cfg["pool"] = cmd[1];
}
action_cb = p->start_pool_rm(cfg);
action_cb = start_pool_rm(cfg);
}
else if (cmd[0] == "ls-pool" || cmd[0] == "pool-ls" || cmd[0] == "ls-pools" || cmd[0] == "pools")
{
@@ -533,12 +554,26 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
cmd.erase(cmd.begin(), cmd.begin()+1);
cfg["names"] = cmd;
}
action_cb = p->start_pool_ls(cfg);
action_cb = start_pool_ls(cfg);
}
else if (cmd[0] == "serve")
{
// Start HTTP server
action_cb = start_serve(cfg);
}
else
{
result = { .err = EINVAL, .text = "unknown command: "+cmd[0].string_value() };
result = { .err = EOPNOTSUPP, .text = "unknown command: "+cmd[0].string_value() };
}
return action_cb;
}
static int run(cli_tool_t *p, json11::Json::object cfg)
{
cli_result_t result = {};
p->is_command_line = true;
p->parse_config(cfg);
auto action_cb = p->start(cfg, result);
if (action_cb != NULL)
{
// Create client
@@ -550,6 +585,7 @@ static int run(cli_tool_t *p, json11::Json::object cfg)
{
result = r;
action_cb = NULL;
p->ringloop->submit();
});
// Loop until it completes
while (action_cb != NULL)
+5 -1
View File
@@ -7,6 +7,7 @@
#include "json11/json11.hpp"
#include "object_id.h"
#include "osd_id.h"
#include "ringloop.h"
#include <functional>
@@ -46,6 +47,7 @@ public:
json11::Json etcd_result;
void parse_config(json11::Json::object & cfg);
void parse_api_opts(json11::Json::object & cfg);
json11::Json parse_tags(std::string tags);
void change_parent(inode_t cur, inode_t new_parent, cli_result_t *result);
@@ -56,8 +58,10 @@ public:
friend struct snap_flattener_t;
friend struct snap_remover_t;
std::function<bool(cli_result_t &)> start(json11::Json::object cfg, cli_result_t & result);
std::function<bool(cli_result_t &)> start_alloc_osd(json11::Json);
std::function<bool(cli_result_t &)> start_create(json11::Json);
std::function<bool(cli_result_t &)> start_dd(json11::Json);
std::function<bool(cli_result_t &)> start_describe(json11::Json);
std::function<bool(cli_result_t &)> start_fix(json11::Json);
std::function<bool(cli_result_t &)> start_flatten(json11::Json);
@@ -75,8 +79,8 @@ public:
std::function<bool(cli_result_t &)> start_rm_data(json11::Json);
std::function<bool(cli_result_t &)> start_rm_osd(json11::Json);
std::function<bool(cli_result_t &)> start_rm_wildcard(json11::Json);
std::function<bool(cli_result_t &)> start_serve(json11::Json);
std::function<bool(cli_result_t &)> start_status(json11::Json);
std::function<bool(cli_result_t &)> start_dd(json11::Json);
// Should be called like loop_and_wait(start_status(), <completion callback>)
void loop_and_wait(std::function<bool(cli_result_t &)> loop_cb, std::function<void(const cli_result_t &)> complete_cb);
+11 -7
View File
@@ -101,6 +101,16 @@ inode_config_t* cli_tool_t::get_inode_cfg(const std::string & name)
return NULL;
}
void cli_tool_t::parse_api_opts(json11::Json::object & cfg)
{
iodepth = cfg["iodepth"].uint64_value();
if (!iodepth)
iodepth = 32;
parallel_osds = cfg["parallel_osds"].uint64_value();
if (!parallel_osds)
parallel_osds = 4;
}
void cli_tool_t::parse_config(json11::Json::object & cfg)
{
for (auto kv_it = cfg.begin(); kv_it != cfg.end();)
@@ -121,15 +131,10 @@ void cli_tool_t::parse_config(json11::Json::object & cfg)
else
color = isatty(1);
json_output = cfg["json"].bool_value();
iodepth = cfg["iodepth"].uint64_value();
if (!iodepth)
iodepth = 32;
parallel_osds = cfg["parallel_osds"].uint64_value();
if (!parallel_osds)
parallel_osds = 4;
log_level = cfg["log_level"].int64_value();
progress = cfg["progress"].uint64_value() ? true : false;
list_first = cfg["wait_list"].uint64_value() ? true : false;
parse_api_opts(cfg);
}
struct cli_result_looper_t
@@ -153,7 +158,6 @@ void cli_tool_t::loop_and_wait(std::function<bool(cli_result_t &)> loop_cb, std:
ringloop->unregister_consumer(&looper->consumer);
looper->loop_cb = NULL;
looper->complete_cb(looper->result);
ringloop->submit();
delete looper;
return;
}
+47 -12
View File
@@ -64,7 +64,8 @@ struct image_creator_t
}
if (new_pool_id)
{
if (pools.find(new_pool_id) == pools.end())
auto pool_it = pools.find(new_pool_id);
if (pool_it == pools.end())
{
result = (cli_result_t){ .err = ENOENT, .text = "Pool "+std::to_string(new_pool_id)+" does not exist" };
state = 100;
@@ -194,15 +195,31 @@ resume_3:
// Save into inode_config for library users to be able to take it from there immediately
new_cfg.mod_revision = parent->etcd_result["header"]["revision"].uint64_value();
parent->cli->st_cli.insert_inode_config(new_cfg);
auto img = json11::Json::object {
{ "inode_id", INODE_WITH_POOL(new_pool_id, new_id) },
{ "inode_num", new_id },
{ "name", image_name },
{ "pool_id", (uint64_t)new_pool_id },
{ "size", size },
};
{
auto new_pool_it = parent->cli->st_cli.pool_config.find(new_pool_id);
if (new_pool_it != parent->cli->st_cli.pool_config.end())
{
img["pool_name"] = new_pool_it->second.name;
}
}
if (new_parent_id)
{
img["parent_name"] = new_parent;
img["parent_inode_id"] = new_parent_id;
img["parent_inode_num"] = INODE_NO_POOL(new_parent_id);
img["parent_pool_id"] = (uint64_t)INODE_POOL(new_parent_id);
}
result = (cli_result_t){
.err = 0,
.text = "Image "+image_name+" created",
.data = json11::Json::object {
{ "name", image_name },
{ "pool", new_pool_name },
{ "parent", new_parent },
{ "size", size },
}
.data = img,
};
state = 100;
}
@@ -272,13 +289,23 @@ resume_4:
// Save into inode_config for library users to be able to take it from there immediately
new_cfg.mod_revision = parent->etcd_result["header"]["revision"].uint64_value();
parent->cli->st_cli.insert_inode_config(new_cfg);
{
auto new_pool_it = parent->cli->st_cli.pool_config.find(new_pool_id);
new_pool_name = new_pool_it != parent->cli->st_cli.pool_config.end() ? new_pool_it->second.name : "";
}
result = (cli_result_t){
.err = 0,
.text = "Snapshot "+image_name+"@"+new_snap+" created",
.data = json11::Json::object {
{ "name", image_name+"@"+new_snap },
{ "pool", (uint64_t)new_pool_id },
{ "parent", new_parent },
{ "inode_id", INODE_WITH_POOL(new_pool_id, new_id) },
{ "inode_num", new_id },
{ "name", image_name },
{ "pool_id", (uint64_t)new_pool_id },
{ "pool_name", new_pool_name },
{ "parent_name", image_name+"@"+new_snap },
{ "parent_inode_id", INODE_WITH_POOL(old_pool_id, old_id) },
{ "parent_inode_num", old_id },
{ "parent_pool_id", (uint64_t)old_pool_id },
{ "size", size },
}
};
@@ -554,8 +581,16 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_create(json11::Json cfg)
auto image_creator = new image_creator_t();
image_creator->parent = this;
image_creator->image_name = cfg["image"].string_value();
image_creator->new_pool_id = cfg["pool"].uint64_value();
image_creator->new_pool_name = cfg["pool"].string_value();
if (!cfg["pool"].is_null())
{
image_creator->new_pool_id = cfg["pool"].uint64_value();
image_creator->new_pool_name = cfg["pool"].string_value();
}
else
{
image_creator->new_pool_id = cfg["pool_id"].uint64_value();
image_creator->new_pool_name = cfg["pool_name"].string_value();
}
image_creator->force = cfg["force"].bool_value();
image_creator->force_size = cfg["force_size"].bool_value();
if (cfg["image_meta"].is_object())
+2 -2
View File
@@ -442,7 +442,7 @@ struct cli_dd_t
}
delete cur_read;
}
else if (!is_zero(read_op->bitmap_buf, (read_op->len/iinfo.in_granularity+7)/8))
else if (!is_zero(read_op->bitmap_buf, read_op->len/iinfo.in_granularity/8))
{
vitastor_read(cur_read);
}
@@ -864,7 +864,7 @@ resume_2:
// Copy data
if (iinfo.in_seekable && iseek >= iinfo.in_size)
{
result = (cli_result_t){ .err = -EINVAL, .text = "Input seek position is beyond end of input" };
result = (cli_result_t){ .err = EINVAL, .text = "Input seek position is beyond end of input" };
goto close_end;
}
if (!iinfo.iwatch && !iinfo.in_seekable && iseek)
+31 -3
View File
@@ -57,12 +57,24 @@ struct cli_describe_t
void parse_options(json11::Json cfg)
{
only_pool = cfg["pool"].uint64_value();
if (!only_pool && cfg["pool"].is_string())
uint64_t pool_id;
std::string pool_name;
if (!cfg["pool"].is_null())
{
pool_id = cfg["pool"].uint64_value();
pool_name = pool_id ? "" : cfg["pool"].string_value();
}
else
{
pool_id = cfg["pool_id"].uint64_value();
pool_name = pool_id ? "" : cfg["pool_name"].string_value();
}
only_pool = pool_id;
if (!only_pool && pool_name != "")
{
for (auto & pp: parent->cli->st_cli.pool_config)
{
if (pp.second.name == cfg["pool"].string_value())
if (pp.second.name == pool_name)
{
only_pool = pp.first;
break;
@@ -106,6 +118,22 @@ struct cli_describe_t
if (cfg["object_state"].string_value().find("misplaced") != std::string::npos)
object_state |= OBJ_MISPLACED;
}
else if (!object_state && cfg["object_state"].is_array())
{
for (auto & st: cfg["object_state"].array_items())
{
if (st == "inconsistent")
object_state |= OBJ_INCONSISTENT;
else if (st == "corrupted")
object_state |= OBJ_CORRUPTED;
else if (st == "incomplete")
object_state |= OBJ_INCOMPLETE;
else if (st == "degraded")
object_state |= OBJ_DEGRADED;
else if (st == "misplaced")
object_state |= OBJ_MISPLACED;
}
}
}
void loop()
+41 -15
View File
@@ -17,6 +17,7 @@ struct image_lister_t
std::string list_pool_name;
std::string sort_field;
std::set<std::string> only_names;
std::vector<uint64_t> only_ids;
bool reverse = false;
bool exact = false;
bool tree = false;
@@ -63,7 +64,6 @@ struct image_lister_t
auto item = json11::Json::object {
{ "name", ic.second.name },
{ "size", ic.second.size },
{ "used_size", 0 },
{ "readonly", ic.second.readonly },
{ "pool_id", (uint64_t)INODE_POOL(ic.second.num) },
{ "pool_name", good_pool ? pool_it->second.name : "? (ID:"+std::to_string(INODE_POOL(ic.second.num))+")" },
@@ -202,20 +202,33 @@ resume_1:
json11::Json::array to_list()
{
json11::Json::array list;
for (auto & kv: stats)
if (only_ids.size())
{
if (!only_names.size())
for (auto & id: only_ids)
{
list.push_back(kv.second);
}
else
{
for (auto & glob: only_names)
if (stats.find(id) != stats.end())
{
if (exact ? (kv.second["name"].string_value() == glob) : stupid_glob(kv.second["name"].string_value(), glob))
list.push_back(stats[id]);
}
}
}
else
{
for (auto & kv: stats)
{
if (!only_names.size())
{
list.push_back(kv.second);
}
else
{
for (auto & glob: only_names)
{
list.push_back(kv.second);
break;
if (exact ? (kv.second["name"].string_value() == glob) : stupid_glob(kv.second["name"].string_value(), glob))
{
list.push_back(kv.second);
break;
}
}
}
}
@@ -570,17 +583,30 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_ls(json11::Json cfg)
lister->parent = this;
lister->exact = cfg["exact"].bool_value();
lister->tree = cfg["tree"].bool_value();
lister->list_pool_id = cfg["pool"].uint64_value();
lister->list_pool_name = lister->list_pool_id ? "" : cfg["pool"].as_string();
if (!cfg["pool"].is_null())
{
lister->list_pool_id = cfg["pool"].uint64_value();
lister->list_pool_name = lister->list_pool_id ? "" : cfg["pool"].as_string();
}
else
{
lister->list_pool_id = cfg["pool_id"].uint64_value();
lister->list_pool_name = lister->list_pool_id ? "" : cfg["pool_name"].string_value();
}
lister->show_stats = cfg["long"].bool_value();
lister->show_delete = cfg["del"].bool_value();
lister->sort_field = cfg["sort"].string_value() != "" ? cfg["sort"].string_value() : "name";
lister->reverse = cfg["reverse"].bool_value();
lister->max_count = cfg["count"].uint64_value();
if (cfg["names"].is_string())
lister->only_names.insert(cfg["names"].string_value());
for (auto & item: cfg["names"].array_items())
{
lister->only_names.insert(item.string_value());
}
if (cfg["ids"].is_string())
for (auto & item: explode(",", cfg["ids"].string_value(), true))
lister->only_ids.push_back(stoull_full(item));
for (auto & item: cfg["ids"].array_items())
lister->only_ids.push_back(item.uint64_value());
return [lister](cli_result_t & result)
{
lister->loop();
+1 -1
View File
@@ -374,7 +374,7 @@ struct snap_merger_t
result = (cli_result_t){ .text = "Done, layers from "+from_name+" to "+to_name+" merged into "+target_name, .data = json11::Json::object {
{ "from", from_name },
{ "to", to_name },
{ "into", target_name },
{ "target", target_name },
}};
state = 100;
resume_100:
+34 -10
View File
@@ -88,10 +88,7 @@ struct image_changer_t
(!new_size && !force_size || cfg.size == new_size || cfg.size >= new_size && inc_size) &&
(new_name == "" || new_name == image_name))
{
result = (cli_result_t){ .err = 0, .text = "No change", .data = json11::Json::object {
{ "error_code", 0 },
{ "error_text", "No change" },
}};
result = (cli_result_t){ .err = 0, .text = "No change", .data = fill_img(cfg) };
state = 100;
return;
}
@@ -235,15 +232,42 @@ resume_2:
result = (cli_result_t){
.err = 0,
.text = "Image "+image_name+" modified",
.data = json11::Json::object {
{ "name", image_name },
{ "inode", INODE_NO_POOL(inode_num) },
{ "pool", (uint64_t)INODE_POOL(inode_num) },
{ "size", new_size },
}
.data = fill_img(cfg)
};
state = 100;
}
json11::Json fill_img(inode_config_t & cfg)
{
auto img = json11::Json::object {
{ "inode_id", inode_num },
{ "inode_num", INODE_NO_POOL(inode_num) },
{ "name", cfg.name },
{ "pool_id", (uint64_t)INODE_POOL(inode_num) },
{ "size", cfg.size },
{ "readonly", cfg.readonly },
{ "deleted", cfg.deleted },
};
{
auto pool_it = parent->cli->st_cli.pool_config.find(INODE_POOL(inode_num));
if (pool_it != parent->cli->st_cli.pool_config.end())
{
img["pool_name"] = pool_it->second.name;
}
}
if (cfg.parent_id)
{
auto parent_it = parent->cli->st_cli.inode_config.find(cfg.parent_id);
if (parent_it != parent->cli->st_cli.inode_config.end())
{
img["parent_name"] = parent_it->second.name;
}
img["parent_inode_id"] = cfg.parent_id;
img["parent_inode_num"] = INODE_NO_POOL(cfg.parent_id);
img["parent_pool_id"] = (uint64_t)INODE_POOL(cfg.parent_id);
}
return img;
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_modify(json11::Json cfg)
+26 -18
View File
@@ -41,7 +41,7 @@ struct osd_tree_printer_t
{
cli_tool_t *parent;
json11::Json cfg;
bool flat = false;
bool as_tree = false;
bool show_stats = false;
int state = 0;
@@ -209,11 +209,14 @@ resume_1:
for (int i = 1; i < node_seq.size(); i++)
{
auto & node = placement_tree->nodes.at(node_seq[i]);
fmt_items.push_back(json11::Json::object{
{ "type", node.level },
{ "name", node.name },
{ "parent", node.parent },
});
if (as_tree)
{
fmt_items.push_back(json11::Json::object{
{ "type", node.level },
{ "name", node.name },
{ "parent", node.parent },
});
}
for (uint64_t osd_num: node.child_osds)
{
auto & osd = placement_tree->osds.at(osd_num);
@@ -221,17 +224,22 @@ resume_1:
{ "type", "osd" },
{ "name", osd.num },
{ "parent", node.name },
{ "up", osd.up ? "up" : "down" },
{ "up", osd.up },
{ "size", osd.size },
{ "free", osd.free },
{ "reweight", osd.reweight },
{ "noout", osd.noout },
{ "tags", osd.tags },
{ "block", (uint64_t)osd.block_size },
{ "bitmap", (uint64_t)osd.bitmap_granularity },
{ "commit", osd.immediate_commit == IMMEDIATE_NONE ? "none" : (osd.immediate_commit == IMMEDIATE_ALL ? "all" : "small") },
{ "op_stats", osd_stats[osd_num]["op_stats"] },
{ "data_block_size", (uint64_t)osd.block_size },
{ "bitmap_granularity", (uint64_t)osd.bitmap_granularity },
{ "immediate_commit", osd.immediate_commit == IMMEDIATE_NONE ? "none" : (osd.immediate_commit == IMMEDIATE_ALL ? "all" : "small") },
};
if (show_stats)
{
json_osd["op_stats"] = osd_stats[osd_num]["op_stats"];
json_osd["subop_stats"] = osd_stats[osd_num]["subop_stats"];
json_osd["recovery_stats"] = osd_stats[osd_num]["recovery_stats"];
}
if (osd_stats[osd_num]["slow_ops_primary"].uint64_value() > 0)
{
json_osd["slow_ops_primary"] = osd_stats[osd_num]["slow_ops_primary"];
@@ -249,7 +257,7 @@ resume_1:
for (int i = 1; i < node_seq.size(); i++)
{
auto & node = placement_tree->nodes.at(node_seq[i]);
if (!flat)
if (as_tree)
{
fmt_items.push_back(json11::Json::object{
{ "type", str_repeat(" ", indents[i]) + node.level },
@@ -257,7 +265,7 @@ resume_1:
});
}
std::string parent = node.name;
if (flat)
if (!as_tree)
{
auto cur = &placement_tree->nodes.at(node.name);
while (cur->parent != "" && cur->parent != node.name)
@@ -270,7 +278,7 @@ resume_1:
{
auto & osd = placement_tree->osds.at(osd_num);
auto fmt = json11::Json::object{
{ "type", (flat ? "osd" : str_repeat(" ", indents[i]+1) + "osd") },
{ "type", (!as_tree ? "osd" : str_repeat(" ", indents[i]+1) + "osd") },
{ "name", osd.num },
{ "parent", parent },
{ "up", osd.up ? "up" : "down" },
@@ -300,7 +308,7 @@ resume_1:
}
}
json11::Json::array cols;
if (!flat)
if (as_tree)
{
cols.push_back(json11::Json::object{
{ "key", "type" },
@@ -309,9 +317,9 @@ resume_1:
}
cols.push_back(json11::Json::object{
{ "key", "name" },
{ "title", flat ? "OSD" : "NAME" },
{ "title", !as_tree ? "OSD" : "NAME" },
});
if (flat)
if (!as_tree)
{
cols.push_back(json11::Json::object{
{ "key", "parent" },
@@ -414,7 +422,7 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_osd_tree(json11::Json cfg)
auto osd_tree_printer = new osd_tree_printer_t();
osd_tree_printer->parent = this;
osd_tree_printer->cfg = cfg;
osd_tree_printer->flat = cfg["flat"].bool_value();
osd_tree_printer->as_tree = cfg["as_tree"].bool_value();
osd_tree_printer->show_stats = cfg["long"].bool_value();
return [osd_tree_printer](cli_result_t & result)
{
+8 -2
View File
@@ -282,10 +282,16 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_pg_list(json11::Json cfg)
{
auto pg_lister = new pg_lister_t();
pg_lister->parent = this;
if (cfg["pool"].uint64_value())
if (!cfg["pool"].is_null())
{
pg_lister->pool_id = cfg["pool"].uint64_value();
pg_lister->pool_name = pg_lister->pool_id ? "" : cfg["pool"].string_value();
}
else
pg_lister->pool_name = cfg["pool"].string_value();
{
pg_lister->pool_id = cfg["pool_id"].uint64_value();
pg_lister->pool_name = pg_lister->pool_id ? "" : cfg["pool_name"].string_value();
}
for (auto & st: cfg["pg_state"].array_items())
pg_lister->pg_state.push_back(st.string_value());
if (cfg["pg_state"].is_string())
+1 -1
View File
@@ -213,7 +213,7 @@ resume_3:
if (failure_domain != "osd")
pool_err += "\n- different parent '"+failure_domain+"' nodes";
result = (cli_result_t){
.err = EINVAL,
.err = EBUSY,
.text = pool_err,
};
state = 100;
+7 -6
View File
@@ -206,7 +206,7 @@ resume_1:
{ "space_efficiency", pool_stats[pool_cfg.id]["space_efficiency"].number_value() },
{ "pg_real_size", pool_stats[pool_cfg.id]["pg_real_size"].uint64_value() },
{ "osd_count", (uint64_t)pg_per_osd.size() },
{ "backfillfull", pool_cfg.backfillfull },
{ "backfillfull", !!pool_cfg.backfillfull },
};
}
// Include full pool config
@@ -546,6 +546,10 @@ resume_3:
{ "write_fmt", "Write" },
{ "delete_fmt", "Delete" },
};
if (sort_field == "osd_tags" || sort_field == "primary_affinity_tags")
{
sort_field += "_fmt";
}
auto list = to_list();
size_t title_len = 0;
for (auto & item: list)
@@ -666,15 +670,12 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_pool_ls(json11::Json cfg)
lister->show_stats = cfg["long"].bool_value();
lister->detailed = cfg["detail"].bool_value();
lister->sort_field = cfg["sort"].string_value();
if ((lister->sort_field == "osd_tags") ||
(lister->sort_field == "primary_affinity_tags" ))
lister->sort_field = lister->sort_field + "_fmt";
lister->reverse = cfg["reverse"].bool_value();
lister->max_count = cfg["count"].uint64_value();
if (cfg["names"].is_string())
lister->only_names.insert(cfg["names"].string_value());
for (auto & item: cfg["names"].array_items())
{
lister->only_names.insert(item.string_value());
}
return [lister](cli_result_t & result)
{
lister->loop();
+15 -10
View File
@@ -39,16 +39,21 @@ struct pool_changer_t
goto resume_1;
else if (state == 2)
goto resume_2;
pool_id = stoull_full(cfg["old_name"].string_value());
if (!pool_id)
if (!cfg["pool"].is_null())
{
pool_name = cfg["old_name"].string_value();
if (pool_name == "")
{
result = (cli_result_t){ .err = ENOENT, .text = "Pool ID or name is required to modify it" };
state = 100;
return;
}
pool_id = cfg["pool"].uint64_value();
pool_name = pool_id ? "" : cfg["pool"].string_value();
}
else
{
pool_id = cfg["id"].uint64_value();
pool_name = pool_id ? "" : cfg["old_name"].string_value();
}
if (!pool_id && pool_name == "")
{
result = (cli_result_t){ .err = ENOENT, .text = "Pool ID or name is required to modify it" };
state = 100;
return;
}
resume_0:
// Get pools from etcd
@@ -179,7 +184,7 @@ resume_2:
result = (cli_result_t){
.err = 0,
.text = "Pool "+pool_name+" updated",
.data = new_pools,
.data = new_pools[std::to_string(pool_id)],
};
state = 100;
}
+13 -3
View File
@@ -196,7 +196,9 @@ resume_3:
result = (cli_result_t){
.err = 0,
.text = "Pool "+pool_name+" deleted",
.data = new_pools
.data = json11::Json::object{
{"ok", true},
},
};
state = 100;
}
@@ -207,8 +209,16 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_pool_rm(json11::Json cfg)
auto pool_remover = new pool_remover_t();
pool_remover->parent = this;
pool_remover->pool_id = cfg["pool"].uint64_value();
pool_remover->pool_name = pool_remover->pool_id ? "" : cfg["pool"].as_string();
if (!cfg["pool"].is_null())
{
pool_remover->pool_id = cfg["pool"].uint64_value();
pool_remover->pool_name = pool_remover->pool_id ? "" : cfg["pool"].as_string();
}
else
{
pool_remover->pool_id = cfg["pool_id"].uint64_value();
pool_remover->pool_name = pool_remover->pool_id ? "" : cfg["pool_name"].as_string();
}
pool_remover->force = !cfg["force"].is_null();
+1 -1
View File
@@ -264,7 +264,7 @@ struct rm_inode_t
{
// Error
result = (cli_result_t){
.err = EIO,
.err = EBUSY,
.text = "Failed: some blocks were not removed",
.data = data,
};
+4
View File
@@ -181,6 +181,7 @@ struct rm_osd_t
if (dry_run || is_dataloss && !force_dataloss || is_warning && !force_warning)
{
result.err = is_dataloss && !force_dataloss || is_warning && !force_warning ? EBUSY : 0;
result.data = json11::Json::object{ { "pool_effects", pool_effects } };
state = 100;
return;
}
@@ -332,6 +333,9 @@ struct rm_osd_t
ids = (osd_ids.size() > 1 ? "OSDs " : "OSD ")+ids+(osd_ids.size() > 1 ? " are" : " is")+" removed from etcd";
state = 100;
result.text = (result.text != "" ? ids+"\n"+result.text : ids);
result.data = json11::Json::object {
{"deleted_osds", osd_ids},
};
result.err = 0;
}
+2
View File
@@ -195,6 +195,8 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_rm_wildcard(json11::Json c
auto wildcard_remover = new wildcard_remover_t();
wildcard_remover->parent = this;
wildcard_remover->cfg = cfg;
if (cfg["globs"].is_string())
wildcard_remover->globs.push_back(cfg["globs"].string_value());
for (auto & glob: cfg["globs"].array_items())
wildcard_remover->globs.push_back(glob.string_value());
wildcard_remover->exact = cfg["exact"].bool_value();
+439
View File
@@ -0,0 +1,439 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 (see README.md for details)
#include <signal.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <stdexcept>
#include "cli.h"
#include "cluster_client.h"
#include "epoll_manager.h"
#include "http_client.h"
#include "str_util.h"
#include "json_util.h"
#include "addr_util.h"
#include "openapi.json.h"
struct cli_serve_conn_t
{
int peer_fd = 0;
std::string peer_addr;
http_co_t *co = NULL;
cli_tool_t *p = NULL;
cli_result_t result;
bool keepalive = false;
bool closed = false;
timespec request_time;
std::string request_method;
std::string request_path;
std::string request_body;
std::string response_type;
std::function<bool(cli_result_t &)> action_cb;
};
struct cli_serve_path_t
{
std::string cmd;
bool allow_get;
};
// Serve vitastor-cli commands over HTTP in JSON format
struct cli_serve_t
{
std::map<std::string, cli_serve_path_t> cmd_paths = {
{"data/delete", {"rm-data", false}},
{"data/describe", {"describe", true}},
{"data/fix", {"fix", false}},
{"data/merge", {"merge-data", false}},
{"image/create", {"create", false}},
{"image/delete", {"rm", false}},
{"image/flatten", {"flatten", false}},
{"image/list", {"ls", true}},
{"image/modify", {"modify", false}},
{"osd/alloc", {"alloc-osd", false}},
{"osd/delete", {"rm-osd", false}},
{"osd/list", {"ls-osd", true}},
{"osd/modify", {"modify-osd", false}},
{"pg/list", {"ls-pgs", true}},
{"pool/create", {"create-pool", false}},
{"pool/delete", {"rm-pool", false}},
{"pool/list", {"pools", true}},
{"pool/modify", {"modify-pool", false}},
{"status", {"status", true}},
};
cli_tool_t *parent = NULL;
json11::Json options;
cli_result_t result;
bool log_body = false;
bool stop = false;
std::vector<std::string> bind_addresses;
int port = 0;
int listen_backlog = 0;
bool ssl = false;
std::vector<int> listen_fds;
http_context_t *http_ctx = NULL;
std::set<cli_serve_conn_t*> connections;
int state = 0;
bool is_done()
{
return state == 100;
}
void loop()
{
if (state == 1)
goto resume_1;
else if (state == 2)
goto resume_2;
else if (state == 100)
return;
if (options["bind_address"].is_string())
bind_addresses = explode(" ", options["bind_address"].string_value(), true);
else
bind_addresses.push_back("127.0.0.1");
port = options["port"].uint64_value();
if (!port)
port = 8080;
else if (port < 0 || port > 65535)
{
result = (cli_result_t){ .err = EINVAL, .text = "HTTP port can't be larger than 65536" };
state = 100;
return;
}
listen_backlog = options["listen_backlog"].uint64_value();
if (!listen_backlog)
listen_backlog = 128;
ssl = json_is_true(options["ssl"]);
if (ssl)
{
std::string ssl_cert = options["ssl_cert"].string_value();
std::string ssl_key = options["ssl_key"].string_value();
std::string ssl_ca = options["ssl_ca"].string_value();
std::string error;
http_ctx = http_context_init(ssl_cert, ssl_key, ssl_ca, ssl_ca != "", error);
if (error != "")
{
result = (cli_result_t){ .err = EINVAL, .text = error };
state = 100;
return;
}
}
for (auto & bind_address: bind_addresses)
{
int listen_fd = create_and_bind_socket(bind_address, port, listen_backlog, NULL);
fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL, 0) | O_NONBLOCK);
parent->epmgr->set_fd_handler(listen_fd, false, [this](int fd, int events)
{
accept_connections(fd);
});
listen_fds.push_back(listen_fd);
}
resume_1:
if (!stop)
{
state = 1;
return;
}
for (auto conn: connections)
{
http_close(conn->co);
}
resume_2:
// Wait for all connections to finish
if (connections.size() > 0)
{
state = 2;
return;
}
if (http_ctx)
{
http_context_destroy(http_ctx);
http_ctx = NULL;
}
state = 100;
}
void accept_connections(int listen_fd)
{
sockaddr_storage addr;
socklen_t peer_addr_size = sizeof(addr);
int peer_fd;
while ((peer_fd = accept(listen_fd, (sockaddr*)&addr, &peer_addr_size)) >= 0)
{
auto peer_addr_str = addr_to_string(addr);
assert(peer_fd != 0);
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("[%s.%03ju] New connection %d from %s\n", format_datetime(ts.tv_sec).c_str(), (uint64_t)ts.tv_nsec/1000000,
peer_fd, peer_addr_str.c_str());
fcntl(peer_fd, F_SETFL, fcntl(peer_fd, F_GETFL, 0) | O_NONBLOCK);
int one = 1;
setsockopt(peer_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
cli_serve_conn_t *conn = new cli_serve_conn_t;
conn->peer_fd = peer_fd;
conn->peer_addr = peer_addr_str;
conn->co = http_init(parent->epmgr->tfd, http_ctx);
http_serve(conn->co, peer_fd, (http_options_t){ .ssl = ssl }, [this, conn](http_message_t *msg)
{
process_request(conn, msg);
});
connections.insert(conn);
// Try to accept next connection
peer_addr_size = sizeof(addr);
}
if (peer_fd == -1 && errno != EAGAIN)
{
throw std::runtime_error(std::string("accept: ") + strerror(errno));
}
}
int map_to_http(int err, std::string *text)
{
int code = 0;
if (err == EINVAL)
{
code = 400;
if (text)
*text = "Bad Request";
}
else if (err == EOPNOTSUPP)
{
code = 404;
if (text)
*text = "Not Found";
}
else if (err == ENOSYS)
{
code = 405;
if (text)
*text = "Method Not Allowed";
}
else if (err == EAGAIN)
{
code = 409;
if (text)
*text = "Update Conflict";
}
else if (err == ENOTEMPTY || err == EEXIST || err == ENOENT || err == EBUSY)
{
code = 412;
if (text)
*text = "Precondition Failed";
}
else /*if (err == EIO || err == EBADF)*/
{
code = 500;
if (text)
*text = "Internal Server Error";
}
return code;
}
std::string cli_http_response(cli_serve_conn_t *conn)
{
timespec now;
clock_gettime(CLOCK_REALTIME, &now);
int code = 200;
std::string response;
if (conn->result.err)
{
std::string status_line;
code = map_to_http(conn->result.err, &status_line);
response = "HTTP/1.1 "+std::to_string(code)+" "+status_line+"\r\n";
}
else
{
response = "HTTP/1.1 200 OK\r\n";
}
response += (conn->keepalive
? "Connection: keep-alive\r\n"
: "Connection: close\r\n");
std::string body;
if (!conn->result.data.is_null())
{
response += "Content-Type: application/json\r\n";
body = conn->result.data.dump();
}
else
{
if (!conn->response_type.empty())
response += "Content-Type: "+conn->response_type+"\r\n";
else
response += "Content-Type: text/plain; charset=utf-8\r\n";
body = conn->result.text;
}
response += "Content-Length: "+std::to_string(body.size())+"\r\n\r\n";
response += body;
if (conn->request_method.find("\n") != std::string::npos)
conn->request_method = str_replace(conn->request_method, "\n", "%0a");
if (conn->request_method.find(" ") != std::string::npos)
conn->request_method = str_replace(conn->request_method, " ", "%20");
if (conn->request_path.find("\n") != std::string::npos)
conn->request_path = str_replace(conn->request_path, "\n", "%0a");
if (conn->request_path.find(" ") != std::string::npos)
conn->request_path = str_replace(conn->request_path, " ", "%20");
uint64_t response_time = (now.tv_sec-conn->request_time.tv_sec)*1000 + (now.tv_nsec-conn->request_time.tv_nsec)/1000000;
printf("[%s.%03ju] %s %s %s %d %.03f sec\n",
format_datetime(now.tv_sec).c_str(), (uint64_t)now.tv_nsec/1000000,
conn->peer_addr.c_str(), conn->request_method.c_str(), conn->request_path.c_str(), code,
response_time/1000.0);
if (log_body)
{
if (conn->request_body.find("\n") != std::string::npos)
conn->request_body = str_replace(conn->request_body, "\n", " ");
if (conn->request_body.size())
printf(" %s\n", conn->request_body.c_str());
printf(" %s\n", body.c_str());
}
return response;
}
void process_request(cli_serve_conn_t *conn, http_message_t *msg)
{
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
if (!msg->error.empty())
{
// connection is closed
fprintf(stderr, "[%s.%03ju] Connection %d closed: %s\n", format_datetime(ts.tv_sec).c_str(),
(uint64_t)ts.tv_nsec/1000000, conn->peer_fd, msg->error.c_str());
if (conn->p)
conn->closed = true;
else
{
connections.erase(conn);
http_destroy(conn->co);
delete conn;
}
return;
}
conn->keepalive = msg->headers.find("connection") != msg->headers.end() &&
msg->headers.at("connection") == "keep-alive";
conn->p = new cli_tool_t;
conn->p->iodepth = parent->iodepth;
conn->p->parallel_osds = parent->parallel_osds;
conn->p->json_output = true;
conn->p->ringloop = parent->ringloop;
conn->p->epmgr = parent->epmgr;
conn->p->cli = parent->cli;
conn->p->is_command_line = false;
// Parse request
auto req_line = explode(" ", msg->status_line, true);
if (req_line.size() < 2)
{
if (req_line[0] == "")
req_line[0] = "-";
req_line.push_back("-");
}
conn->request_time = ts;
conn->request_method = std::move(req_line[0]);
conn->request_path = std::move(req_line[1]);
conn->request_body = std::move(msg->body);
conn->response_type = "";
auto ctype = msg->headers["content-type"];
if (conn->request_method != "GET" && conn->request_method != "POST")
{
conn->result = { .err = ENOSYS, .text = "Unsupported request method "+conn->request_method };
}
else if (ctype != (conn->request_method == "GET" ? "" : "application/json"))
{
conn->result = { .err = EINVAL, .text = "Unsupported Content-Type: "+ctype+" for "+conn->request_method+" requests" };
}
else
{
auto uri = explode("?", conn->request_path, true);
uri[0] = trim(uri[0], "/");
auto cmd_it = cmd_paths.find(uri[0]);
if (uri[0] == "")
{
std::string text = "Supported APIs:\n\n- GET /openapi\n";
for (auto & pp: cmd_paths)
{
text += (pp.second.allow_get ? "- GET" : "- POST") + (" /" + pp.first) + "\n";
}
conn->result = { .text = text };
}
else if (uri[0] == "openapi")
{
conn->response_type = "application/json";
conn->result = { .text = openapi_description };
}
else if (cmd_it == cmd_paths.end())
{
conn->result = { .err = EOPNOTSUPP, .text = "unknown command: "+uri[0] };
}
else if (conn->request_method == "GET" && !cmd_it->second.allow_get)
{
conn->result = { .err = ENOSYS, .text = "method /"+uri[0]+" only allows POST requests" };
}
else
{
std::string error;
json11::Json::object cfg;
if (conn->request_method == "POST")
{
cfg = json11::Json::parse(conn->request_body, error).object_items();
}
else
{
// Parse URI
cfg = parse_uri_params(uri[1]);
}
if (error != "")
{
conn->result = { .err = EINVAL, .text = "Invalid JSON in body: "+error };
}
else
{
cfg["command"] = json11::Json::array{cmd_it->second.cmd};
conn->p->parse_api_opts(cfg);
conn->action_cb = conn->p->start(cfg, conn->result);
}
}
}
if (!conn->action_cb)
{
http_reply(conn->co, cli_http_response(conn));
delete conn->p;
conn->p = NULL;
return;
}
conn->p->loop_and_wait(conn->action_cb, [this, conn](const cli_result_t & r)
{
conn->result = r;
conn->action_cb = NULL;
delete conn->p;
conn->p = NULL;
if (!conn->closed)
http_reply(conn->co, cli_http_response(conn));
else
{
connections.erase(conn);
http_destroy(conn->co);
delete conn;
}
});
}
};
std::function<bool(cli_result_t &)> cli_tool_t::start_serve(json11::Json cfg)
{
auto server = new cli_serve_t();
server->parent = this;
server->options = cfg;
return [server](cli_result_t & result)
{
server->loop();
if (server->is_done())
{
result = server->result;
delete server;
return true;
}
return false;
};
}
+6 -4
View File
@@ -18,6 +18,7 @@ struct status_printer_t
{
cli_tool_t *parent;
cli_result_t result;
int state = 0;
json11::Json::array mon_members;
json11::Json agg_stats;
@@ -89,7 +90,7 @@ resume_2:
return;
if (parent->etcd_err.err)
{
fprintf(stderr, "%s\n", parent->etcd_err.text.c_str());
result = parent->etcd_err;
state = 100;
return;
}
@@ -107,7 +108,8 @@ resume_2:
if (etcd_states[i]["error"].is_null())
{
etcd_alive++;
etcd_db_size = etcd_states[i]["dbSize"].uint64_value();
uint64_t db_size = etcd_states[i]["dbSize"].uint64_value();
etcd_db_size = db_size > etcd_db_size ? db_size : etcd_db_size;
}
}
int mon_count = 0;
@@ -265,7 +267,7 @@ resume_2:
obj_n = agg_stats["object_counts"][str].uint64_value() * parent->cli->st_cli.global_block_size;
json_status[str+"_data"] = obj_n;
}
printf("%s\n", json11::Json(json_status).dump().c_str());
result.data = json11::Json(json_status);
state = 100;
return;
}
@@ -406,7 +408,7 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_status(json11::Json cfg)
printer->loop();
if (printer->is_done())
{
result = { .err = 0 };
result = printer->result;
delete printer;
return true;
}
+937
View File
@@ -0,0 +1,937 @@
{
"openapi": "3.0.0",
"info": {
"description": "Vitastor control API",
"version": "3.0.2",
"license": {
"name": "VNPL 1.1"
}
},
"paths": {
"/status": { "get": {
"summary": "Show server status",
"operationId": "status",
"produces": [ "application/json" ],
"parameters": [],
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"pool_count": { "type": "integer", "format": "uint64", "description": "Total number of pools" },
"active_pool_count": { "type": "integer", "format": "uint64", "description": "Number of active pools" },
"backfillfull_pools": { "type": "array", "items": { "type": "integer", "format": "uint64" }, "description": "ID(s) of backfillfull pools" },
"clean_data": { "type": "integer", "format": "uint64", "description": "Clean data - user size in bytes" },
"misplaced_data": { "type": "integer", "format": "uint64", "description": "Misplaced data - user size in bytes" },
"degraded_data": { "type": "integer", "format": "uint64", "description": "Degraded data - user size in bytes" },
"incomplete_data": { "type": "integer", "format": "uint64", "description": "Incomplete data - user size in bytes" },
"osd_count": { "type": "integer", "format": "uint64", "description": "Total number of OSDs" },
"osd_up": { "type": "integer", "format": "uint64", "description": "Number of active OSDs" },
"osds_full": { "type": "integer", "format": "uint64", "description": "Number of full OSDs" },
"osds_nearfull": { "type": "integer", "format": "uint64", "description": "Number of nearfull OSDs" },
"osds_primary_slow_ops": { "type": "array", "items": { "type": "integer", "format": "uint64" }, "description": "Numbers of OSDs with slow/hung client operations" },
"osds_secondary_slow_ops": { "type": "array", "items": { "type": "integer", "format": "uint64" }, "description": "Numbers of OSDs with slow/hung storage operations" },
"total_raw": { "type": "integer", "format": "uint64", "description": "Total size of all OSDs in bytes" },
"free_raw": { "type": "integer", "format": "uint64", "description": "Total free space on OSDs in bytes" },
"down_raw": { "type": "integer", "format": "uint64", "description": "Total size of stopped OSDs in bytes" },
"free_down_raw": { "type": "integer", "format": "uint64", "description": "Total free space on stopped OSDs in bytes" },
"etcd_count": { "type": "integer", "format": "uint64", "description": "Total number of etcd endpoints in the config" },
"etcd_alive": { "type": "integer", "format": "uint64", "description": "Number of healthy etcd endpoints" },
"etcd_db_size": { "type": "integer", "format": "uint64", "description": "Estimated maximum size of etcd databases (dbSize)" },
"mon_count": { "type": "integer", "format": "uint64", "description": "Number of active monitors" },
"mon_master": { "type": "string", "description": "Name of the host with active monitor" },
"readonly": { "type": "boolean", "description": "readonly flag" },
"no_rebalance": { "type": "boolean", "description": "no_rebalance flag" },
"no_recovery": { "type": "boolean", "description": "no_recovery flag" },
"no_scrub": { "type": "boolean", "description": "no_scrub flag" },
"object_counts": { "type": "object", "properties": {
"object": { "type": "integer", "format": "uint64", "description": "Total number of objects" },
"clean": { "type": "integer", "format": "uint64", "description": "Number of clean objects" },
"degraded": { "type": "integer", "format": "uint64", "description": "Number of degraded objects" },
"incomplete": { "type": "integer", "format": "uint64", "description": "Number of incomplete objects" },
"misplaced": { "type": "integer", "format": "uint64", "description": "Number of misplaced objects" }
} },
"pg_states": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Number of PGs by state" },
"op_stats": { "$ref": "#/components/schemas/OpStats" },
"recovery_stats": { "$ref": "#/components/schemas/RecoveryStats" }
}
} } } }
}
} },
"/image/list": { "get": {
"summary": "List images",
"operationId": "imageListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
"schema": { "$ref": "#/components/schemas/ImageListParams" },
"style": "form",
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageList" } } } }
}
}, "post": {
"summary": "List images",
"operationId": "imageListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImageList" } } } }
}
} },
"/image/create": { "post": {
"summary": "Create an image, snapshot, or a clone",
"operationId": "imageCreate",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"required": [ "image" ],
"properties": {
"image": { "type": "string", "description": "Image name" },
"snapshot": { "type": "string", "description": "Snapshot name" },
"size": {
"oneOf": [
{ "type": "integer", "format": "uint64" },
{ "type": "string", "pattern": "^\\d+[KMGT]$" }
],
"description": "Image size in bytes or with K/M/G/T suffix (not for snapshots)"
},
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID for the new image/snapshot" },
"pool_name": { "type": "string", "description": "Pool name for the new image/snapshot" },
"parent": { "type": "string", "description": "Create a clone with this parent image name" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/image/modify": { "post": {
"summary": "Modify an image",
"operationId": "imageModify",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"required": [ "image" ],
"properties": {
"image": { "type": "string", "description": "Old full name of the image" },
"rename": { "type": "string", "description": "New full image name" },
"resize": {
"oneOf": [
{ "type": "integer", "format": "uint64" },
{ "type": "string", "pattern": "^\\d+[KMGT]$" }
],
"description": "New image size in bytes or with K/M/G/T suffix"
},
"readonly": { "type": "boolean", "description": "Make the image read-only" },
"readwrite": { "type": "boolean", "description": "Make the image read-write" },
"deleted": { "type": "boolean", "description": "Set or clear the 'deleted' flag" },
"force": { "type": "boolean", "description": "Proceed with shrinking or setting readwrite flag even if the image has children" },
"down_ok": { "type": "boolean", "description": "Proceed with shrinking even if some data will be left on unavailable OSDs" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Image" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/image/delete": { "post": {
"summary": "Delete image(s)",
"operationId": "imageDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"oneOf": [ {
"type": "object",
"required": [ "names" ],
"properties": {
"names": { "type": "array", "items": { "type": "string" }, "description": "Exact image name list to delete" },
"writers_stopped": { "type": "boolean", "description": "Allow 'inverse' snapshot deletion optimisation" },
"down_ok": { "type": "boolean", "description": "Continue deletion/merging even if some data will be left on unavailable OSDs" }
}
}, {
"type": "object",
"required": [ "matching" ],
"properties": {
"matching": { "type": "array", "items": { "type": "string" }, "description": "Glob image name patterns to delete" },
"writers_stopped": { "type": "boolean", "description": "Allow 'inverse' snapshot deletion optimisation" },
"down_ok": { "type": "boolean", "description": "Continue deletion/merging even if some data will be left on unavailable OSDs" }
}
}, {
"type": "object",
"required": [ "from" ],
"properties": {
"from": { "type": "string", "description": "Parent image in a parent-child sequence to delete" },
"to": { "type": "string", "description": "Child image in a parent-child sequence to delete" },
"writers_stopped": { "type": "boolean", "description": "Allow 'inverse' snapshot deletion optimisation" },
"down_ok": { "type": "boolean", "description": "Continue deletion/merging even if some data will be left on unavailable OSDs" }
}
} ]
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"deleted_ids": { "type": "array", "items": { "type": "integer", "format": "uint64" }, "description": "Full inode ID(s) of deleted images" },
"deleted_images": { "type": "array", "items": { "type": "string" }, "description": "Deleted image name(s)" },
"rebased_images": { "type": "array", "items": { "type": "string" }, "description": "Rebased child image name(s)" },
"renamed_from": { "type": "string", "description": "Name of the renamed snapshot when 'inverse' deletion is enabled" },
"renamed_to": { "type": "string", "description": "Name of the rename destination when 'inverse' deletion is enabled" }
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/image/flatten": { "post": {
"summary": "Flatten an image",
"operationId": "imageFlatten",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"required": [ "names" ],
"properties": {
"image": { "type": "string", "description": "Image name to flatten" }
}
} } } },
"responses": {
"200": { "content": { "text/plain": { "schema": { "type": "string", "description": "Empty response" } } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/osd/list": { "get": {
"summary": "List OSDs",
"operationId": "osdListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
"schema": { "$ref": "#/components/schemas/OsdListParams" },
"style": "form",
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdList" } } } }
}
}, "post": {
"summary": "List OSDs",
"operationId": "osdListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OsdList" } } } }
}
} },
"/osd/alloc": { "post": {
"summary": "Allocate a new OSD number",
"operationId": "osdAlloc",
"produces": [ "application/json" ],
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "integer",
"format": "uint64",
"description": "New OSD number"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/osd/delete": { "post": {
"summary": "Delete stopped OSD(s) from etcd",
"operationId": "osdDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"force": { "type": "boolean", "description": "Allow to remove non-empty OSDs" },
"allow_data_loss": { "type": "boolean", "description": "Allow to remove non-empty OSDs even if it will lead to data loss" },
"dry_run": { "type": "boolean", "description": "Only check if the removal is possible but do not delete" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"deleted_osds": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Deleted OSD numbers"
}
},
"format": "uint64",
"description": "New OSD number"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"pool_effects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"pool_id": { "type": "integer", "format": "uint64", "description": "Affected pool ID" },
"pool_name": { "type": "string", "description": "Affected pool name" },
"effect": {
"type": "string",
"enum": [ "incomplete", "has_incomplete", "offline", "degraded" ],
"description": "Possible pool PG state after removing OSD(s)"
}
}
}
}
}
} } } }
}
} },
"/osd/modify": { "post": {
"summary": "Modify OSD reweight, tags or noout flag",
"operationId": "osdDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"osd_num": { "type": "integer", "format": "uint64", "description": "OSD number to modify" },
"reweight": { "type": "number", "minimum": 0, "maximum": 1, "description": "New OSD weight" },
"tags": { "type": "array", "items": { "type": "string" }, "description": "New OSD tags" },
"noout": { "type": "boolean", "description": "New value of the noout flag" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"reweight": { "type": "number", "minimum": 0, "maximum": 1, "description": "OSD weight" },
"tags": { "type": "array", "items": { "type": "string" }, "description": "OSD tags" },
"noout": { "type": "boolean", "description": "noout flag" }
},
"format": "uint64",
"description": "New OSD number"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/pool/list": { "get": {
"summary": "List pools",
"operationId": "poolListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
"schema": { "$ref": "#/components/schemas/PoolListParams" },
"style": "form",
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolList" } } } }
}
}, "post": {
"summary": "List pools",
"operationId": "poolListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PoolList" } } } }
}
} },
"/pool/create": { "post": {
"summary": "Create a pool",
"operationId": "poolCreate",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"allOf": [ {
"$ref": "#/components/schemas/PoolConfig"
}, { "type": "object", "properties": {
"wait": { "type": "boolean", "description": "Wait for the new pool to come online" },
"force": { "type": "boolean", "description": "Do not check that cluster has enough OSDs to create the pool" }
} } ]
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"$ref": "#/components/schemas/PoolConfig"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/pool/modify": { "post": {
"summary": "Modify a pool",
"operationId": "poolModify",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"allOf": [ {
"$ref": "#/components/schemas/PoolConfig"
}, { "type": "object", "properties": {
"id": { "type": "integer", "format": "uint64", "description": "Pool ID to modify" },
"old_name": { "type": "string", "description": "Pool name to modify" },
"force": { "type": "boolean", "description": "Allow modifications which may lead to data loss" }
} } ]
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"$ref": "#/components/schemas/PoolConfig"
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/pool/delete": { "post": {
"summary": "Delete an pool",
"operationId": "poolDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID to delete" },
"pool_name": { "type": "string", "description": "Pool name to delete" },
"force": { "type": "boolean", "description": "Allow to delete non-empty pools" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": { "ok": { "type": "boolean" } }
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"409": { "$ref": "#/components/responses/UpdateConflict" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} },
"/pg/list": { "get": {
"summary": "List PGs",
"operationId": "pgListGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
"schema": { "$ref": "#/components/schemas/PgListParams" },
"style": "form",
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgList" } } } }
}
}, "post": {
"summary": "List PGs",
"operationId": "pgListPost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgListParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PgList" } } } }
}
} },
"/data/delete": { "post": {
"summary": "Remove inode data without changing metadata",
"operationId": "dataDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"pool": { "type": "integer", "format": "uint64", "description": "Pool ID" },
"inode": { "type": "integer", "format": "uint64", "description": "Inode number" },
"min_offset": { "type": "integer", "format": "uint64", "description": "Purge only data starting with specified offset" },
"max_offset": { "type": "integer", "format": "uint64", "description": "Purge only data before specified offset" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"removed_objects": { "type": "integer", "format": "uint64", "description": "Removed object count" }
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"412": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"total_objects": { "type": "integer", "format": "uint64", "description": "Total object count" },
"removed_objects": { "type": "integer", "format": "uint64", "description": "Removed object count" },
"error_count": { "type": "integer", "format": "uint64", "description": "Error count" },
"inactive_osds": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Inactive OSDs possibly with some non-removed data left"
},
"inactive_pgs": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Inactive PGs possibly with some non-removed data left"
}
}
} } } }
}
} },
"/data/describe": { "get": {
"summary": "Describe unclean object locations in the cluster",
"operationId": "dataDescribeGet",
"produces": [ "application/json" ],
"parameters": [ {
"name": "params",
"in": "query",
"schema": { "$ref": "#/components/schemas/DataDescribeParams" },
"style": "form",
"explode": true
} ],
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribe" } } } }
}
}, "post": {
"summary": "Describe unclean object locations in the cluster",
"operationId": "dataDescribePost",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribeParams" } } } },
"responses": {
"200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DataDescribe" } } } }
}
} },
"/data/fix": { "post": {
"summary": "Fix inconsistent objects in the cluster by deleting some copies",
"operationId": "dataDelete",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"objects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"inode": { "type": "integer", "format": "uint64", "description": "Full inode ID with pool" },
"stripe": { "type": "integer", "format": "uint64", "description": "Object offset within inode" }
}
}
},
"bad_osds": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Remove inconsistent copies/parts of objects from these OSDs"
},
"part": { "type": "integer", "minimum": 0, "maximum": 255, "description": "Only remove EC object part with this number" },
"check": { "type": "boolean", "default": true, "description": "Do safety checks before removing parts" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"inode": { "type": "integer", "format": "uint64", "description": "Full inode ID with pool" },
"stripe": { "type": "integer", "format": "uint64", "description": "Object offset within inode" },
"part": { "type": "integer", "minimum": 0, "maximum": 255, "description": "Object part number" },
"osd_num": { "type": "integer", "format": "uint64", "description": "OSD number with this part" }
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" }
}
} },
"/data/merge": { "post": {
"summary": "Merge layer data without changing metadata",
"operationId": "dataMerge",
"consumes": [ "application/json" ],
"produces": [ "application/json" ],
"requestBody": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"from": { "type": "string", "description": "First parent layer to merge" },
"to": { "type": "string", "description": "Last child layer to merge" },
"target": { "type": "string", "description": "Merge target (must be between from and to)" },
"delete_source": { "type": "boolean", "description": "Delete source data after merging" },
"fsync_interval": { "type": "integer", "format": "uint64", "description": "Fsync every this number of writes during merge" },
"cas": { "type": "boolean", "description": "Use CAS writes to allow online merging" }
}
} } } },
"responses": {
"200": { "content": { "application/json": { "schema": {
"type": "object",
"properties": {
"from": { "type": "string", "description": "First merged parent layer" },
"to": { "type": "string", "description": "Last merged child layer" },
"target": { "type": "string", "description": "Merge target" }
}
} } } },
"400": { "$ref": "#/components/responses/Invalid" },
"412": { "$ref": "#/components/responses/PreconditionFailed" }
}
} }
},
"components": {
"responses": {
"Invalid": {
"description": "Invalid Arguments (EINVAL)",
"content": {
"text/plain": {
"schema": { "type": "string", "description": "Error text" }
}
}
},
"PreconditionFailed": {
"description": "Precondition Failed (ENOTEMPTY, EEXIST or ENOENT)",
"content": {
"text/plain": {
"schema": { "type": "string", "description": "Error text" }
}
}
},
"UpdateConflict": {
"description": "Update Conflict",
"content": {
"text/plain": {
"schema": { "type": "string", "description": "Error text" }
}
}
}
},
"schemas": {
"OpStat": {
"type": "object",
"properties": {
"bytes": { "type": "integer", "format": "uint64", "description": "Total bytes processed" },
"count": { "type": "integer", "format": "uint64", "description": "Number of processed operations" },
"usec": { "type": "integer", "format": "uint64", "description": "Total operation processing time in microseconds" },
"bps": { "type": "integer", "format": "uint64", "description": "Bytes per second (average since previous report)" },
"iops": { "type": "integer", "format": "uint64", "description": "Operations per second (average since previous report)" },
"lat": { "type": "integer", "format": "uint64", "description": "Operation latency (average since previous report)" }
}
},
"SubOpStat": {
"type": "object",
"properties": {
"count": { "type": "integer", "format": "uint64", "description": "Number of processed operations" },
"usec": { "type": "integer", "format": "uint64", "description": "Total operation processing time in microseconds" },
"iops": { "type": "integer", "format": "uint64", "description": "Operations per second (average since previous report)" },
"lat": { "type": "integer", "format": "uint64", "description": "Operation latency (average since previous report)" }
}
},
"RecoveryStat": {
"type": "object",
"properties": {
"bytes": { "type": "integer", "format": "uint64", "description": "Total bytes processed" },
"count": { "type": "integer", "format": "uint64", "description": "Number of processed operations" },
"bps": { "type": "integer", "format": "uint64", "description": "Bytes per second (average since previous report)" },
"iops": { "type": "integer", "format": "uint64", "description": "Operations per second (average since previous report)" }
}
},
"RecoveryStats": {
"description": "Recovery statistics",
"type": "object",
"properties": {
"misplaced": { "$ref": "#/components/schemas/RecoveryStat" },
"degraded": { "$ref": "#/components/schemas/RecoveryStat" }
}
},
"OpStats": {
"description": "Operation statistics (processing time without client RTT)",
"type": "object",
"properties": {
"read": { "$ref": "#/components/schemas/OpStat" },
"write": { "$ref": "#/components/schemas/OpStat" },
"write_stable": { "$ref": "#/components/schemas/OpStat" },
"sync": { "$ref": "#/components/schemas/OpStat" },
"stabilize": { "$ref": "#/components/schemas/OpStat" },
"rollback": { "$ref": "#/components/schemas/OpStat" },
"delete": { "$ref": "#/components/schemas/OpStat" },
"list": { "$ref": "#/components/schemas/OpStat" },
"show_config": { "$ref": "#/components/schemas/OpStat" },
"primary_read": { "$ref": "#/components/schemas/OpStat" },
"primary_write": { "$ref": "#/components/schemas/OpStat" },
"primary_sync": { "$ref": "#/components/schemas/OpStat" },
"primary_delete": { "$ref": "#/components/schemas/OpStat" },
"ping": { "$ref": "#/components/schemas/OpStat" },
"sec_read_bmp": { "$ref": "#/components/schemas/OpStat" },
"scrub": { "$ref": "#/components/schemas/OpStat" },
"describe": { "$ref": "#/components/schemas/OpStat" },
"sec_lock": { "$ref": "#/components/schemas/OpStat" }
}
},
"SubOpStats": {
"description": "Suboperation statistics (processing+RTT to secondary as primary sees it)",
"type": "object",
"properties": {
"read": { "$ref": "#/components/schemas/SubOpStat" },
"write": { "$ref": "#/components/schemas/SubOpStat" },
"write_stable": { "$ref": "#/components/schemas/SubOpStat" },
"sync": { "$ref": "#/components/schemas/SubOpStat" },
"stabilize": { "$ref": "#/components/schemas/SubOpStat" },
"rollback": { "$ref": "#/components/schemas/SubOpStat" },
"delete": { "$ref": "#/components/schemas/SubOpStat" },
"list": { "$ref": "#/components/schemas/SubOpStat" },
"show_config": { "$ref": "#/components/schemas/SubOpStat" },
"ping": { "$ref": "#/components/schemas/SubOpStat" },
"sec_read_bmp": { "$ref": "#/components/schemas/SubOpStat" },
"sec_lock": { "$ref": "#/components/schemas/SubOpStat" }
}
},
"ImageListParams": {
"type": "object",
"properties": {
"globs": { "type": "array", "items": { "type": "string" }, "description": "Glob patterns or exact name matches" },
"exact": { "type": "boolean", "description": "Treat globs as exact name matches, not as patterns" },
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID to list" },
"pool_name": { "type": "string", "description": "Pool name to list" },
"long": { "type": "boolean", "description": "Include allocated size and I/O statistics" },
"sort": {
"type": "string",
"description": "Sort by specified field",
"enum": [
"name", "size", "used_size",
"read_iops", "read_bps", "read_lat", "read_queue",
"write_iops", "write_bps", "write_lat", "write_queue",
"delete_iops", "delete_bps", "delete_lat", "delete_queue"
]
},
"reverse": { "type": "boolean", "description": "Sort in descending order" },
"count": { "type": "integer", "format": "uint64", "description": "Only list first N items" }
}
},
"Image": {
"type": "object",
"properties": {
"inode_id": { "type": "integer", "format": "uint64", "description": "Full inode ID with pool" },
"inode_num": { "type": "integer", "format": "uint64", "description": "Inode number without pool" },
"name": { "type": "string", "description": "Image name" },
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID" },
"pool_name": { "type": "string", "description": "Pool name" },
"parent_name": { "type": "string", "description": "Name of the parent image (layer)" },
"parent_inode_id": { "type": "integer", "format": "uint64", "description": "Full ID of the parent inode" },
"parent_inode_num": { "type": "integer", "format": "uint64", "description": "Parent inode number without pool" },
"parent_pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID of the parent inode" },
"size": { "type": "integer", "format": "uint64", "description": "Image size in bytes" },
"used_size": { "type": "integer", "format": "uint64", "description": "Used space in bytes" },
"readonly": { "type": "boolean", "description": "Readonly flag" },
"deleted": { "type": "boolean", "description": "Deleted flag" }
}
},
"ImageList": {
"type": "array",
"items": { "$ref": "#/components/schemas/Image" }
},
"OsdListParams": {
"type": "object",
"properties": {
"as_tree": { "type": "boolean", "description": "Include all placement tree nodes, not just OSDs" },
"long": { "type": "boolean", "description": "Include per-OSD I/O statistics" }
}
},
"OsdList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "OSD number" },
"type": { "type": "string", "description": "Placement tree node type (osd for OSDs)" },
"up": { "type": "boolean", "description": "Whether the OSD is active" },
"parent": { "type": "string", "description": "Parent placement tree node name (host name, etc)" },
"size": { "type": "integer", "format": "uint64", "description": "OSD size in bytes" },
"free": { "type": "integer", "format": "uint64", "description": "Free space on the OSD in bytes" },
"reweight": { "type": "number", "minimum": 0, "maximum": 1, "description": "OSD weight in data distribution" },
"tags": { "type": "array", "items": { "type": "string" }, "description": "OSD tags" },
"data_block_size": { "type": "integer", "format": "uint64" },
"bitmap_granularity": { "type": "integer", "format": "uint64" },
"immediate_commit": { "type": "string" },
"noout": { "type": "boolean", "description": "noout flag" },
"slow_ops_primary": { "type": "integer", "format": "uint64", "description": "Number of slow/hung client operations on this OSD" },
"slow_ops_secondary": { "type": "integer", "format": "uint64", "description": "Number of slow/hung storage operations on this OSD" },
"op_stats": { "$ref": "#/components/schemas/OpStats" },
"subop_stats": { "$ref": "#/components/schemas/SubOpStats" },
"recovery_stats": { "$ref": "#/components/schemas/RecoveryStats" }
}
}
},
"PoolListParams": {
"type": "object",
"properties": {
"globs": { "type": "array", "items": { "type": "string" }, "description": "Glob patterns to restrict listing" },
"long": { "type": "boolean", "description": "Include per-pool I/O statistics" },
"sort": { "type": "string", "description": "Sort by specified field" },
"reverse": { "type": "boolean", "description": "Sort in descending order" },
"count": { "type": "integer", "format": "uint64", "description": "Only list first N items" }
}
},
"PoolConfig": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Pool name" },
"used_for_app": { "type": "string", "description": "Application name pool is used for" },
"scheme": { "type": "string", "enum": [ "replicated", "ec", "xor" ], "description": "Redundancy scheme" },
"pg_count": { "type": "integer", "format": "uint64", "description": "Configured PG count" },
"pg_size": { "type": "integer", "minimum": 1, "maximum": 255, "description": "Number of replicas or total data and parity EC chunks" },
"pg_minsize": { "type": "integer", "minimum": 1, "maximum": 255, "description": "Minimum active OSD count in a PG to allow activity" },
"parity_chunks": { "type": "integer", "minimum": 1, "maximum": 255, "description": "Parity chunk count for EC" },
"local_reads": { "type": "string", "enum": [ "primary", "nearest", "random" ], "description": "Local read policy" },
"root_node": { "type": "string", "description": "Root placement tree node for pool OSDs" },
"pg_stripe_size": { "type": "integer", "format": "uint64", "description": "Stripe size to map to one PG in bytes" },
"failure_domain": { "type": "string", "description": "Failure domain" },
"osd_tags": { "type": "array", "items": { "type": "string" }, "description": "Put pool only on OSDs tagged with all specified tags" },
"primary_affinity_tags": { "type": "array", "items": { "type": "string" }, "description": "Tags to prefer for primary OSDs" },
"block_size": { "type": "integer", "format": "uint64", "description": "Put pool only on OSDs with this data block size" },
"bitmap_granularity": { "type": "integer", "format": "uint64", "description": "Put pool only on OSDs with this logical sector size" },
"immediate_commit": { "type": "string", "enum": [ "none", "small", "all" ], "description": "Put pool only on OSDs with this or larger immediate_commit" },
"scrub_interval": { "type": "string", "pattern": "^\\d+[smhdMy]$", "description": "Automatic scrub interval" },
"level_placement": { "type": "string", "description": "Additional failure domain rules" },
"raw_placement": { "type": "string", "description": "Raw PG generation rules" },
"max_osd_combinations": { "type": "integer", "format": "uint64", "description": "Maximum number of random combinations during PG generation" }
}
},
"PoolList": {
"type": "array",
"items": {
"type": "object",
"allOf": [ {
"$ref": "#/components/schemas/PoolConfig"
}, { "type": "object", "properties": {
"id": { "type": "integer", "format": "uint64", "description": "Pool ID" },
"status": {
"type": "string",
"enum": [ "active", "has_misplaced", "has_degraded", "degraded", "has_incomplete", "incomplete", "inactive" ],
"description": "Pool status (worst status of pool PGs)"
},
"real_pg_count": { "type": "integer", "format": "uint64", "description": "Currently active PG count" },
"total_raw": { "type": "integer", "format": "uint64", "description": "Total raw space on all pool OSDs" },
"used_raw": { "type": "integer", "format": "uint64", "description": "Raw OSD space used by the pool data" },
"max_available": { "type": "integer", "format": "uint64", "description": "Maximum available user space in the pool" },
"raw_to_usable": { "type": "number", "minimum": 1, "description": "Raw to user space ratio" },
"space_efficiency": { "type": "number", "minimum": 0, "maximum": 1, "description": "Space efficiency (1 - wasted due to unbalanced PGs)" },
"pg_real_size": { "type": "integer", "format": "uint64", "description": "Minimum of PG size and available failure domain count" },
"osd_count": { "type": "integer", "format": "uint64", "description": "Number of OSDs used for the pool" },
"backfillfull": { "type": "boolean", "description": "Backfillfull (is rebalance stopped due to low space)?" },
"misplaced_count": { "type": "integer", "format": "uint64", "description": "Misplaced object count" },
"degraded_count": { "type": "integer", "format": "uint64", "description": "Degraded object count" },
"incomplete_count": { "type": "integer", "format": "uint64", "description": "Incomplete object count" },
"object_count": { "type": "integer", "format": "uint64", "description": "Total object count" },
"read_bps": { "type": "integer", "format": "uint64", "description": "Read bytes per second" },
"read_iops": { "type": "integer", "format": "uint64", "description": "Read operations per second" },
"read_lat": { "type": "integer", "format": "uint64", "description": "Average read latency" },
"write_bps": { "type": "integer", "format": "uint64", "description": "Write bytes per second" },
"write_iops": { "type": "integer", "format": "uint64", "description": "Write operations per second" },
"write_lat": { "type": "integer", "format": "uint64", "description": "Average write latency" },
"delete_bps": { "type": "integer", "format": "uint64", "description": "Delete bytes per second" },
"delete_iops": { "type": "integer", "format": "uint64", "description": "Delete operations per second" },
"delete_lat": { "type": "integer", "format": "uint64", "description": "Average delete latency" }
} } ]
}
},
"PgListParams": {
"type": "object",
"properties": {
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID" },
"pool_name": { "type": "string", "description": "Pool name" },
"pg_state": {
"type": "array",
"items": { "type": "string" },
"description": "PG state combinations (OR) separated by + (AND) and possibly with ! for negation"
},
"min": { "type": "integer", "format": "uint64", "description": "First PG number to show" },
"max": { "type": "integer", "format": "uint64", "description": "Last PG number to show" },
"osd": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Only return PGs with data on one of these OSDs"
}
}
},
"PgList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID" },
"pool_name": { "type": "string", "description": "Pool name" },
"pg_num": { "type": "integer", "format": "uint64", "description": "PG number" },
"target_set": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Target OSD set for this PG"
},
"target_primary": { "type": "integer", "format": "uint64", "description": "Target primary OSD of this PG (0 = no primary)" },
"epoch": { "type": "integer", "format": "uint64", "description": "PG epoch number" },
"next_scrub": { "type": "integer", "format": "uint64", "description": "UNIX timestamp of the next planned scrub" },
"target_history": {
"type": "array",
"items": { "type": "array", "items": { "type": "integer", "format": "uint64" } },
"description": "Other OSD sets of this PG which possibly still have some data left"
},
"pause": { "type": "boolean", "description": "Pause PG control flag from monitor" },
"cur_primary": { "type": "integer", "format": "uint64", "description": "Current primary OSD of this PG (0 = no primary)" },
"state": {
"type": "array",
"items": { "type": "string", "enum": [
"starting", "peering", "incomplete", "active", "repeering", "stopping", "offline",
"degraded", "has_inconsistent", "has_corrupted", "has_incomplete", "has_degraded",
"has_misplaced", "has_unclean", "has_invalid", "left_on_dead", "scrubbing"
] },
"description": "PG state"
},
"all_peers": {
"type": "array",
"items": { "type": "array", "items": { "type": "integer", "format": "uint64" } },
"description": "Other OSDs of this PG which possibly still have some data left"
}
}
}
},
"DataDescribeParams": {
"type": "object",
"properties": {
"pool_id": { "type": "integer", "format": "uint64", "description": "Only list objects from the given pool (by ID)" },
"pool_name": { "type": "string", "description": "Only list objects from the given pool (by name)" },
"pg": { "type": "integer", "format": "uint64", "description": "Only list objects in the given PG of the pool" },
"inode": { "type": "integer", "format": "uint64", "description": "Single inode number to list" },
"min_inode": { "type": "integer", "format": "uint64", "description": "Minimum inode number to list" },
"max_inode": { "type": "integer", "format": "uint64", "description": "Maximum inode number to list" },
"object_state": {
"type": "array",
"items": { "type": "string", "enum": [ "degraded", "misplaced", "incomplete", "corrupted", "inconsistent" ] },
"description": "Only list objects in given state(s)"
},
"osd": {
"type": "array",
"items": { "type": "integer", "format": "uint64" },
"description": "Only list objects from these primary OSD(s)"
}
}
},
"DataDescribe": {
"type": "array",
"items": {
"type": "object",
"properties": {
"inode": { "type": "integer", "format": "uint64", "description": "Full inode ID with pool" },
"stripe": { "type": "integer", "format": "uint64", "description": "Object offset within inode" },
"part": { "type": "integer", "minimum": 0, "maximum": 255, "description": "Object part number" },
"osd_num": { "type": "integer", "format": "uint64", "description": "OSD number with this part" },
"corrupted": { "type": "boolean", "description": "Part is corrupted flag" },
"inconsistent": { "type": "boolean", "description": "Inconsistency flag (parts do not match)" },
"outdated": { "type": "boolean", "description": "Part on this OSD is outdated" }
}
}
}
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ add_executable(vitastor-disk
disk_tool.cpp disk_simple_offsets.cpp
disk_tool_discard.cpp disk_tool_journal.cpp disk_tool_meta.cpp disk_tool_prepare.cpp disk_tool_resize.cpp
disk_tool_resize_auto.cpp disk_tool_udev.cpp disk_tool_utils.cpp disk_tool_upgrade.cpp
../util/crc32c.c ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ../util/rw_blocking.cpp ../util/allocator.cpp ../util/ringloop.cpp
../util/crc32c.c ../util/xxhash.c ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ../util/rw_blocking.cpp ../util/allocator.cpp ../util/ringloop.cpp
../blockstore/blockstore_disk.cpp ../blockstore/blockstore_heap.cpp ../blockstore/multilist.cpp
)
target_link_libraries(vitastor-disk
+2 -3
View File
@@ -136,8 +136,8 @@ struct disk_tool_t
void choose_journal_block(uint32_t je_size);
int resize_rebuild_journal();
int resize_write_new_journal();
void remap_big_write(heap_entry_t *wr);
void remap_small_write(heap_entry_t *wr);
void remap_big_write(blockstore_heap_t *heap, heap_entry_t *wr);
void remap_small_write(blockstore_heap_t *heap, heap_entry_t *wr);
void fill_old_clean_entry(blockstore_heap_t *heap, heap_entry_t *big_wr);
void fill_old_journal_entry(blockstore_heap_t *heap, heap_entry_t *wr);
int resize_rebuild_meta();
@@ -175,7 +175,6 @@ struct disk_tool_t
void disk_tool_simple_offsets(json11::Json cfg, bool json_output);
uint64_t sscanf_json(const char *fmt, const json11::Json & str);
void fromhexstr(const std::string & from, int bytes, uint8_t *to);
int disable_cache(const std::string & dev);
uint64_t get_atomic_write_size(const std::string & dev);
uint64_t get_device_size(const std::string & dev, bool should_exist = false);
+1
View File
@@ -4,6 +4,7 @@
#include <unistd.h>
#include "disk_tool.h"
#include "str_util.h"
#include "malloc_or_die.h"
int disk_tool_t::dump_journal()
+6 -4
View File
@@ -3,7 +3,9 @@
#include "disk_tool.h"
#include "rw_blocking.h"
#include "osd_id.h"
#include "json_util.h"
#include "str_util.h"
#include "malloc_or_die.h"
int disk_tool_t::process_meta(std::function<void(blockstore_meta_header_v3_t *)> hdr_fn,
@@ -331,7 +333,7 @@ void disk_tool_t::dump_meta_header(blockstore_meta_header_v3_t *hdr)
}
else
{
printf("{\"version\":\"0.5\",\"meta_block_size\":%ju,\"entries\":[\n", dsk.meta_block_size);
printf("{\"version\":\"0.5\",\"meta_block_size\":%u,\"entries\":[\n", dsk.meta_block_size);
}
first_entry = true;
}
@@ -751,7 +753,7 @@ close_err0:
{
*wr->get_checksum(&heap) = sscanf_json("%jx", write_entry["data_crc32c"]);
}
wr->crc32c = wr->calc_crc32c();
wr->checksum = wr->calc_checksum(&heap);
assert((uint8_t*)wr + wr->size == new_meta_buf + meta_offset + used_space);
}
}
@@ -794,7 +796,7 @@ close_err:
fromhexstr(meta_entry["ext_bitmap"].string_value(), new_clean_entry_bitmap_size, wr->get_ext_bitmap(&heap));
if (new_meta_hdr->data_csum_type != 0)
fromhexstr(meta_entry["data_csum"].string_value(), new_data_csum_size, wr->get_checksums(&heap));
wr->crc32c = wr->calc_crc32c();
wr->checksum = wr->calc_checksum(&heap);
assert((uint8_t*)wr + wr->size == new_meta_buf + meta_offset + used_space);
auto j_it = journal_by_object.find(oid);
if (j_it != journal_by_object.end())
@@ -860,7 +862,7 @@ close_err:
assert(0);
}
wr->size = wr->get_size(&heap);
wr->crc32c = wr->calc_crc32c();
wr->checksum = wr->calc_checksum(&heap);
assert((uint8_t*)wr + wr->size == new_meta_buf + meta_offset + used_space);
}
}
+2 -4
View File
@@ -4,6 +4,7 @@
#include "disk_tool.h"
#include "str_util.h"
#include "json_util.h"
#include "osd_id.h"
void disk_tool_t::parse_meta_reserve()
{
@@ -149,12 +150,9 @@ int disk_tool_t::prepare_one(std::map<std::string, std::string> options, int is_
{
if (options["block_size"] == "")
options["block_size"] = "1M";
if (is_hybrid && options["atomic_write_size"] == "")
options["atomic_write_size"] = "0";
if (is_hybrid && options["throttle_small_writes"] == "")
options["throttle_small_writes"] = "1";
if (!is_hybrid && options.find("data_csum_type") != options.end() && options.at("data_csum_type") != "" &&
options["csum_block_size"] == "")
if (!is_hybrid && options.find("data_csum_type") != options.end() && options.at("data_csum_type") != "")
options["csum_block_size"] = "32k";
}
else if (!json_is_true(options["disable_data_fsync"]))
+11 -11
View File
@@ -212,11 +212,11 @@ void disk_tool_t::resize_init(blockstore_meta_header_v3_t *hdr)
dsk.calc_lengths();
if (((new_data_offset-dsk.data_offset) % dsk.data_block_size))
{
fprintf(stderr, "Data alignment mismatch: old data offset is 0x%jx, new is 0x%jx, but alignment on %jx should be equal\n",
fprintf(stderr, "Data alignment mismatch: old data offset is 0x%jx, new is 0x%jx, but alignment on %x should be equal\n",
dsk.data_offset, new_data_offset, dsk.data_block_size);
exit(1);
}
data_idx_diff = ((int64_t)(dsk.data_offset-new_data_offset))/((int64_t)dsk.data_block_size);
data_idx_diff = ((int64_t)(dsk.data_offset-new_data_offset)) / dsk.data_block_size;
free_first = new_data_offset > dsk.data_offset ? (new_data_offset-dsk.data_offset) / dsk.data_block_size : 0;
free_last = (new_data_offset+new_data_len < dsk.data_offset+dsk.data_len)
? (dsk.data_offset+dsk.data_len-new_data_offset-new_data_len) / dsk.data_block_size
@@ -351,7 +351,7 @@ int disk_tool_t::resize_copy_data()
if (data->res != dsk.data_block_size)
{
fprintf(
stderr, "Failed to read %ju bytes at %ju from %s: %s\n", dsk.data_block_size,
stderr, "Failed to read %u bytes at %ju from %s: %s\n", dsk.data_block_size,
dsk.data_offset + moving_blocks[i].old_loc*dsk.data_block_size, dsk.data_device.c_str(),
data->res < 0 ? strerror(-data->res) : "short read"
);
@@ -376,7 +376,7 @@ int disk_tool_t::resize_copy_data()
if (data->res != dsk.data_block_size)
{
fprintf(
stderr, "Failed to write %ju bytes at %ju to %s: %s\n", dsk.data_block_size,
stderr, "Failed to write %u bytes at %ju to %s: %s\n", dsk.data_block_size,
dsk.data_offset + moving_blocks[i].new_loc*dsk.data_block_size, dsk.data_device.c_str(),
data->res < 0 ? strerror(-data->res) : "short write"
);
@@ -531,7 +531,7 @@ int disk_tool_t::resize_write_new_journal()
return 0;
}
void disk_tool_t::remap_big_write(heap_entry_t *wr)
void disk_tool_t::remap_big_write(blockstore_heap_t *heap, heap_entry_t *wr)
{
uint64_t block_num = wr->big().block_num;
auto remap_it = data_remap.find(block_num);
@@ -544,10 +544,10 @@ void disk_tool_t::remap_big_write(heap_entry_t *wr)
}
block_num += data_idx_diff;
wr->big().block_num = block_num;
wr->crc32c = wr->calc_crc32c();
wr->checksum = wr->calc_checksum(heap);
}
void disk_tool_t::remap_small_write(heap_entry_t *wr)
void disk_tool_t::remap_small_write(blockstore_heap_t *heap, heap_entry_t *wr)
{
if (new_meta_format == BLOCKSTORE_META_FORMAT_HEAP && wr->small().len > 0)
{
@@ -559,7 +559,7 @@ void disk_tool_t::remap_small_write(heap_entry_t *wr)
memcpy(new_journal_ptr, buffer_area+wr->small().location, wr->small().len);
wr->small().location = new_journal_ptr-new_journal_buf;
new_journal_ptr += wr->small().len;
wr->crc32c = wr->calc_crc32c();
wr->checksum = wr->calc_checksum(heap);
}
}
@@ -673,11 +673,11 @@ int disk_tool_t::resize_rebuild_meta()
{
if (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT)
{
remap_big_write(wr);
remap_big_write(heap, wr);
}
else if (wr->type() == BS_HEAP_SMALL_WRITE)
{
remap_small_write(wr);
remap_small_write(heap, wr);
}
else if (wr->type() != BS_HEAP_DELETE && new_meta_format != BLOCKSTORE_META_FORMAT_HEAP)
{
@@ -786,7 +786,7 @@ int disk_tool_t::resize_rebuild_meta()
memcpy(((uint8_t*)wr) + sizeof(heap_big_write_t) + new_clean_entry_bitmap_size, bitmap+new_clean_entry_bitmap_size, new_clean_entry_bitmap_size);
memcpy(((uint8_t*)wr) + sizeof(heap_big_write_t) + 2*new_clean_entry_bitmap_size, bitmap+2*new_clean_entry_bitmap_size, new_data_csum_size);
}
wr->crc32c = wr->calc_crc32c();
wr->checksum = wr->calc_checksum(&dsk);
new_meta_pos += wr->size;
}
else
+1 -4
View File
@@ -140,10 +140,7 @@ uint32_t disk_tool_t::write_osd_superblock(std::string device, json11::Json para
}
close(fd);
free(buf);
if (!test_mode)
{
shell_exec({ "udevadm", "trigger", "--settle", device }, "", NULL, NULL);
}
shell_exec({ "udevadm", "trigger", "--settle", device }, "", NULL, NULL);
return sb_size;
}

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