Compare commits

..

74 Commits

Author SHA1 Message Date
ded8832e65 WIP Switch back to TCP when RDMA send fails (does not work) 2026-07-13 00:24:34 +03:00
7e018f98a8 Close TCP connections for RDMA clients 2026-07-13 00:24:34 +03:00
edc0ebb2a0 Add security parameter documentation 2026-07-12 00:30:10 +03:00
452a722884 Implement vitastor-cli cpubench for AES and xxhash3 benchmarks 2026-07-09 00:34:30 +03:00
29c3841eaa Allow to make-etcd --antietcd-only 2026-07-05 18:51:23 +03:00
45204da444 Rename server_cert to api_cert 2026-07-05 15:44:59 +03:00
c18ddbf255 Remove antietcd_ca parameter from mon/antietcd_adapter - it is client_ca 2026-07-05 15:36:20 +03:00
76410df0e8 Do not require explicit peer_ca for antietcd 2026-07-05 15:19:03 +03:00
6a0f3a38d0 Do not enable use_perms by default - make it more explicit for users 2026-07-05 15:19:03 +03:00
265e99ccd0 Support TLS certificate generation in make-etcd 2026-07-05 15:19:03 +03:00
aa71a1968f Rename use_auth to use_perms 2026-07-05 14:58:24 +03:00
5382f1c7bb Fix use_auth usage 2026-07-05 14:58:24 +03:00
a5dbf74123 Add support for full AES-GCM including double encryption of AES-XTS :D 2026-07-05 14:58:24 +03:00
784fd7d233 Also allow clients to load /inode/stats/ for their inodes 2026-07-05 14:58:24 +03:00
13b7c0e73d Load all /config/ 2026-07-05 14:58:24 +03:00
d747ec8c41 Fix modify owner/groups 2026-07-05 14:58:24 +03:00
cabe5d1543 Skip non-existing pools 2026-07-05 14:58:24 +03:00
38da624930 Use osd & mon certs instead of usernames 2026-07-05 14:58:24 +03:00
e0ff8a014f Remove mon and osd user types 2026-07-05 14:58:24 +03:00
8721f21874 Also check for "Received garbage" during test 2026-07-05 14:58:24 +03:00
9eb858fb5f Add a CI test with RDMA 2026-07-05 14:58:24 +03:00
9f084f48f6 Add request size validation to prevent OOMDoS 2026-07-05 14:58:24 +03:00
c998325448 Implement OSD-side authorization for operations 2026-07-05 14:58:24 +03:00
a243ff6459 Remove tls_ from option names 2026-07-05 14:58:24 +03:00
47b3294666 Remove TLS support (superseded by direct GCM) 2026-07-05 14:58:24 +03:00
3afed2473e Implement TLS 1.3-like handshake manually 2026-07-05 14:58:24 +03:00
fdeacdcf30 Extract openssl-related code, wire ssl implementation back 2026-07-05 14:58:24 +03:00
d7ddd7752c Coalesce entries in send_out_buf 2026-07-05 14:58:24 +03:00
2b2c7d5e39 Support isa-l_crypto for AES-XTS too 2026-07-05 14:58:24 +03:00
e26478c2e7 Fix xts+rdma encrypt errors 2026-07-05 14:58:24 +03:00
c7b9d30fd3 Fix "use-after-realloc" warning 2026-07-05 14:58:24 +03:00
5803655840 Support isa-l_crypto for AES-GCM 2026-07-05 14:58:24 +03:00
61ce26ba29 Remove WITH_OPENSSL from all files except http_client, always require OpenSSL 2026-07-05 14:58:24 +03:00
32e651ec96 Use pools for GCM contexts 2026-07-05 14:58:23 +03:00
dbed0e94cb Try to run tests with AES-GCM 2026-07-05 14:58:09 +03:00
8b39a268b9 Implement direct AES-256-GCM with a static key for benchmark 2026-07-05 14:58:09 +03:00
d40ba7c5ce Make sure to send all TLS data before continuing 2026-07-05 14:58:09 +03:00
985257f2d7 Parse standard TLS record headers 2026-07-05 14:58:09 +03:00
a4f3383f21 Omit msgr_tls_record_hdr_t for non-tls data 2026-07-05 14:58:09 +03:00
b2d828ec84 Allow 2 and 4 byte per block chain_info (allow more than 255 snapshots with encryption) 2026-07-05 14:58:09 +03:00
b2a74de715 Implement OSD TLS support 2026-07-05 14:58:09 +03:00
430761ad2e Allow to skip checksums for headers 2026-07-05 14:58:09 +03:00
38a1f6937d Implement protocol-level checksums (xxhash3) 2026-07-05 14:58:09 +03:00
8b46914999 Include xxhash3 x86dispatch 2026-07-05 14:58:09 +03:00
1b81e60187 Do not use scrap_buffer in the client (it would block protocol checksum support) 2026-07-05 14:58:09 +03:00
526c597cd0 Support TLS CN authentication and per-image permissions in vitastor-cli serve 2026-07-05 14:58:09 +03:00
7a97783ead Add VitastorAuthFilter 2026-07-05 14:58:09 +03:00
b4425d8e65 Implement vitastor-cli ls-user, modify-user, remove-user commands 2026-07-05 14:58:09 +03:00
1afda35bd3 Add image owner/owner_group/reader_group support (for antietcd VitastorAuthFilter) 2026-07-05 14:58:09 +03:00
0953f5ebdd Support inline (string PEM) certificates and pkeys 2026-07-05 14:10:11 +03:00
1b03615e50 Show encryption keys (only IDs) in the listing 2026-07-05 14:10:11 +03:00
8f208c53df Support storing image encryption keys in Vault 2026-07-05 14:10:11 +03:00
c0d2dabe66 Prefer local etcd addresses and correctly cycle over them even when they need resolving
Seems slightly overcomplicated...
2026-07-05 14:10:10 +03:00
6da4aaf176 Support DNS resolving via libc-ares 2026-07-05 14:10:10 +03:00
5b6a6fce9a Batch handle_immediate_ops more 2026-07-05 14:10:10 +03:00
79fb2def57 Add vitastor-cli create & modify --enc-key parameter 2026-07-05 14:10:10 +03:00
7f5c24144b Support reading from snapshots encrypted with different keys 2026-07-05 14:10:10 +03:00
3cf876fafa Support decryption with multiple keys 2026-07-05 14:10:10 +03:00
771aa83282 Allow to return chain_info in response to reads 2026-07-05 14:10:10 +03:00
03404ac95d Add basic AES-XTS client-side encryption support 2026-07-05 14:10:10 +03:00
decf314238 Rework msgr send/receive to allow encryption support 2026-07-05 14:10:10 +03:00
ab1849ff07 Move fromhexstr() to str_util 2026-07-05 14:10:10 +03:00
33b18c7229 Add openapi description 2026-07-05 14:10:10 +03:00
4a2efb29ec Slightly fix API return and input types 2026-07-05 14:10:10 +03:00
00a432193a Implement vitastor-cli serve command to serve simple HTTP API 2026-07-05 14:10:10 +03:00
d83eb599a0 Implement HTTP server support O_o 2026-07-05 14:10:10 +03:00
c1c9b1975d Rename http_response_t to http_message_t 2026-07-05 14:10:10 +03:00
ba0d9ad9f2 Extract common HTTP context 2026-07-05 14:10:10 +03:00
4feabc4ab6 Support xxhash 32-bit checksums (data_csum_type=xxh3_32) 2026-07-05 14:10:10 +03:00
1a14301c50 Detect block checksums using csum_block_size, not data_csum_type 2026-07-05 14:09:57 +03:00
5dc52a7c06 Add client certificate support 2026-07-05 14:09:57 +03:00
4d45b27696 Do not re-initialize TLS context every connection 2026-07-05 14:09:57 +03:00
5995c4a00f Add https support to antietcd 2026-07-05 14:09:57 +03:00
906388adaa Implement etcd SSL support via OpenSSL
Maybe I should remove all of this and use libwebsockets :)
2026-07-05 14:09:57 +03:00
236 changed files with 3469 additions and 23368 deletions

View File

@@ -23,7 +23,7 @@ RUN apt-get update
RUN apt-get -y install etcd qemu-system-x86 qemu-block-extra qemu-utils fio libasan8 \
libgoogle-perftools-dev devscripts libjerasure-dev cmake libibverbs-dev libisal-dev
RUN apt-get -y build-dep fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted libc-ares-dev udev iproute2
RUN apt-get update && apt-get -y install jq lp-solve sudo nfs-common fdisk parted libc-ares-dev udev
RUN apt-get --download-only source fio qemu=`dpkg -s qemu-system-x86|grep ^Version:|awk '{print $2}'`
RUN set -ex; \

View File

@@ -62,13 +62,8 @@ jobs:
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run tests
id: test
run: cd /root/vitastor/build && make -j16 build_tests test
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
cat /root/vitastor/build/Testing/Temporary/LastTest.log
# leak sanitizer sometimes crashes
- run: cd /root/vitastor/build && ASAN_OPTIONS=detect_leaks=0 make -j16 build_tests test
npm_lint:
runs-on: ubuntu-latest
@@ -167,24 +162,6 @@ jobs:
echo ""
done
test_change_pg_minsize:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_change_pg_minsize.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_change_pg_size:
runs-on: ubuntu-latest
needs: build
@@ -311,42 +288,6 @@ jobs:
echo ""
done
test_img_online_resize:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_img_online_resize.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_no_uring:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_no_uring.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
@@ -2345,24 +2286,6 @@ jobs:
echo ""
done
test_nfs_long_readdir:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_nfs_long_readdir.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_write_encrypted:
runs-on: ubuntu-latest
needs: build

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.30)
project(vitastor)
set(VITASTOR_VERSION "3.2.2")
set(VITASTOR_VERSION "3.0.15")
include(CTest)

View File

@@ -37,8 +37,8 @@ RUN (echo deb http://vitastor.io/debian trixie main > /etc/apt/sources.list.d/vi
wget -q -O /etc/apt/trusted.gpg.d/vitastor.gpg https://vitastor.io/debian/pubkey.gpg && \
apt-get update && \
apt-get install -y vitastor-client ibverbs-providers && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2/qemu-utils_10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2/qemu-block-extra_10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.2%2Bds-2%2Bvitastor1/qemu-utils_10.0.2%2Bds-2%2Bvitastor1_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.2%2Bds-2%2Bvitastor1/qemu-block-extra_10.0.2%2Bds-2%2Bvitastor1_amd64.deb && \
dpkg -x qemu-utils*.deb tmp1 && \
dpkg -x qemu-block-extra*.deb tmp1 && \
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \

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)
@@ -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-10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2/qemu-utils_10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2/qemu-block-extra_10.0.11%2Bds-0%2Bdeb13u1%2Bvitastor2_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-utils_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
wget https://vitastor.io/archive/qemu/qemu-trixie-9.2.2%2Bds-1%2Bvitastor4/qemu-block-extra_9.2.2%2Bds-1%2Bvitastor4_amd64.deb && \
dpkg -x qemu-utils*.deb tmp1 && \
dpkg -x qemu-block-extra*.deb tmp1 && \
cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \

View File

@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.2.2
VITASTOR_VERSION ?= v3.0.15
all: build push

View File

@@ -49,7 +49,7 @@ spec:
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: vitalif/vitastor-csi:v3.2.2
image: vitalif/vitastor-csi:v3.0.15
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"

View File

@@ -87,7 +87,7 @@ spec:
- name: socket-dir
mountPath: /csi
- name: csi-attacher
image: k8s.gcr.io/sig-storage/csi-attacher:v3.2.2
image: k8s.gcr.io/sig-storage/csi-attacher:v3.1.0
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
@@ -121,7 +121,7 @@ spec:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
image: vitalif/vitastor-csi:v3.2.2
image: vitalif/vitastor-csi:v3.0.15
args:
- "--node=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"

View File

@@ -7,7 +7,6 @@ require (
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/kubernetes-csi/csi-lib-utils v0.9.1
golang.org/x/net v0.7.0
golang.org/x/sys v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/grpc v1.33.1
google.golang.org/protobuf v1.24.0

View File

@@ -5,7 +5,7 @@ package vitastor
const (
vitastorCSIDriverName = "csi.vitastor.io"
vitastorCSIDriverVersion = "3.2.2"
vitastorCSIDriverVersion = "3.0.15"
)
// Config struct fills the parameters of request or user input

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
"os"
"io/ioutil"
@@ -26,6 +27,7 @@ const (
MB int64 = 1024 * KB
GB int64 = 1024 * MB
TB int64 = 1024 * GB
ETCD_TIMEOUT time.Duration = 15*time.Second
)
type InodeIndex struct
@@ -685,7 +687,7 @@ func (cs *ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi
return &csi.ControllerExpandVolumeResponse{
CapacityBytes: int64(inodeCfg[0].Size),
NodeExpansionRequired: true,
NodeExpansionRequired: false,
}, nil
}

View File

@@ -96,16 +96,18 @@ func NewNodeServer(driver *Driver) *NodeServer
{
ns.restoreUblkDaemons()
}
ns.restoreNfsDaemons()
dur, err := time.ParseDuration(os.Getenv("RESTART_INTERVAL"))
if (err != nil)
if (ns.method == MOUNT_VDUSE || ns.method == MOUNT_UBLK)
{
dur = 10 * time.Second
}
ns.restartInterval = dur
if (ns.restartInterval != time.Duration(0))
{
go ns.restarter()
dur, err := time.ParseDuration(os.Getenv("RESTART_INTERVAL"))
if (err != nil)
{
dur = 10 * time.Second
}
ns.restartInterval = dur
if (ns.restartInterval != time.Duration(0))
{
go ns.restarter()
}
}
return ns
}
@@ -132,8 +134,10 @@ func (ns *NodeServer) unlockVolume(lockId string)
func (ns *NodeServer) restarter()
{
// Restart dead storage daemons at regular intervals. Otherwise volume I/O
// may hang and may even lead to a kernel panic if panic_on_hung_task is set.
// Restart dead VDUSE daemons at regular intervals
// Otherwise volume I/O may hang in case of a qemu-storage-daemon crash
// Moreover, it may lead to a kernel panic of the kernel is configured to
// panic on hung tasks
ticker := time.NewTicker(ns.restartInterval)
defer ticker.Stop()
for
@@ -147,7 +151,6 @@ func (ns *NodeServer) restarter()
{
ns.restoreUblkDaemons()
}
ns.restoreNfsDaemons()
}
}
@@ -326,10 +329,15 @@ func (ns *NodeServer) restoreNfsDaemons()
{
return
}
activeNFS, err := ns.listActiveNFS()
if (err != nil)
{
return
}
// Check all state files and try to restore active mounts
for _, stateFile := range stateFiles
{
ns.checkNfsState(stateFile)
ns.checkNfsState(stateFile, activeNFS)
}
}
@@ -355,43 +363,24 @@ func (ns *NodeServer) readNfsState(stateFile string, allowNotExists bool) (*NfsS
return &state, nil
}
func (ns *NodeServer) checkNfsState(stateFile string)
func (ns *NodeServer) checkNfsState(stateFile string, activeNfs map[int][]string)
{
// Read state file to determine the lock ID
// Read state file
state, err := ns.readNfsState(stateFile, false)
if (err != nil)
{
return
}
// Lock FS
lockId := state.ConfigPath+":fs:"+state.FsName
ns.lockVolume(lockId)
defer ns.unlockVolume(lockId)
// Recheck state and mounts under the lock: publish/unpublish may have changed
// both while this recovery attempt was waiting for the lock.
state, err = ns.readNfsState(stateFile, true)
if (err != nil || state == nil)
{
return
}
activeNfs, err := ns.listActiveNFS()
if (err != nil)
{
return
}
ns.lockVolume(state.ConfigPath+":fs:"+state.FsName)
defer ns.unlockVolume(state.ConfigPath+":fs:"+state.FsName)
// Check if NFS at this port is still mounted
pidFile := ns.stateDir + filepath.Base(stateFile)
pidFile = pidFile[0:len(pidFile)-5] + ".pid"
if (len(activeNfs[state.Port]) == 0)
{
// This is a stale state file. Remove the root staging mount before
// stopping the daemon, otherwise a dead hidden NFS mount may remain.
// this is a stale state file, remove it
klog.Warningf("state file %v contains stale mount at port %d, removing it", stateFile, state.Port)
err = mount.CleanupMountPoint(state.Path, ns.mounter, false)
if (err != nil)
{
klog.Errorf("failed to unmount stale NFS mount %v: %v", state.Path, err)
return
}
ns.stopNFS(stateFile, pidFile)
return
}
@@ -411,7 +400,6 @@ func (ns *NodeServer) checkNfsState(stateFile string)
"--pidfile", pidFile,
"--bind", "127.0.0.1",
"--port", fmt.Sprintf("%d", state.Port),
"--config_path", state.ConfigPath,
"--fs", state.FsName,
"--pool", state.Pool,
"--portmap", "0",
@@ -737,7 +725,6 @@ func (ns *NodeServer) mountNFS(ctxVars map[string]string) (string, error)
"--pidfile", pidFile,
"--bind", "127.0.0.1",
"--port", "auto",
"--config_path", state.ConfigPath,
"--fs", state.FsName,
"--pool", state.Pool,
"--portmap", "0",
@@ -767,7 +754,7 @@ func (ns *NodeServer) mountNFS(ctxVars map[string]string) (string, error)
// Mount NFS
_, _, err = system(
"mount", "-t", "nfs", "127.0.0.1:/", state.Path,
"-o", fmt.Sprintf("port=%d,mountport=%d,nfsvers=3,soft,sync,nolock,tcp", port, port),
"-o", fmt.Sprintf("port=%d,mountport=%d,nfsvers=3,soft,nolock,tcp", port, port),
)
if (err != nil)
{
@@ -828,38 +815,26 @@ func (ns *NodeServer) listActiveNFS() (map[int][]string, error)
klog.Errorf("failed to list mounts: %v", err)
return nil, err
}
return collectActiveNFS(mounts), nil
}
func collectActiveNFS(mounts []mount.MountInfo) map[int][]string
{
activeNFS := make(map[int][]string)
for _, mnt := range mounts
for _, mount := range mounts
{
// Only count PVC subdirectory mounts, not the root staging mount. Newer
// kernels expose the subdirectory in Source while older ones expose it
// in Root. The staging mount may also be visible through several bind
// aliases in the CSI container, so its mount point isn't a reliable
// discriminator.
if (mnt.FsType != "nfs" || !strings.HasPrefix(mnt.Source, "127.0.0.1:/") ||
(mnt.Source == "127.0.0.1:/" && mnt.Root == "/"))
// Volume mounts always refer to subpaths
if (mount.FsType == "nfs" && mount.Root != "/")
{
continue
}
// NFS-specific options, including port, are per-superblock options.
for _, opt := range mnt.SuperOptions
{
if (strings.HasPrefix(opt, "port="))
for _, opt := range mount.MountOptions
{
port64, err := strconv.ParseUint(opt[5:], 10, 16)
if (err == nil)
if (strings.HasPrefix(opt, "port="))
{
activeNFS[int(port64)] = append(activeNFS[int(port64)], mnt.MountPoint)
port64, err := strconv.ParseUint(opt[5:], 10, 16)
if (err == nil)
{
activeNFS[int(port64)] = append(activeNFS[int(port64)], mount.MountPoint)
}
}
}
}
}
return activeNFS
return activeNFS, nil
}
// NodePublishVolume mounts the volume mounted to the staging path to the target path
@@ -1055,131 +1030,10 @@ func (ns *NodeServer) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVo
return nil, status.Error(codes.Unimplemented, "")
}
// NodeExpandVolume expands the block device and, for filesystem volumes, the filesystem on the node
// NodeExpandVolume expanding the file system on the node
func (ns *NodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error)
{
klog.Infof("received node expand volume request %+v", protosanitizer.StripSecrets(req))
if (req == nil)
{
return nil, status.Error(codes.InvalidArgument, "request cannot be empty")
}
if (req.VolumeId == "")
{
return nil, status.Error(codes.InvalidArgument, "VolumeId is required")
}
ctxVars := make(map[string]string)
err := json.Unmarshal([]byte(req.VolumeId), &ctxVars)
if (err != nil)
{
return nil, status.Error(codes.Internal, "volume ID not in JSON format")
}
volName := ctxVars["name"]
if (ctxVars["vitastorfs"] != "")
{
// VitastorFS CSI volumes are NFS-backed and currently don't have per-volume quotas to resize here.
return &csi.NodeExpandVolumeResponse{CapacityBytes: req.GetCapacityRange().GetRequiredBytes()}, nil
}
ns.lockVolume(ctxVars["configPath"]+":block:"+volName)
defer ns.unlockVolume(ctxVars["configPath"]+":block:"+volName)
targetPath := req.GetStagingTargetPath()
if (targetPath == "")
{
targetPath = req.GetVolumePath()
}
if (targetPath == "")
{
return nil, status.Error(codes.InvalidArgument, "VolumePath or StagingTargetPath is required")
}
devicePath, err := GetDeviceNameFromMount(targetPath)
if (err != nil)
{
if (os.IsNotExist(err))
{
return nil, status.Error(codes.NotFound, "Target path not found")
}
return nil, err
}
if (devicePath == "")
{
return nil, status.Errorf(codes.NotFound, "%s is not a mountpoint", targetPath)
}
if (devicePath[0] != '/')
{
return nil, status.Errorf(codes.Internal, "failed to resolve device for %s: %s", targetPath, devicePath)
}
if (len(devicePath) < 5 || devicePath[0:5] != "/dev/")
{
// NodeExpandVolume may be called with the published target path. For block volumes it is
// usually a bind mount of the staging target, so resolve one more mount layer if needed.
var realDevicePath string
realDevicePath, err = GetDeviceNameFromMount(devicePath)
if (err == nil && realDevicePath != "")
{
devicePath = realDevicePath
}
}
// All newest Vitastor driver versions (nbd, ublk and vduse) support online resize.
// So all we need to do is just wait a bit until the device is resized by the driver.
requiredBytes := req.GetCapacityRange().GetRequiredBytes()
if (requiredBytes > 0)
{
timeout := 30
var actualSize uint64
var err error
for i := 0; i < timeout && actualSize < uint64(requiredBytes); i++
{
actualSize, err = GetBlockDeviceSize(devicePath)
if (err != nil)
{
return nil, err
}
time.Sleep(1 * time.Second)
}
if (actualSize < uint64(requiredBytes))
{
return nil, status.Errorf(codes.NotFound, "Failed to wait for online resize of %s to %v bytes: actual size is still %v after %v seconds",
devicePath, requiredBytes, actualSize, timeout)
}
}
isBlock := req.GetVolumeCapability().GetBlock() != nil
if (isBlock)
{
return &csi.NodeExpandVolumeResponse{CapacityBytes: requiredBytes}, nil
}
diskMounter := &mount.SafeFormatAndMount{Interface: ns.mounter, Exec: utilexec.New()}
fsType := req.GetVolumeCapability().GetMount().GetFsType()
if (fsType == "")
{
fsType, err = diskMounter.GetDiskFormat(devicePath)
if (err != nil)
{
return nil, err
}
}
switch (fsType)
{
case "ext4":
_, err = systemCombined("resize2fs", devicePath)
case "xfs":
_, err = systemCombined("xfs_growfs", targetPath)
default:
return nil, status.Errorf(codes.InvalidArgument, "unsupported filesystem for resize: %s", fsType)
}
if (err != nil)
{
return nil, err
}
return &csi.NodeExpandVolumeResponse{CapacityBytes: requiredBytes}, nil
return nil, status.Error(codes.Unimplemented, "")
}
// NodeGetCapabilities returns the supported capabilities of the node server
@@ -1194,13 +1048,6 @@ func (ns *NodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetC
},
},
},
&csi.NodeServiceCapability{
Type: &csi.NodeServiceCapability_Rpc{
Rpc: &csi.NodeServiceCapability_RPC{
Type: csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
},
},
},
},
}, nil
}

View File

@@ -1,73 +0,0 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
package vitastor
import (
"reflect"
"testing"
"k8s.io/utils/mount"
)
func TestCollectActiveNFS(t *testing.T)
{
mounts := []mount.MountInfo{
{
Root: "/",
Source: "127.0.0.1:/",
MountPoint: "/var/lib/kubelet/plugins/csi.vitastor.io/nfs/hash",
FsType: "nfs",
MountOptions: []string{"rw", "relatime"},
SuperOptions: []string{"rw", "vers=3", "port=45059"},
},
{
// This is how NFS subdirectory bind mounts appear on Linux 6.19:
// Root is still / and the subdirectory is only visible in Source.
Root: "/",
Source: "127.0.0.1:/pvc-one",
MountPoint: "/var/lib/kubelet/pods/pod-one/volumes/kubernetes.io~csi/pvc-one/mount",
FsType: "nfs",
MountOptions: []string{"rw", "relatime"},
SuperOptions: []string{"rw", "vers=3", "port=45059"},
},
{
Root: "/",
Source: "127.0.0.1:/pvc-two",
MountPoint: "/var/lib/kubelet/pods/pod-two/volumes/kubernetes.io~csi/pvc-two/mount",
FsType: "nfs",
MountOptions: []string{"rw", "relatime"},
SuperOptions: []string{"rw", "vers=3", "port=45059"},
},
{
// Older kernels expose the bind-mounted subdirectory in Root.
Root: "/pvc-three",
Source: "127.0.0.1:/",
MountPoint: "/var/lib/kubelet/pods/pod-three/volumes/kubernetes.io~csi/pvc-three/mount",
FsType: "nfs",
MountOptions: []string{"rw", "relatime"},
SuperOptions: []string{"rw", "vers=3", "port=45059"},
},
{
Root: "/",
Source: "server:/other",
MountPoint: "/mnt/other",
FsType: "nfs",
MountOptions: []string{"rw"},
SuperOptions: []string{"rw", "port=2049"},
},
}
got := collectActiveNFS(mounts)
want := map[int][]string{
45059: {
"/var/lib/kubelet/pods/pod-one/volumes/kubernetes.io~csi/pvc-one/mount",
"/var/lib/kubelet/pods/pod-two/volumes/kubernetes.io~csi/pvc-two/mount",
"/var/lib/kubelet/pods/pod-three/volumes/kubernetes.io~csi/pvc-three/mount",
},
}
if (!reflect.DeepEqual(got, want))
{
t.Fatalf("unexpected active NFS mounts: got %#v, want %#v", got, want)
}
}

View File

@@ -20,7 +20,6 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"golang.org/x/sys/unix"
)
type MountMethod int
@@ -520,22 +519,3 @@ func GetDeviceNameFromMount(mountPath string) (string, error)
return device, nil
}
func GetBlockDeviceSize(devicePath string) (uint64, error)
{
file, err := os.Open(devicePath)
if (err != nil)
{
return 0, err
}
defer file.Close()
// BLKGETSIZE64 returns the size in bytes as a uint64/int64
size, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKGETSIZE64)
if (err != nil)
{
return 0, fmt.Errorf("ioctl BLKGETSIZE64 failed: %w", err)
}
return uint64(size), nil
}

2
debian/changelog vendored
View File

@@ -1,4 +1,4 @@
vitastor (3.2.2-1) unstable; urgency=medium
vitastor (3.0.15-1) unstable; urgency=medium
* Bugfixes

View File

@@ -12,9 +12,7 @@ ARG REL=
WORKDIR /root
RUN set -e -x; \
if [ "$REL" = "buster" ]; then \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
fi; \
perl -i -pe 's/deb.debian.org/archive.debian.org/' /etc/apt/sources.list; \
apt-get update; \
apt-get -y install wget; \
wget https://vitastor.io/debian/pubkey.gpg -O /etc/apt/trusted.gpg.d/vitastor.gpg; \
@@ -26,6 +24,6 @@ RUN set -e -x; \
RUN apt-get update && \
apt-get -y install fio libgoogle-perftools-dev devscripts libjerasure-dev cmake libc-ares-dev libisal-crypto-dev \
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev libssl-dev curl nodejs npm node-nan node-bindings && \
libibverbs-dev librdmacm-dev libisal-dev libnl-3-dev libnl-genl-3-dev curl nodejs npm node-nan node-bindings && \
apt-get -y build-dep fio && \
apt-get --download-only source fio

View File

@@ -4,8 +4,6 @@ usr/bin/vitastor-rm
usr/bin/vitastor-nbd
usr/bin/vitastor-ublk
usr/bin/vitastor-nfs
usr/sbin/mount.vitastorfs
usr/sbin/mount.vitastorblk
usr/bin/vitastor-kv
usr/bin/vitastor-kv-stress
usr/lib/*/libvitastor*.so*

View File

@@ -1,4 +1,4 @@
VITASTOR_VERSION ?= v3.2.2
VITASTOR_VERSION ?= v3.0.15
all: build push

View File

@@ -4,7 +4,7 @@
#
# Desired Vitastor version
VITASTOR_VERSION=v3.2.2
VITASTOR_VERSION=v3.0.15
# Additional arguments for all containers
# For example, you may want to specify a custom logging driver here

View File

@@ -17,9 +17,6 @@ between clients, OSDs and etcd.
- [rdma_device](#rdma_device)
- [rdma_port_num](#rdma_port_num)
- [rdma_gid_index](#rdma_gid_index)
- [rdma_traffic_class](#rdma_traffic_class)
- [rdma_sl](#rdma_sl)
- [rdma_set_flow_label](#rdma_set_flow_label)
- [rdma_mtu](#rdma_mtu)
- [rdma_max_sge](#rdma_max_sge)
- [rdma_max_msg](#rdma_max_msg)
@@ -36,7 +33,6 @@ between clients, OSDs and etcd.
- [etcd_ws_keepalive_interval](#etcd_ws_keepalive_interval)
- [etcd_min_reload_interval](#etcd_min_reload_interval)
- [tcp_header_buffer_size](#tcp_header_buffer_size)
- [rxbounce](#rxbounce)
- [min_zerocopy_send_size](#min_zerocopy_send_size)
- [use_sync_send_recv](#use_sync_send_recv)
@@ -158,40 +154,6 @@ A correct rdma_gid_index for RoCEv2 is usually 1 (IPv6) or 3 (IPv4).
Not relevant for RDMA-CM (use_rdmacm).
## rdma_traffic_class
- Type: integer
- Default: 0
Full TOS byte for RDMA traffic in the IP header (RoCEv2): DSCP in the
higher 6 bits and ECN in the lower 2 bits. For example, 106 (0x6A) =
DSCP 26 + ECT(0). Works with both libibverbs and RDMA-CM modes and should
be set on both OSDs and clients to mark traffic in both directions.
Default 0 = don't mark.
## rdma_sl
- Type: integer
- Default: 0
Service Level (802.1p priority analogue) for RDMA traffic, put into the
address vector of every connection. Only applies to the libibverbs mode:
in RDMA-CM mode the service level is derived from rdma_traffic_class.
Default 0 = as is without PFC-configured networks.
## rdma_set_flow_label
- Type: boolean
- Default: false
Automatically derive an IPv6 Flow Label for RDMA connections from the
queue pair number pair, so that every connection gets its own label and
connections spread over ECMP links while each connection keeps a single
stable path. Only applied to IPv6 GIDs: an IPv4-mapped GID
(::ffff:a.b.c.d) produces IPv4 packets which have no flow label. Only
applies to the libibverbs mode; RDMA-CM generates flow labels on its own.
Default false = don't set.
## rdma_mtu
- Type: integer
@@ -341,21 +303,6 @@ is received without an additional copy. You can try to play with this
parameter and see how it affects random iops and linear bandwidth if you
want.
## rxbounce
- Type: boolean
- Default: false
- Can be changed online: yes
Receive all TCP data into an intermediate buffer before copying to the actual
destination. Enabling this option is required for Windows guests if you use TCP
and [security.en.md#proto_checksums](proto_checksums) because Windows uses a
shared system-wide 'dummy page' to skip unneeded blocks during reads and breaks
checksum verification with it when data copy is avoided. rxbounce is only needed
when you use TCP and don't use transport encryption (AES-GCM); it's not required
with RDMA or with GCM encryption because both always copy data anyway (yes, RDMA
isn't actually zerocopy in Vitastor).
## min_zerocopy_send_size
- Type: integer

View File

@@ -17,9 +17,6 @@
- [rdma_device](#rdma_device)
- [rdma_port_num](#rdma_port_num)
- [rdma_gid_index](#rdma_gid_index)
- [rdma_traffic_class](#rdma_traffic_class)
- [rdma_sl](#rdma_sl)
- [rdma_set_flow_label](#rdma_set_flow_label)
- [rdma_mtu](#rdma_mtu)
- [rdma_max_sge](#rdma_max_sge)
- [rdma_max_msg](#rdma_max_msg)
@@ -36,7 +33,6 @@
- [etcd_ws_keepalive_interval](#etcd_ws_keepalive_interval)
- [etcd_min_reload_interval](#etcd_min_reload_interval)
- [tcp_header_buffer_size](#tcp_header_buffer_size)
- [rxbounce](#rxbounce)
- [min_zerocopy_send_size](#min_zerocopy_send_size)
- [use_sync_send_recv](#use_sync_send_recv)
@@ -161,40 +157,6 @@ libibverbs < v32.
Опция неприменима к RDMA-CM (use_rdmacm).
## rdma_traffic_class
- Тип: целое число
- Значение по умолчанию: 0
Полный байт TOS для RDMA-трафика в заголовке IP (RoCEv2): DSCP в старших
6 битах и ECN в младших 2. Например, 106 (0x6A) = DSCP 26 + ECT(0).
Работает в обоих режимах RDMA - и libibverbs, и RDMA-CM; должен быть задан
и на OSD, и на клиентах, чтобы трафик помечался в обе стороны. По умолчанию
0 - не помечать.
## rdma_sl
- Тип: целое число
- Значение по умолчанию: 0
Service Level (аналог приоритета 802.1p) для RDMA-трафика; попадает в
адресный вектор каждого соединения. Применим только к libibverbs-режиму:
в RDMA-CM уровень обслуживания выводится из rdma_traffic_class.
По умолчанию 0 = как в сетях без PFC.
## rdma_set_flow_label
- Тип: булево (да/нет)
- Значение по умолчанию: false
Автоматически выводить IPv6 Flow Label для RDMA-соединений из пары
номеров очередей (QPN), чтобы каждое соединение получало свою метку,
соединения распределялись по ECMP-каналам, но каждое соединение шло по
одному стабильному пути. Применяется только к IPv6-GID: IPv4-mapped GID
(::ffff:a.b.c.d) даёт IPv4-пакеты, где поля flow label нет. Применим
только к libibverbs-режиму - RDMA-CM всегда генерирует flow label сам.
По умолчанию false = не задавать.
## rdma_mtu
- Тип: целое число
@@ -348,22 +310,6 @@ Vitastor содержат 128-байтные заголовки, за котор
поменять этот параметр и посмотреть, как он влияет на производительность
случайного и линейного доступа.
## rxbounce
- Тип: булево (да/нет)
- Значение по умолчанию: false
- Можно менять на лету: да
Принимать все данные по TCP в промежуточный буфер и потом копировать их в
буфер клиента (ВМ). Это нужно для гостевых ОС Windows с TCP и включёнными
[security.ru.md#proto_checksums](proto_checksums), потому что Windows использует
одну разделяемую общесистемную страницу-заглушку для пропуска ненужных блоков
при чтении и ломает проверку контрольных сумм, если данные не копируются лишний
раз. Опция rxbounce нужна только если вы используете TCP и не используете
транспортное шифрование (AES-GCM); с RDMA или шифрованием опция не нужна, так
как в этом случае данные копируются всегда (да, RDMA в Vitastor на самом деле
не zero-copy).
## min_zerocopy_send_size
- Тип: целое число

View File

@@ -19,7 +19,6 @@ a PEM string with certificate or a private key. In the latter case, the string m
- [pkey](#pkey)
- [etcd_ca](#etcd_ca)
- [client_ca](#client_ca)
- [admin_ca](#admin_ca)
- [osd_ca](#osd_ca)
- [mon_ca](#mon_ca)
- [antietcd_cert](#antietcd_cert)
@@ -87,12 +86,6 @@ certificate itself - it's fine to use it for etcd_ca.
Trusted TLS CA to verify Vitastor client certificates.
Mandatory for Vitastor protocol encryption.
## admin_ca
- Type: string
Trusted TLS CA to verify Vitastor administrator certificates.
## osd_ca
- Type: string

View File

@@ -21,7 +21,6 @@ OSD, мониторами и клиентами.
- [pkey](#pkey)
- [etcd_ca](#etcd_ca)
- [client_ca](#client_ca)
- [admin_ca](#admin_ca)
- [osd_ca](#osd_ca)
- [mon_ca](#mon_ca)
- [antietcd_cert](#antietcd_cert)
@@ -89,12 +88,6 @@ OSD, мониторами и клиентами.
Доверенный TLS-сертификат для проверки сертификатов клиентов Vitastor.
Требуется для шифрования протокола Vitastor.
## admin_ca
- Тип: строка
Доверенный TLS-сертификат для проверки сертификатов администраторов Vitastor.
## osd_ca
- Тип: строка

View File

@@ -13,7 +13,6 @@ const L = {
toc_installation: 'Installation',
toc_config: '[Configuration](../config.en.md)',
toc_usage: 'Usage',
toc_tests: 'Tests',
toc_performance: 'Performance',
online: 'Can be changed online: yes',
},
@@ -29,7 +28,6 @@ const L = {
toc_installation: 'Установка',
toc_config: '[Конфигурация](../config.ru.md)',
toc_usage: 'Использование',
toc_tests: 'Тесты',
toc_performance: 'Производительность',
online: 'Можно менять на лету: да',
},

View File

@@ -188,53 +188,6 @@
Правильный rdma_gid_index для RoCEv2, как правило, 1 (IPv6) или 3 (IPv4).
Опция неприменима к RDMA-CM (use_rdmacm).
- name: rdma_traffic_class
type: int
default: 0
info: |
Full TOS byte for RDMA traffic in the IP header (RoCEv2): DSCP in the
higher 6 bits and ECN in the lower 2 bits. For example, 106 (0x6A) =
DSCP 26 + ECT(0). Works with both libibverbs and RDMA-CM modes and should
be set on both OSDs and clients to mark traffic in both directions.
Default 0 = don't mark.
info_ru: |
Полный байт TOS для RDMA-трафика в заголовке IP (RoCEv2): DSCP в старших
6 битах и ECN в младших 2. Например, 106 (0x6A) = DSCP 26 + ECT(0).
Работает в обоих режимах RDMA - и libibverbs, и RDMA-CM; должен быть задан
и на OSD, и на клиентах, чтобы трафик помечался в обе стороны. По умолчанию
0 - не помечать.
- name: rdma_sl
type: int
default: 0
info: |
Service Level (802.1p priority analogue) for RDMA traffic, put into the
address vector of every connection. Only applies to the libibverbs mode:
in RDMA-CM mode the service level is derived from rdma_traffic_class.
Default 0 = as is without PFC-configured networks.
info_ru: |
Service Level (аналог приоритета 802.1p) для RDMA-трафика; попадает в
адресный вектор каждого соединения. Применим только к libibverbs-режиму:
в RDMA-CM уровень обслуживания выводится из rdma_traffic_class.
По умолчанию 0 = как в сетях без PFC.
- name: rdma_set_flow_label
type: bool
default: false
info: |
Automatically derive an IPv6 Flow Label for RDMA connections from the
queue pair number pair, so that every connection gets its own label and
connections spread over ECMP links while each connection keeps a single
stable path. Only applied to IPv6 GIDs: an IPv4-mapped GID
(::ffff:a.b.c.d) produces IPv4 packets which have no flow label. Only
applies to the libibverbs mode; RDMA-CM generates flow labels on its own.
Default false = don't set.
info_ru: |
Автоматически выводить IPv6 Flow Label для RDMA-соединений из пары
номеров очередей (QPN), чтобы каждое соединение получало свою метку,
соединения распределялись по ECMP-каналам, но каждое соединение шло по
одному стабильному пути. Применяется только к IPv6-GID: IPv4-mapped GID
(::ffff:a.b.c.d) даёт IPv4-пакеты, где поля flow label нет. Применим
только к libibverbs-режиму - RDMA-CM всегда генерирует flow label сам.
По умолчанию false = не задавать.
- name: rdma_mtu
type: int
info: |
@@ -405,29 +358,6 @@
параметра читается без дополнительного копирования. Вы можете попробовать
поменять этот параметр и посмотреть, как он влияет на производительность
случайного и линейного доступа.
- name: rxbounce
type: bool
default: false
online: true
info: |
Receive all TCP data into an intermediate buffer before copying to the actual
destination. Enabling this option is required for Windows guests if you use TCP
and [security.en.md#proto_checksums](proto_checksums) because Windows uses a
shared system-wide 'dummy page' to skip unneeded blocks during reads and breaks
checksum verification with it when data copy is avoided. rxbounce is only needed
when you use TCP and don't use transport encryption (AES-GCM); it's not required
with RDMA or with GCM encryption because both always copy data anyway (yes, RDMA
isn't actually zerocopy in Vitastor).
info_ru: |
Принимать все данные по TCP в промежуточный буфер и потом копировать их в
буфер клиента (ВМ). Это нужно для гостевых ОС Windows с TCP и включёнными
[security.ru.md#proto_checksums](proto_checksums), потому что Windows использует
одну разделяемую общесистемную страницу-заглушку для пропуска ненужных блоков
при чтении и ломает проверку контрольных сумм, если данные не копируются лишний
раз. Опция rxbounce нужна только если вы используете TCP и не используете
транспортное шифрование (AES-GCM); с RDMA или шифрованием опция не нужна, так
как в этом случае данные копируются всегда (да, RDMA в Vitastor на самом деле
не zero-copy).
- name: min_zerocopy_send_size
type: int
default: 32768

View File

@@ -39,12 +39,6 @@
info_ru: |
Доверенный TLS-сертификат для проверки сертификатов клиентов Vitastor.
Требуется для шифрования протокола Vitastor.
- name: admin_ca
type: string
info: |
Trusted TLS CA to verify Vitastor administrator certificates.
info_ru: |
Доверенный TLS-сертификат для проверки сертификатов администраторов Vitastor.
- name: osd_ca
type: string
info: |

View File

@@ -26,9 +26,9 @@ at Vitastor Kubernetes operator: https://github.com/Antilles7227/vitastor-operat
The instruction is very simple.
1. Download a Docker image of the desired version: \
`docker pull vitalif/vitastor:v3.2.2`
`docker pull vitalif/vitastor:v3.0.15`
2. Install scripts to the host system: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.2.2 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
3. Reload udev rules: \
`udevadm control --reload-rules`
4. Enable the vitastor-host service: \

View File

@@ -25,9 +25,9 @@ Vitastor можно установить в Docker/Podman. При этом etcd,
Инструкция по установке максимально простая.
1. Скачайте Docker-образ желаемой версии: \
`docker pull vitalif/vitastor:v3.2.2`
`docker pull vitalif/vitastor:v3.0.15`
2. Установите скрипты в хост-систему командой: \
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.2.2 install.sh`
`docker run --rm -it -v /etc:/host-etc -v /usr/bin:/host-bin vitalif/vitastor:v3.0.15 install.sh`
3. Перезагрузите правила udev: \
`udevadm control --reload-rules`
4. Включите сервис vitastor-host: \

View File

@@ -14,9 +14,12 @@
- Debian 13 (Trixie/Sid): `deb https://vitastor.io/debian trixie main`
- Debian 12 (Bookworm): `deb https://vitastor.io/debian bookworm main`
- Debian 11 (Bullseye): `deb https://vitastor.io/debian bullseye main`
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
- Debian 10 (Buster): `deb https://vitastor.io/debian buster main`
- Ubuntu 22.04 (Jammy): `deb https://vitastor.io/debian jammy main`
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
- Add `-oldstable` to bookworm/bullseye/buster in this line to install the last
stable version from 0.9.x branch instead of 1.x
- To always prefer vitastor-patched QEMU and Libvirt versions, add the following to `/etc/apt/preferences`:
```
Package: *
@@ -28,14 +31,17 @@
## CentOS
- Add Vitastor package repository:
- 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
- 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

View File

@@ -14,9 +14,12 @@
- Debian 13 (Trixie/Sid): `deb https://vitastor.io/debian trixie main`
- Debian 12 (Bookworm): `deb https://vitastor.io/debian bookworm main`
- Debian 11 (Bullseye): `deb https://vitastor.io/debian bullseye main`
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
- Debian 10 (Buster): `deb https://vitastor.io/debian buster main`
- Ubuntu 22.04 (Jammy): `deb https://vitastor.io/debian jammy main`
- Ubuntu 24.04 (Noble): `deb https://vitastor.io/debian noble main`
- Ubuntu 26.04 (Resolute): `deb https://vitastor.io/debian resolute main`
- Добавьте `-oldstable` к слову bookworm/bullseye/buster в этой строке, чтобы
установить последнюю стабильную версию из ветки 0.9.x вместо 1.x
- Чтобы всегда предпочитались версии пакетов QEMU и Libvirt с патчами Vitastor, добавьте в `/etc/apt/preferences`:
```
Package: *
@@ -28,14 +31,17 @@
## CentOS
- Добавьте в систему репозиторий Vitastor:
- 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: не нужно
- Включите 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: не нужно

View File

@@ -6,10 +6,10 @@
# Proxmox VE
To enable Vitastor support in Proxmox Virtual Environment (7.x-9.x are supported):
To enable Vitastor support in Proxmox Virtual Environment (6.4-9.x are supported):
- Add the corresponding Vitastor Debian repository into sources.list on Proxmox hosts:
trixie for 9.0+, bookworm for 8.1+, pve8.0 for 8.0, bullseye for 7.4, pve7.3 for 7.3, pve7.2 for 7.2, pve7.1 for 7.1
trixie for 9.0+, bookworm for 8.1+, pve8.0 for 8.0, bullseye for 7.4, pve7.3 for 7.3, pve7.2 for 7.2, pve7.1 for 7.1, buster for 6.4
- Install vitastor-client, pve-qemu-kvm, pve-storage-vitastor (* or see note) packages from Vitastor repository
- Define storage in `/etc/pve/storage.cfg` (see below)
- Block network access from VMs to Vitastor network (to OSDs and etcd),

View File

@@ -6,10 +6,10 @@
# Proxmox VE
Чтобы подключить Vitastor к Proxmox Virtual Environment (поддерживаются версии 7.x-9.x):
Чтобы подключить Vitastor к Proxmox Virtual Environment (поддерживаются версии 6.4-9.x):
- Добавьте соответствующий Debian-репозиторий Vitastor в sources.list на хостах Proxmox:
trixie для 9.0+, bookworm для 8.1+, pve8.0 для 8.0, bullseye для 7.4, pve7.3 для 7.3, pve7.2 для 7.2, pve7.1 для 7.1
trixie для 9.0+, bookworm для 8.1+, pve8.0 для 8.0, bullseye для 7.4, pve7.3 для 7.3, pve7.2 для 7.2, pve7.1 для 7.1, buster для 6.4
- Установите пакеты vitastor-client, pve-qemu-kvm, pve-storage-vitastor (* или см. сноску) из репозитория Vitastor
- Определите тип хранилища в `/etc/pve/storage.cfg` (см. ниже)
- Обязательно заблокируйте доступ от виртуальных машин к сети Vitastor (OSD и etcd), т.к. Vitastor (пока) не поддерживает аутентификацию

View File

@@ -1,220 +0,0 @@
[Documentation](../../README.md#documentation) → Tests → The PG chaos-test model
-----
[Читать на русском](pg-chaos.ru.md)
# The PG chaos-test model
A seed-driven stress test of the PG machinery (`src/test/test_pg_chaos.cpp`): ONE
real `osd_t` (the primary) with a real blockstore over a mock disk (entirely in
RAM, `ringloop_mock`) owns a single PG (number 1, pool 1, four 128 K objects, 4 K
granularity). The other PG members are fake secondary OSDs inside the test. The
pool is either replicated x3 (members 2,3, spare 4) or EC 2+2 (members 2..4,
spare 5, one object = one parity stripe). The seed uniquely determines the
configuration and the whole event sequence; a failing run is reproduced with
the same `--seed`.
What it mainly verifies is the PG machinery (the subop fan-out,
peering/LIST/rollback/stabilize, repeer daemons, stop_pg, INCOMPLETE, recovery) and
partly the primary OSD's storage; checking the storage is not the test's main goal -
test_blockstore_chaos covers it.
## Configurations (10)
- replicated/EC ×
- immediate_commit none/all ×
- csum_block none/4096/16384
`--recovery` enables recovery/rebalance after a set change (off by default so that
degraded states get exercised).
## The world model
Each object has an ordered version history; a version enters the model at SUBMIT
time (the delta = the request's exact bytes/length/granule bitmap), with a state:
- `V_SUBMITTED` - the op was sent and has no answer yet (in flight). It behaves
like V_OPTIONAL in every respect but one: concurrent reads are entitled not to
show this write's result, and that must not mark the version DEAD.
- `V_OPTIONAL` - the write was sent but not acknowledged: it either applied (then
as a whole) or did not apply at all. Versions reach this state from V_SUBMITTED
and, in the explicit-fsync modes, from V_ACKED when the client session dies
before a SYNC request (that counts as a marker that the write is unacknowledged).
Observing an OPTIONAL write's effect in a later read restores it to ACKED;
observing its absence (not visible over its own range) makes it DEAD.
- `V_ACKED` - the write was acknowledged; the model records `ack_live` (the live set
at ack time - used to check that the failure model wasn't exceeded). With
immediate_commit=all it is immediately considered on-disk durable. An ACKED write
must not roll back while the client's session is alive.
- `V_SYNCED` - acknowledged and SYNCed: durable on disk, and it may never roll back
again at all.
- `V_PARTIAL` - deleted, but possibly not from every OSD (the PG stayed LEFT_ON_DEAD).
- `V_DEAD` - the version was either definitely refused (-EAGAIN, possibly -ENOSPC in
the future), or it was OPTIONAL and it later became certain that it was refused
because a subsequent read did NOT observe it. A DEAD version showing again fails
the test.
Object contents are self-identifying: the pattern encodes the object, the version and
the offset, so a read result decodes back into "which version wrote these bytes".
Verification reads ONLY through the primary OSD and checks that the answer is
explained by a merge of ONE consistent subset of the written versions, where every
durable version is definitely present, every refused one is definitely absent, and
the contents never lie about their authorship. With an incomplete deletion, EC may
legitimately answer INCOMPLETE, and replicated pools may resurrect the object.
Everything derives from version states and events, with no extra flags: an ACKED
version cannot vanish except through an event that first breaks the session's
guarantee - a crash (taking the session down with the process) or a
repeer/stop_pg (which stop dirty sessions BEFORE peering starts).
The test's checks look ONLY at what the write path exposes to the client; the model
deliberately ignores things the client cannot know about, such as paired OSD
deaths per se.
## The simulated reality
- **Disk simulator**: requests carry delays; `power_loss()` = power outage -
uncommitted writes are lost, committed ones survive. An unknown FD/opcode aborts
the test (a stub guard).
- **etcd simulator**: keys/values + leases; revoke/TTL are emulated on a crash (the
OSD's keys are removed, its lease revoked).
- **Peer OSD simulator**: answers per the secondary-OSD/blockstore contract -
read/write/delete, SYNC, LIST. When a peer OSD dies, non-SYNCed writes die with it
with probability `peer_cache_loss`% (default 50 - modelling a volatile disk cache);
SYNCed ones always survive. A peer crash by itself does NOT change the world model:
the model only knows what the client sees. The crash repeers the PG, the repeer
kills dirty client sessions (volatile ACKED writes are exactly what makes a client
dirty), the session dies - and THAT is what the client observes
(PEER_STOPPED/EPIPE), and only session_died() demotes. If a crash rolled something
back under a live session, that would be a rollback invisible to the client -
check 5 catches it.
- **Network simulator**: service_peers runs every round. The request-phase delay:
a random `--p-wire`% (default 70) of each peer's queued subops gets applied; the
response-phase delay: a random `p_reply`% of the already-applied replies gets
delivered. A reply belongs to its connection: the connection died - undelivered
replies die with `-EPIPE` (the apply stays). When an OSD instance dies, however,
mutating ops still in `sent_ops` are copied into a separate "wire" queue; they are
applied later at the `--p-wire` rate WITHOUT an answer (the connection fell).
Non-mutating ops (READ/LIST) are dropped (nobody left to answer), and the harvested
queue is capped at 64 entries.
## Operations (one per step)
Each step picks an action by weight (`--p-*`; 0 disables it):
- **wait (500)** - send nothing, just run the simulation (event windows).
- **write (60)** - write a random range of a random object; bump the version; the
delta is remembered as OPTIONAL; at most max_inflight (8) in flight.
- **delete (6)** - delete a random object; the version is remembered as OPTIONAL with
zero length (a full delete); LEFT_ON_DEAD/ack are tracked in account().
- **read (40)** - read an object: whole with probability `full_read`%, otherwise an
arbitrary range; a whole read verifies the object.
- **sync (30)** - flush the cache (fsync); on confirmation the versions are marked
SYNCED (durable).
- **restart (1)** - a CLEAN OSD restart: drain the queue (all inflight finish), wait
for the disk to go idle, destroy, recreate, wait for the OSD to come up, then,
with `--p-post` (50%) chance, read and verify all object contents ("a clean restart
that lost nothing").
- **takeover (1)** - a PG handover: first stop the PG by pointing the primary at a
fake member in the etcd stub -> stop_pg stops the PG; right after the
`/pg/state/1/1` key disappears (meaning the OSD released the PG) -> recreate the OSD
and point the primary back at it -> re-verify the objects as in restart.
- **crash (2)** - the primary OSD crashes: a disk power loss (un-synced writes lost),
lease cancel + removal of the OSD's keys without cancelling the ops in flight to
peer OSDs, a new instance starts, objects re-verified.
- **peer_down (3)** - a peer OSD crashes: a victim is chosen that breaks no object's
quorum (all latest OPTIONAL versions down to the first SYNCED are checked), the peer
OSD stops and its connection drops.
- **peer_up (3)** - a peer OSD comes back (connects - if the PG or its history still
involves it); then settle(): wait for the PG to come up (ACTIVE|INCOMPLETE +
quiescence), then run recover_pending - deferred checks of objects whose chains may
have re-settled.
- **set_change (1)** - the PG moves to another OSD set through etcd; with
`--recovery` - recovery reads; without it, degradation is legal and the
"surviving copies" world is protected by the checks.
At the end - wait for completion: clean up until "the PG is healthy and every op has
finished", at most MAX_STEPS=1e6 steps -> exceeding that aborts with a "did not
settle" error.
## Op outcome accounting (account)
- Completed with the expected retval -> V_ACKED. A delete without LEFT_ON_DEAD closes
the partial-deletion window. A delete with LEFT_ON_DEAD must be repeated after the
peers come back, and in EC it allows a future INCOMPLETE unless the object is
rewritten in the meantime.
- Completed with EAGAIN -> V_DEAD: the version was definitely not applied; currently
unreachable in the test, kept as a placeholder for the future - it would also apply
to e.g. ENOSPC.
- Completed with EPIPE -> the version stays OPTIONAL: a refusal is not an ack, but the
op may have applied (or not). An optional delete must also be repeated after the
peers come back and likewise allows EC INCOMPLETE until the next rewrite.
- Completed with any other error -> the test aborts ("failed with retval").
## Checks (every abort point)
**Data consistency (world_check):**
1. world_check - the answer must be explained by a subset of the versions: every
required (durable) one is present; bytes are answered by exactly their own version
("record obj/seq/off mismatch", a bitmap granule "carries data from wrong
version").
2. "shows %s but the highest..." - an old version of the object was read while later
writes are lost (monotonicity broken).
3. "read shows seq over acked v's granule" - a granule's contents (obj/seq/off in the
record) contradict the version that owns the granule in the model: stitched or
torn contents instead of the acknowledged bytes.
4. "DEAD version showed again" - refused versions must not come back.
5. "committed version read fully absent over floor" / "established version read
absent over its granule" - acknowledged ops must not disappear.
6. A read -EIO "OSD deems unrecoverable" is legal in exactly one window: an
unfinished EC deletion. Outside it, -EIO aborts the test - including a "clean" ack
delete + INCOMPLETE after a revival (the OSD lied about completeness) and a chain
rollback under a live session with no deletion window; any other retval != len
aborts the test.
7. bitmap_len != the expected read answer length -> abort the test.
8. "came back INCOMPLETE" after set_change/crash - same criterion: an unfinished
deletion window makes INCOMPLETE legal and the deletion is repeated; anything
else aborts the test.
9. "came back ... which no world of the recorded versions explains" - the final
check of object contents after the test settles.
10. "changed across a clean restart that lost nothing" - object immutability across
a clean restart.
**Liveness/timing:**
11. "did not settle in MAX_STEPS" - the ops never completed within a huge step count
(a possible self-deadlock);
12. "operations did not complete" - stuck ops, printed in the dump; "a read of
object N did not complete in MAX_STEPS ... deadlock?"; "OSD did not load PGs
from etcd in MAX_STEPS" / "OSD did not finish peering in MAX_STEPS" - start-up
checks.
13. "did not release the PG" - the OSD didn't give the PG up when commanded;
14. "disk did not go idle" - a use-after-free guard: every disk op must finish
before the OSD is destroyed;
15. "PG did not become healthy after reviving every peer" - the PG stayed down after
all its OSDs came back;
16. "pending recovery/repeated deletion could not complete on a healthy PG" - a
deletion/recovery that never finished on a live PG.
**Test-stub guards:**
17. An unexpected SQE (fd/opcode) on the disk stub; "unexpected secondary opcode" on
a peer; arguments like `--p-reply >= 1`, `--max-inflight >= 1`, etc.
**Coverage checks:**
18. `peer_cache_loss<100` + a volatile config + >=8 peer deaths -> verify that the
variant where a peer OSD kept its journal ran at least once.
19. >=10 dirty OSD deaths/PG handovers -> wire_harvested > 0: at least one op of the
primary OSD that was in flight must have been harvested into the "wire" on the
way down - the "delivery from a dead OSD" path was exercised.
## Reporting and reproducibility
The OK line: writes(refused)/deletes/syncs/crashes/restarts/takeovers/peer
deaths(kept journal)/recoveries/set changes/verified reads/stragglers
(applied/captured/lost)/disk ops.
Determinism: one seed -> the same configuration and the same schedule; `-v` traces
disk and peer-OSD operations, op harvesting on peer deaths, and etcd changes.

View File

@@ -1,197 +0,0 @@
[Документация](../../README-ru.md#документация) → Тесты → Модель хаос-теста PG (`src/test/test_pg_chaos.cpp`)
-----
[Read in English](pg-chaos.en.md)
# Модель хаос-теста PG
Seed-driven стресс-тест PG-машинерии (`src/test/test_pg_chaos.cpp`): ОДИН настоящий
`osd_t` (primary) с настоящим хранилищем на заглушке диска (полностью в RAM, `ringloop_mock`)
владеет одной PG (номер 1, пул 1, объекты — 4 шт по 128 К, гранула 4 К). Остальные члены PG —
фейковые вторичные OSD внутри теста. PG: реплицированная x3 (members 2,3, spare 4) или
EC 2+2 (members 2..4, spare 5, объект = один parity-stripe). Сид однозначно задаёт
конфигурацию и всю последовательность событий; упавший прогон воспроизводится тем же `--seed`.
Проверяется в первую очередь PG-машинерия (subop-веер, peering/LIST/rollback/stabilize, repeer-демоны,
stop_pg, INCOMPLETE, recovery) и частично хранилище первичного OSD; однако, проверка хранилища —
не главная цель теста, его покрывает test_blockstore_chaos.
## Конфигурации (10)
- replicated/EC ×
- immediate_commit none/all ×
- csum_block нет/4096/16384
`--recovery` включает recovery/rebalance после set-change (по умолчанию выкл, чтобы
гонять degraded-состояния).
## Модель мира
Для каждого объекта — упорядоченная история версий; версия пишется в модель в момент
SUBMIT (дельта = точные байты/длина/битмап гранул запроса), состояние:
- `V_SUBMITTED` — операция отправлена, ответа ещё нет (выполняется). Ведёт себя как
V_OPTIONAL во всём, кроме одного: параллельные чтения вправе не показывать результат
этой записи, и это не будет маркировать её DEAD.
- `V_OPTIONAL` — запись отправлена, но не подтверждена, и может быть либо применена
(но тогда целиком), либо не применена вообще. Версии попадают в это состояние из
V_SUBMITTED и, в режимах с явными fsync, V_ACKED при гибели клиентской сессии до
запроса SYNC (это считается маркером, что запись не подтверждена).
Наблюдение эффекта OPTIONAL записи в последующем чтении восстанавливает её в ACKED;
наблюдение её отсутствия (когда она не видна в своём диапазоне) ⇒ DEAD.
- `V_ACKED` — запись подтверждена; запоминается `ack_live` (живой набор на момент ack —
используется для проверки превышения модели отказов). При immediate_commit=all сразу
считается зафиксированной на диске. ACKED запись не должна откатываться, пока сессия
клиента жива.
- `V_SYNCED` — подтверждена и SYNC-нута ⇒ зафиксирована на диске и более не должна
откатываться вообще.
- `V_PARTIAL` — удалена, но, возможно, не со всех OSD (PG осталась в LEFT_ON_DEAD).
- `V_DEAD` — версия либо точно отказана (-EAGAIN, возможно -ENOSPC в будущем), либо
была OPTIONAL, но после этого стало точно известно, что она откачена, так как последующее
чтение её уже НЕ наблюдало. Повторное появление DEAD-версии — провал теста.
Содержимое объектов самоидентифицируется версией: паттерн кодирует объект, версию и смещение —
прочитанное можно декодировать обратно в «какая версия записала эти байты».
Верификация читает ТОЛЬКО через первичный OSD и проверяет, что ответ объясняется слиянием
ОДНОГО согласованного подмножества записанных версий, где все зафиксированные точно присутствуют,
все откаченные точно отсутствуют, а содержимое не врёт об авторстве. При неполном удалении
разрешается INCOMPLETE в EC, а в репликах допускается воскрешение объекта.
Всё выводится из состояний версий и событий, без дополнительных флагов: ACKED не может
исчезнуть, кроме как через событие, которое сначала ломает гарантию сессии — краш (уносит
сессию вместе с процессом) или repeer/stop_pg (останавливают dirty-сессии ДО начала пиринга).
Проверки в тесте смотрят ТОЛЬКО на то, как процесс записи видит клиент, и не учитывают
в модели, например, падения парных OSD сами по себе, так как клиент не может о них знать.
## Симулируемая реальность
- **Симулятор диска**: запросы с задержками; `power_loss()` = потеря питания — незафиксированные
записи теряются, зафиксированные остаются. Неизвестный FD/opcode → падение теста (защита заглушки).
- **Симулятор etcd**: ключ/значение + lease; revoke/TTL эмулируются при крахе (ключи OSD'а
удаляются, lease отзывается).
- **Симулятор парных OSD**: отвечает в соответствии с контрактом вторичного OSD / blockstore.
Чтение/запись/удаление, SYNC, LIST. При падении парного OSD не-SYNC-нутые записи гибнут
с вероятностью `peer_cache_loss`% (по умолчанию 50 — моделирует волатильный кэш диска),
SYNC-нутые выживают всегда. Модель мира на краш пира сама НЕ реагирует: она знает
только то, что видит клиент. Краш реперит PG, репер убивает dirty-сессии клиентов
(volatile ACKED — это и есть dirty-клиент), сессия гибнет — вот это клиент наблюдает
(PEER_STOPPED/EPIPE), и только session_died() делает demote. Если краш откатит
что-то при живой сессии — это ненаблюдаемый клиентом откат, проверка 5 его ловит.
- **Симулятор сети**: service_peers, каждый раунд. Задержка на этапе запроса — применяются случайные
`--p-wire`% (по умолчанию 70) очередей subop'ов пира; задержка на этапе ответа — доставляются
случайные `p_reply`% применённых ответов. Ответ принадлежит соединению: соединение
умерло — недоставленные ответы гибнут с `-EPIPE` (применение остаётся). Однако, при
гибели экземпляра OSD мутирующие операции в `sent_ops` копируются в отдельную очередь
«сети»; применяются позже (`--p-wire`%) БЕЗ ответа (соединение упало). Немутирующие
операции (READ/LIST) отбрасываются (отвечать некому), очередь собранных операций
ограничивается 64.
## Операции (step)
На каждом шаге (веса `--p-*`; 0 выключает действие):
- **wait (500)** — ничего не слать, прокрутить симуляцию (событийные окна).
- **write (60)** — запись случайного диапазона случайного объекта; увеличение версии;
дельта запоминается как OPTIONAL; ≤ max_inflight (8) одновременно.
- **delete (6)** — удаление случайного объекта; версия запоминается как OPTIONAL с
нулевой длиной (полное удаление); LEFT_ON_DEAD/ack отслеживаются в account().
- **read (40)** — чтение объекта, полное с вероятностью `full_read`%, иначе произвольный
диапазон; при полном чтении объект проверяется.
- **sync (30)** — синхронизация кэша (fsync); при подтверждении версии помечаются SYNCED
(зафиксированными).
- **restart (1)** — ЧИСТАЯ перезагрузка OSD: очистка очереди (все inflight закончены), ожидание
простоя диска, уничтожение, пересоздание, ожидание подъёма OSD и, с шансом `--p-post`,
чтение и проверка содержимого всех объектов («чистый перезапуск, который ничего не потерял»).
- **takeover (1)** — передача PG: сначала остановка PG через смену первичного OSD на
фейковый в заглушке etcd → stop_pg останавливает PG; далее сразу после удаления ключа
`/pg/state/1/1` (означающего, что OSD остановил PG) → пересоздание OSD и его переустановка
в качестве первичного → перепроверка объектов, как в restart.
- **crash (2)** — падение первичного OSD: потеря питания диска (потеря не-синкнутых записей),
отмена лизы + удаление ключей OSD без отмены операций в полёте к парным OSD, запуск нового
экземпляра, перепроверка объектов.
- **peer_down (3)** — крах парного OSD: выбирается жертва, не ломающая кворум ни
одного объекта (проверяются все последние OPTIONAL версии до первой SYNCED), парный OSD
останавливается, соединение с ним разрывается.
- **peer_up (3)** — подъём парного OSD (соединение — если PG или её история его ещё
касается); после — settle(): ждать подъёма PG (ACTIVE|INCOMPLETE + покой), затем
прогнать recover_pending — отложенные проверки объектов, чьи цепи могли перегласоваться.
- **set_change (1)** — переезд PG на другой OSD-набор через etcd; с `--recovery` —
восстановительные чтения; без — деградация легальна, мир «выживших копий» защищён
проверками.
В конце — ожидание завершения: очистка до «PG в порядке и все операции завершены», максимум
до MAX_STEPS=1e6 шагов → если превышается, то падение с ошибкой «did not settle».
## Учёт результатов операций (account)
- Завершена с ожидаемым кодом возврата → V_ACKED. Удаление без LEFT_ON_DEAD закрывает
окно частичного удаления. Удаление с LEFT_ON_DEAD требует повтора после подъёма
пиров, а с EC допускает будущий INCOMPLETE, если объект не перезаписан вновь.
- Завершена с EAGAIN → V_DEAD: версия точно не применена; в тесте сейчас недостижимо,
оставлено как заготовка на будущее, также применимо, например, к ENOSPC.
- Завершена с EPIPE → версия остаётся OPTIONAL: отказ — не ack, но операция могла
примениться (а могла и нет). Опциональное удаление также требует повтора после
подъёма пиров и тоже разрешает EC INCOMPLETE до следующей перезаписи.
- Завершена с другой ошибкой → падение теста («failed with retval»).
## Проверки (все точки abort)
**Согласованность данных (world_check):**
1. world_check — ответ обязан объясняться подмножеством версий: все обязательные
(зафиксированные) присутствуют; байты отвечают ровно своей версией
(«record obj/seq/off mismatch», битмап-гранула «carries data from wrong version»).
2. «shows %s but the highest…» — прочитана старая версия объекта, последние записи
потеряны (монотонность нарушена).
3. «read shows seq over acked v's granule» — содержимое гранулы (obj/seq/off в record)
противоречит версии, которой гранула принадлежит по модели: сборное/порванное
содержимое вместо подтверждённых байт.
4. «DEAD version showed again» — откаченные версии не должны возвращаться.
5. «committed version read fully absent over floor» / «established version read absent
over its granule» — подтверждённые операции не должны исчезать.
6. read -EIO «OSD deems unrecoverable» — легален ровно в одном окне: незавершённое
EC-удаление. Вне его -EIO — падение теста, в т.ч. «чистый» ack удаления + INCOMPLETE
после revival (OSD соврал о полноте) и откат цепей при живой сессии без окна удаления;
прочие retval ≠ len → падение теста.
7. bitmap_len ≠ ожидаемому в ответах чтения → падение теста.
8. «came back INCOMPLETE» после set_change/crash — тот же критерий: окно незавершённого
удаления ⇒ INCOMPLETE легален и удаление повторяется; в ином случае → падение теста.
9. «came back … which no world of the recorded versions explains» — финальная проверка
корректности содержимого объектов после завершения теста.
10. «changed across a clean restart that lost nothing» — неизменность объектов после перезапуска.
**Живость/тайминг:**
11. «did not settle in MAX_STEPS» — операции не завершились за большое число шагов (возможна
самоблокировка);
12. «operations did not complete» — залипшие операции, печатаются в дампе;
«a read of object N did not complete in MAX_STEPS ... deadlock?»; «OSD did not load
PGs from etcd in MAX_STEPS» / «OSD did not finish peering in MAX_STEPS» — проверка на старте OSD.
13. «did not release the PG» — OSD не отпустил PG по команде;
14. «disk did not go idle» — проверка против use-after-free, что все операции диска завершены
перед уничтожением OSD;
15. «PG did not become healthy after reviving every peer» — PG не поднялась после подъёма всех OSD;
16. «pending recovery/repeated deletion could not complete on a healthy PG» — удаление/восстановление
не завершено на живой PG.
**Защиты тестовых заглушек:**
17. Неожиданный SQE (fd/opcode) на заглушке диска; «unexpected secondary opcode» у пира;
аргументы `--p-reply ≥ 1`, `--max-inflight ≥ 1` и т.п.
**Проверки покрытия:**
18. `peer_cache_loss<100` + volatile-конфиг + ≥8 смертей пира ⇒ проверка, что хотя бы
раз выполнен вариант, в котором парный OSD не потерял журнал.
19. ≥10 грязных смертей OSD/передач PG ⇒ wire_harvested > 0: хотя бы одна операция
первичного OSD, бывшая в полёте, обязана была собраться в «сеть» при остановке —
путь «доставки от мёртвого OSD» выполнен.
## Отчёт и воспроизводимость
OK-строка: writes(refused)/deletes/syncs/crashes/restarts/takeovers/peer deaths
(kept journal)/recoveries/set changes/verified reads/stragglers (applied/captured/lost)/disk ops.
Детерминизм: один seed ⇒ та же конфигурация и то же расписание; `-v` — трасса операций
диска и парных OSD, захватов операций при падении пиров и изменений etcd.

View File

@@ -196,7 +196,6 @@ Options can be specified in classic dd style (`key=value`) or like usual (`--key
| `iimg=<image>` | Copy from Vitastor image `<image>` |
| `if=<file>` | Copy from file `<file>` |
| `oimg=<image>` | Copy to Vitastor image `<image>` |
| `opool=<pool>` | Pool to create output image if it doesn't exist |
| `of=<file>` | Copy to file `<file>` |
| `bs=1M` | Set copy block size |
| `count=N` | Copy only N input blocks. If N ends in B it counts bytes, not blocks |
@@ -243,16 +242,15 @@ Flatten a layer, i.e. merge data and detach it from parents.
## rm-data
`vitastor-cli rm-data --pool <id|name> --inode <inode> [--wait-list] [--min-offset <offset>]`
`vitastor-cli rm-data --pool <pool> --inode <inode> [--wait-list] [--min-offset <offset>]`
Remove inode data without changing metadata. `<inode>` may be decimal or 0x hex.
Remove inode data without changing metadata.
```
--wait-list Retrieve full objects listings before starting to remove objects.
Requires more memory, but allows to show correct removal progress.
--min-offset Purge only data starting with specified offset.
--max-offset Purge only data before specified offset.
Offsets are in bytes or with a K/M/G/T unit suffix, decimal or 0x hex.
--client_wait_up_timeout 16 Timeout for waiting until PGs are up in seconds.
```
@@ -509,12 +507,15 @@ the Vitastor authorization model.
## modify-user
`vitastor-cli modify-user --groups <group1,group2,...> <username>`
`vitastor-cli modify-user --type <type> --groups <group1,group2,...> <username>`
Create or update a user with the given name (matching TLS certificate CN).
Options:
* `--type TYPE` - Set user type: `client` or `admin`. Default is `client`.
Administrators (`admin`) have full access to all cluster operations; clients (`client`)
have limited access based on ownership and group membership of images.
* `--groups GROUPS` - Set user's groups (comma-separated). Groups are used together with
`--owner-group` / `--reader-group` fields of images and `--creator_group` of the pool to
authorize operations. Pass an empty string to clear the groups.
@@ -533,25 +534,23 @@ Start an HTTP server that handles vitastor-cli commands over a REST API in JSON
Options:
| <!-- --> | <!-- --> |
|-----------------------|-------------------------------------------------------------------------------|
| `--bind_address ADDR` | Server IP address(es), separated by space. Default is `127.0.0.1`. |
| `--port 8080` | Server port. Default is `8080`. |
| `--api_cert FILE` | Server TLS certificate. |
| `--api_pkey FILE` | Private key for `--api_cert`. |
| `--cert FILE` | Administrator or client certificate used by the server to access the cluster. |
| `--pkey FILE` | Private key for `--cert`. |
| <!-- --> | <!-- --> |
|----------------------|-----------------------------------------------------------------------------|
| `--bind_address ADDR` | Server IP address(es), separated by space. Default is `127.0.0.1`. |
| `--port 8080` | Server port. Default is `8080`. |
| `--api_cert FILE` | Path to server TLS certificate file (PEM format). Required for HTTPS. |
| `--api_pkey FILE` | Path to server TLS private key file (PEM format). Required for HTTPS. |
| `--client_ca FILE` | Path to file with TLS CA certificates used to validate client connections. |
When `api_cert`/`api_pkey` are set the server operates in HTTPS mode. If `client_ca`
is also set (in configuration or on the command line), connecting clients are authenticated by their
TLS certificates. When [use_perms](../config/security.en.md#use_perms) is enabled, HTTPS with client
authentication is mandatory and per-user permissions are enforced. For regular clients the server only
allows operations on images that the user owns or has group access to; all other operations require
administrator privileges.
is also set, connecting clients are authenticated by their TLS certificates.
When [use_perms](../config/security.en.md#use_perms) is enabled, HTTPS with client
authentication is mandatory and per-user permissions are enforced.
The vitastor-cli process itself must be authenticated as an admin user (its own client
[cert](../config/security.en.md#cert) must be signed with [admin_ca](../config/security.en.md#admin_ca))
to be able to serve all requests.
`cert`/`pkey` must belong to a user of `type=admin`) to be able to serve requests.
For regular clients the server only allows operations on images that the user owns
or has group access to; all other operations require administrator rights.
`vitastor-cli serve` exposes a full OpenAPI specification at `/openapi` endpoint.
Start the server and check it out for the information about available API methods.

View File

@@ -203,7 +203,6 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
| `iimg=<image>` | Копировать из образа Vitastor `<image>` |
| `if=<file>` | Копировать из файла `<file>` |
| `oimg=<image>` | Копировать в образ Vitastor `<image>` |
| `opool=<pool>` | Пул для создания выходного образа, если он не существует |
| `of=<file>` | Копировать в файл `<file>` |
| `bs=1M` | Задать размер блока копирования |
| `count=N` | Копировать не более N блоков. Если N заканчивается на B - то N байт. |
@@ -252,16 +251,15 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
## rm-data
`vitastor-cli rm-data --pool <id|name> --inode <inode> [--wait-list] [--min-offset <offset>]`
`vitastor-cli rm-data --pool <pool> --inode <inode> [--wait-list] [--min-offset <offset>]`
Удалить данные инода, не меняя метаданные образов. `<inode>` можно задать в десятичном виде или в hex с 0x.
Удалить данные инода, не меняя метаданные образов.
```
--wait-list Сначала запросить полный листинг объектов, а потом начать удалять.
Требует больше памяти, но позволяет правильно печатать прогресс удаления.
--min-offset Удалять только данные, начиная с заданного смещения.
--max-offset Удалять только данные до (исключительно) заданного смещения.
Смещения задаются в байтах или с суффиксом K/M/G/T, в десятичном виде или в hex с 0x.
--client_wait_up_timeout 16 Время ожидания поднятия PG в секундах.
```
@@ -528,12 +526,15 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
## modify-user
`vitastor-cli modify-user --groups <group1,group2,...> <username>`
`vitastor-cli modify-user --type <type> --groups <group1,group2,...> <username>`
Создать или изменить пользователя с указанным именем (совпадающим с CN TLS-сертификата).
Опции:
* `--type TYPE` - Задать тип пользователя: `client` или `admin`. По умолчанию `client`.
Администраторы (`admin`) имеют полный доступ ко всем операциям кластера; клиенты
(`client`) имеют ограниченный доступ на основе владения образами и членства в группах.
* `--groups GROUPS` - Задать группы пользователя (через запятую). Группы используются
вместе с полями `owner-group` / `reader-group` образов и полем `--creator_group` пулов
для авторизации операций. Пустая строка очищает список групп.
@@ -552,25 +553,24 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
Опции:
| <!-- --> | <!-- --> |
|-----------------------|----------------------------------------------------------------------------------|
| `--bind_address ADDR` | IP-адрес(а) сервера через пробел. По умолчанию `127.0.0.1`. |
| `--port 8080` | Порт сервера. По умолчанию `8080`. |
| `--api_cert FILE` | TLS-сертификат сервера. |
| `--api_pkey FILE` | Закрытый ключ для сертификата `--api_cert`. |
| `--cert FILE` | Сертификат администратора/клиента, используемый сервером для доступа к кластеру. |
| `--pkey FILE` | Закрытый ключ для сертификата `--cert`. |
| <!-- --> | <!-- --> |
|-----------------------|------------------------------------------------------------------------------|
| `--bind_address ADDR` | IP-адрес(а) сервера через пробел. По умолчанию `127.0.0.1`. |
| `--port 8080` | Порт сервера. По умолчанию `8080`. |
| `--api_cert FILE` | Путь к файлу TLS-сертификата сервера (в формате PEM). Требуется для HTTPS. |
| `--api_pkey FILE` | Путь к файлу закрытого TLS-ключа сервера (PEM). Требуется для HTTPS. |
| `--client_ca FILE` | Путь к файлу с TLS CA-сертификатами для проверки подключений клиентов. |
Если заданы `api_cert`/`api_pkey`, сервер работает в режиме HTTPS. Если дополнительно
задан `client_ca` (в файле конфигурации или в командной строке), подключающиеся клиенты аутентифицируются
по своим TLS-сертификатам. Если включена опция [use_perms](../config/security.ru.md#use_perms), HTTPS с
аутентификацией клиентов обязателен, и права доступа проверяются для каждого пользователя. Обычным клиентам
сервер разрешает только операции с образами, которыми они владеют или к которым имеют доступ через группы;
остальные операции разрешены только администраторам.
задан `client_ca`, подключающиеся клиенты аутентифицируются по своим TLS-сертификатам.
Если включена опция [use_perms](../config/security.ru.md#use_perms), HTTPS с
аутентификацией клиентов обязателен, и права доступа проверяются для каждого пользователя.
Сам процесс vitastor-cli при этом должен использовать сертификат администратора ([cert](../config/security.en.md#cert),
подписанный [admin_ca](../config/security.en.md#admin_ca)), чтобы иметь возможность обслуживать
все виды запросов.
Сам процесс vitastor-cli при этом должен использовать сертификат (`cert`+`pkey`)
пользователя с правами администратора (`type=admin`), чтобы иметь возможность
обслуживать запросы. Обычным клиентам сервер разрешает только операции с образами,
которыми они владеют или к которым имеют доступ через группы; остальные операции
разрешены только администраторам.
`vitastor-cli serve` выставляет полную OpenAPI-спецификацию по адресу `/openapi`.
Запустите сервер и посмотрите её для подробной информации о доступных методах API.

View File

@@ -8,27 +8,15 @@
Vitastor has two file system implementations. Both can be used via `vitastor-nfs`.
Commands:
- [mount](#mount)
- [start](#start)
- [upgrade](#upgrade)
- [defrag](#defrag)
⚠️ Important: follow the instructions from [Linux NFS write size](#linux-nfs-write-size)
for optimal Vitastor NFS performance if you use EC and HDD and mount your NFS from Linux.
- [Pseudo-FS](#pseudo-fs)
- [VitastorFS](#vitastorfs)
- [Supported POSIX features](#supported-posix-features)
- [Limitations](#limitations)
- [Linux NFS](#linux-nfs)
- [Sync vs async safety](#sync-vs-async-safety)
- [Linux NFS readahead](#linux-nfs-readahead)
- [Linux NFS write size](#linux-nfs-write-size)
- [Horizontal scaling](#horizontal-scaling)
- [RDMA](#rdma)
- [Commands](#commands)
- [mount](#mount)
- [start](#start)
- [upgrade](#upgrade)
- [defrag](#defrag)
- [Mounting from /etc/fstab](#mounting-from-etc-fstab)
- [Common options](#common-options)
## Pseudo-FS
Simplified pseudo-FS proxy is used for file-based image access emulation. It's not
@@ -54,7 +42,7 @@ vitastor-nfs start --block --etcd_address 192.168.5.10:2379 --portmap 0 --port 2
To mount the FS exported by this server, run:
```
mount server:/ /mnt/ -o port=2050,mountport=2050,nfsvers=3,soft,sync,nolock,tcp
mount server:/ /mnt/ -o port=2050,mountport=2050,nfsvers=3,soft,nolock,tcp
```
## VitastorFS
@@ -117,57 +105,7 @@ Other notable missing features which should be addressed in the future:
in the DB. The FS is implemented is such way that this garbage doesn't affect its
function, but having a tool to clean it up still seems a right thing to do.
## Linux NFS
Linux NFS client has some specific features of behaviour important for Vitastor:
### Sync vs async safety
Linux NFS has 2 options which affect the mount mode: **soft/hard** and **sync/async**.
**⚠️ Warning: soft,async mode is dangerous and may lead to data loss!**
How these options work:
- In the **hard** mode all requests to the server are retried infinitely. If the NFS server becomes
unavailable, it becomes impossible to unmount the FS until you reboot the client host.
- In the **soft** each request is only tried 2 times (default **retrans** option value for TCP connections).
In this case, unmounting a dead FS is possible.
- In the **sync** mode all modification requests are sent to the server immediately. This is slower,
but allows the client to see all errors if they happen.
- In the **async** mode, modification requests are accumulated and sent to the server in the background
mode in parallel. It is faster, but it can hide write errors from the client. Instead of receiving an
error from a write operation, the client will only get an error when he closes the file (unsuccessful
close() call). Another problem is that there's no convenient way to set write parallelism for NFS, so
the client can overload the server.
That's exactly why **soft,async** is dangerous. When a large file is copied, the server is easily overwhelmed
with write requests and starts to respond slowly. Write requests time out, soft mode retries them only 1 time
and discards them, which results in parts of the file being lost.
So to recap: always use either **soft,sync** mode, or **hard,async**. By default, `vitastor-nfs mount` uses
**soft,sync** if mode options are not specified.
### Linux NFS readahead
By default, Linux uses rather small readahead value of 128 KB for NFS mounts.
For simple reading/copuing of files it's way too low because it stops VitastorFS from
using parallel reads.
However, you can increase readahead. You have 3 options:
1. Use the built-in `vitastor-nfs mount --readahead <SIZE>` option (for example, `--readahead 2M`).
2. Edit `[nfsrahead]` section in `/etc/nfs.conf` - add `nfs=2048` in it (global setting for all NFS 3.0 mounts).
3. Manually with the following command: \
`echo 2048 > /sys/class/bdi/$(findmnt -no MAJ:MIN /mnt/nfs 2>/dev/null | tr -d ' ')/read_ahead_kb`
Here, `2048` is the desired readahead in KB, and `/mnt/nfs` is your FS mountpoint.
You can easily see 2x-3x linear read improvement after you increase the parameter, so we
really urge you to try increasing it!
### Linux NFS write size
## Linux NFS write size
Linux NFS client (nfs/nfsv3/nfsv4 kernel modules) has a hard-coded maximum I/O size,
currently set to 1 MB - see `rsize` and `wsize` in [man 5 nfs](https://linux.die.net/man/5/nfs).
@@ -223,7 +161,7 @@ modprobe nfsv3
After these (not much complicated 🙂) manipulations NFS begins to be mounted
with new wsize and rsize by default and it fixes Vitastor-NFS linear write performance.
### Horizontal scaling
## Horizontal scaling
Linux NFS 3.0 client doesn't support built-in scaling or failover, i.e. you can't
specify multiple server addresses when mounting the FS.
@@ -262,7 +200,6 @@ Use regular `umount <MOUNTPOINT>` to unmount the FS.
The server will be automatically stopped when the FS is unmounted.
- `-o|--options <OPT>` - Pass additional NFS mount options (ex.: -o async).
- `--readahead <SIZE>` - Set Linux FS readahead to `<SIZE>`. Default is 128 KB, larger values increase sequential read speed (for example, try `--readahead 2M`).
### start
@@ -312,31 +249,6 @@ Options:
| `--include-empty` | Include old and empty volumes; make sure to restart NFS servers before using it |
| `--no-rm` | Move, but do not delete data |
## Mounting from /etc/fstab
Vitastor packages include mount helpers for `/etc/fstab`: **/usr/sbin/mount.vitastorfs** and **/usr/sbin/mount.vitastorblk**.
Both are simple symbolic links to `vitastor-nfs` and allow to handle `mount` calls correctly.
Example `/etc/fstab` entries:
```
myfs /S vitastorfs pool=fspool,readahead=2M,--etcd_address=10.0.2.15:2379,soft,sync 0 0
none /mnt/vitastor-block vitastorblk pool=ecpool 0 0
```
When you run `mount /S`, the helper is called internally and starts VitastorFS.
Helper call syntax is:
```
mount.vitastorfs <FSNAME> <MOUNTPOINT> -o <OPTS>
```
`<OPTS>` is comma-separated and includes all mount options from fstab. You can include
both Vitastor and NFS mount options in it. An option is treated as an NFS option if it
does not start with `--`, `pool=`, `readahead=` or if it doesn't contain `=` or `_`.
Other options are treated as Vitastor options.
## Common options
| <!-- --> | <!-- --> |

View File

@@ -8,27 +8,15 @@
В Vitastor есть две реализации файловой системы. Обе используются через `vitastor-nfs`.
Команды:
- [mount](#mount)
- [start](#start)
- [upgrade](#upgrade)
- [defrag](#defrag)
⚠️ Важно: для оптимальной производительности Vitastor NFS в Linux при использовании
HDD и EC (erasure кодов) выполните инструкции из раздела [Размер записи Linux NFS](#размер-записи-linux-nfs).
- [Псевдо-ФС](#псевдо-фс)
- [VitastorFS](#vitastorfs)
- [Поддерживаемые функции POSIX](#поддерживаемые-функции-posix)
- [Ограничения](#ограничения)
- [NFS в Linux](#nfs-в-linux)
- [Безопасность режимов sync и async](#безопасность-режимов-sync-и-async)
- [Упреждающее чтение Linux NFS](#упреждающее-чтение-linux-nfs)
- [Размер записи Linux NFS](#размер-записи-linux-nfs)
- [Горизонтальное масштабирование](#горизонтальное-масштабирование)
- [RDMA](#rdma)
- [Команды](#команды)
- [mount](#mount)
- [start](#start)
- [upgrade](#upgrade)
- [defrag](#defrag)
- [Монтирование из /etc/fstab](#монтирование-из-etc-fstab)
- [Общие опции](#общие-опции)
## Псевдо-ФС
Упрощённая реализация псевдо-ФС используется для эмуляции файлового доступа к блочным
@@ -54,7 +42,7 @@ vitastor-nfs start --block --etcd_address 192.168.5.10:2379 --portmap 0 --port 2
Примонтировать ФС, запущенную с такими опциями, можно следующей командой:
```
mount server:/ /mnt/ -o port=2050,mountport=2050,nfsvers=3,soft,sync,nolock,tcp
mount server:/ /mnt/ -o port=2050,mountport=2050,nfsvers=3,soft,nolock,tcp
```
## VitastorFS
@@ -121,58 +109,7 @@ JSON-формате :-). Для инспекции содержимого БД
записи. ФС устроена так, что на работу они не влияют, но для порядка и их стоит
уметь подчищать.
## NFS в Linux
NFS в Linux имеет некоторые важные для Vitastor особенности поведения:
### Безопасность режимов sync и async
В Linux NFS есть 2 опции, влияющие на режим монтирования: **soft/hard** и **sync/async**.
**⚠️ Внимание: сочетание soft,async опасно и может приводить к потере данных!**
Логика работы опций:
- В режиме **hard** все запросы к серверу повторяются бесконечно. Если NFS-сервер становится недоступен,
отмонтировать ФС становится невозможно до перезагрузки клиентской машины.
- В режиме **soft** каждый запрос повторяется только 2 раза (значение опции **retrans** по умолчанию для
TCP соединений). В этом случае отмонтировать умершую ФС можно.
- В режиме **sync** все запросы изменения отправляются на сервер сразу. Это медленнее, но зато любую
ошибку записи клиент видит сразу.
- В режиме **async** запросы изменения накапливаются и отправляются на сервер в фоновом режиме, параллельно.
Это быстрее, но ошибки записи могут быть незаметны для клиента и вместо ошибки записи клиент может
получить ошибку в момент закрытия файла (вызова close()). Помимо этого, в NFS нет нормального способа
регулировать параллелизм записи, поэтому клиент может легко перегружать сервер большим количеством команд.
Именно поэтому режим **soft,async** опасен. При копировании любого большого файла сервер перегружается и
начинает медленно отвечать. Команды записи заканчиваются таймаутом, soft режим делает 1 повторную попытку,
после чего забывает команды совсем. Часть блоков файла оказывается просто потерянной.
Резюме: всегда используйте либо режим **soft,sync**, либо **hard,async**. Если опции `vitastor-nfs mount`
не указаны, режим по умолчанию - **soft,sync**.
### Упреждающее чтение Linux NFS
По умолчанию Linux использует довольно небольшой размер упреждающего чтения для
файловых систем NFS - 128 КБ.
Для простого чтения/копирования файлов его не достаточно, потому что оно мешает VitastorFS
нормально утилизировать параллелизм чтения.
Однако, вы можете увеличить эту настройку целыми 3 способами:
1. Встроенной опцией `vitastor-nfs mount --readahead <SIZE>` (например, `--readahead 2M`).
2. Добавив в секцию `[nfsrahead]` файла конфигурации `/etc/nfs.conf` строку `nfs=2048` (глобальная
настройка для всех NFS 3.0 точек монтирования).
3. Вручную следующей командой: \
`echo 2048 > /sys/class/bdi/$(findmnt -no MAJ:MIN /mnt/nfs 2>/dev/null | tr -d ' ')/read_ahead_kb`
Здесь `2048` - размер readahead, а `/mnt/nfs` - путь к вашей ФС.
После увеличения параметра скорость линейного чтения легко может увеличиться в 2-3 раза, так что
советуем обязательно попробовать!
### Размер записи Linux NFS
## Размер записи Linux NFS
Клиент Linux NFS (модули ядра nfs/nfsv3/nfsv4) имеет фиксированный в коде максимальный
размер запроса ввода-вывода, равный 1 МБ - см. `rsize` и `wsize` в [man 5 nfs](https://linux.die.net/man/5/nfs).
@@ -232,7 +169,7 @@ modprobe nfsv3
монтироваться с новыми wsize и rsize, и производительность линейной записи в Vitastor-NFS
исправляется.
### Горизонтальное масштабирование
## Горизонтальное масштабирование
Клиент Linux NFS 3.0 не поддерживает встроенное масштабирование или отказоустойчивость.
То есть, вы не можете задать несколько адресов серверов при монтировании ФС.
@@ -272,9 +209,6 @@ VitastorFS из GPUDirect.
Сервер автоматически останавливается при отмонтировании ФС.
- `-o|--options <OPT>` - Передать дополнительные опции монтирования NFS (пример: -o async).
- `--readahead <SIZE>` - Устанавливает размер упреждающей записи ФС Linux в `<SIZE>`.
Значение по умолчанию 128 KB, бОльшие значения значительно улучшают скорость линейного чтения.
Попробуйте, например, `--readahead 2M`.
### start
@@ -327,32 +261,6 @@ VitastorFS из GPUDirect.
| `--include-empty` | Дефрагментировать старые и пустые тома; обязательно перезапустите NFS-сервера после использования этой опции |
| `--no-rm` | Перемещать, но не удалять данные |
## Монтирование из /etc/fstab
Пакеты Vitastor включают хелперы для монтирования из `/etc/fstab`: **/usr/sbin/mount.vitastorfs** и **/usr/sbin/mount.vitastorblk**.
Оба являются простыми символическими ссылками на `vitastor-nfs` и дают возможность
корректно обрабатывать вызовы монтирования утилитой `mount`.
Примеры записей `/etc/fstab`:
```
myfs /S vitastorfs pool=fspool,readahead=2M,--etcd_address=10.0.2.15:2379,soft,sync 0 0
none /mnt/vitastor-block vitastorblk pool=ecpool 0 0
```
Когда вы запускаете `mount /S`, внутри вызывается хелпер и запускает VitastorFS.
Синтаксис вызова самого хелпера:
```
mount.vitastorfs <FSNAME> <MOUNTPOINT> -o <OPTS>
```
`<OPTS>` - это набор опций, разделённых запятой, и в нём передаются все опции
монтирования из fstab. В него можно включать и опции Vitastor, и опции NFS. Опция
считается опцией NFS, если она не начинается с `--`, `pool=` и `readahead=`, а также
если она не содержит `=` или `_`. Все прочие опции считаются опциями Vitastor.
## Общие опции
| <!-- --> | <!-- --> |

View File

@@ -75,11 +75,13 @@ For a quick setup, use the `/usr/lib/vitastor/mon/make-etcd` script:
3. Run `/usr/lib/vitastor/mon/make-etcd` without parameters or with the `--antietcd-only`
parameter if you want to initialize the cluster with Antietcd only, without etcd.
4. The script will generate all necessary certificates and offer to copy them to the other
monitor nodes. Root ssh access to these nodes is required to do it, so agree if it's
available.
5. If you don't have root ssh access from the first node to other nodes, copy configuration
manually (vitastor.conf and all certificates/keys, except CA `*_ca.key`) and call
`/usr/lib/vitastor/mon/make-etcd` on every node.
monitor nodes (agree!).
5. Log in to all other monitor nodes and repeat the `/usr/lib/vitastor/mon/make-etcd` call there.
6. If you also have nodes with OSDs only (without monitors), run the following command to
copy only the required configuration to these nodes:
```
/usr/lib/vitastor/mon/make-etcd --copy-to-osd osdnode1,osdnode2,...
```
After that, you can proceed with OSD initialization.
@@ -160,28 +162,26 @@ When OSD transport encryption is enabled (at least for headers), you can enable
control by turning on the `use_perms=true` option. When this option is enabled, each user
(and even an OSD or a monitor) can perform only operations allowed for him.
Each user, administrator, OSD or monitor must have their own certificate signed by a specific
root certificate:
- `client_ca` for regular users. Common Name of the certificate must be equal to the user's
name. Regular users can only read and modify explicitly permitted images and can't modify
cluster state.
- `admin_ca` for administrators. Administrators can read and modify all images, and also
adminster the cluster: view overall statistics and status, create and delete OSDs, etc.
Common Name is not checked for admin users.
- `osd_ca` for OSDs and `mon_ca` for monitors (if Antietcd runs separately). OSDs and monitors
also have distinct permission sets.
Each user (or administrator) must have their own certificate signed by a common root
certificate for clients (`client_ca`), with a Common Name equal to the user name.
Privilege settings are stored in etcd. OSDs and monitors don't need user accounts;
they authenticate via separate certificates.
User privileges for regular users are stored in etcd data and currently consist of only
one property in `/vitastor/config/user/<name>`:
- `groups` - List of group names the user is a member of.
User privileges are stored in etcd data under the keys `/vitastor/config/user/<name>`.
The following is defined per user in this key:
- Type:
- Client (`type=client` or omitted) — can only read and modify explicitly permitted images.
- Administrator (`type=admin`) — can read and modify all images, and also administer the
cluster: view overall statistics and status, create and delete OSDs, etc.
- List of group names the user is a member of.
Images have the following properties:
- Owner (`owner`) — the user name that is allowed to both read and modify the image
- Owner group (`owner_group`) — the owner group name
- Reader group (`reader_group`) — the name of the group of users allowed to read the image
- Owner (owner) — the user name that is allowed to both read and modify the image
- Owner group (owner_group) — the owner group name
- Reader group (reader_group) — the name of the group of users allowed to read the image
And there is also a property on the pool:
- Creator group (`creator_group`) — the name of the group of users allowed to create images in the pool
- Creator group (creator_group) — the name of the group of users allowed to create images in the pool
For the list of allowed operations on image data on the OSD side, see the
[OSD data access rights](#osd-data-access-rights) section.
@@ -212,17 +212,15 @@ For the list of allowed operations with etcd data, see the
### Configuring OSD transport encryption
You need 3 certificates: one for OSDs, one for signing client certificates and one for
signing administrator certificates. For OSDs, you can use a self-signed certificate (osd_ca.crt)
or a separate certificate (osd.crt) signed by a trusted osd_ca.crt certificate. For clients,
you must use separate certificates signed by a common CA certificate (client_ca.crt). One more
CA certificate (admin_ca.crt) is required for signing administrator certificates.
You need 2 certificates: one for OSDs and one for signing all client certificates.
For OSDs, you can use a self-signed certificate (osd_ca.crt) or a separate certificate (osd.crt)
signed by a trusted osd_ca.crt certificate. For clients, you must use separate certificates
signed by a common trusted (client_ca.crt).
Add to the Vitastor configuration on OSD servers:
- use_perms: true
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
- osd_cert: osd_ca.crt
- osd_pkey: osd_ca.key
@@ -237,7 +235,8 @@ The following configuration options are available:
#### Mon with embedded Antietcd
The simplest option. Apart from OSD certificates, you only need 1 certificate for Antietcd (antietcd.crt).
The simplest option. You need 1 certificate for Antietcd (antietcd.crt), plus root
certificates for OSDs and clients.
Vitastor settings (`/etc/vitastor/vitastor.conf`):
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
@@ -248,7 +247,6 @@ Vitastor settings (`/etc/vitastor/vitastor.conf`):
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
#### Mon as an Etcd proxy
@@ -256,6 +254,7 @@ If you want to enable privileges, but stay on etcd, you can use etcd proxy mode.
You will need 2 separate certificates: one for etcd (etcd.crt) and one for antietcd (antietcd.crt).
The etcd client port must be different from the standard 2379 — for example, you can pick 2381.
OSD and client certificates are also needed.
Vitastor settings:
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
@@ -275,7 +274,6 @@ Vitastor settings:
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
etcd command-line options:
```
@@ -286,11 +284,11 @@ etcd command-line options:
#### Mon with a separate Antietcd Proxy
If in addition to the previous option you want to offload Vitastor monitor's tasks from Antietcd,
you can run it separately.
If in addition to the previous option you want to offload Antietcd from the Vitastor monitor's
tasks, you can run it separately.
Similar to the previous option, 2 certificates are needed: one for etcd and one for antietcd,
plus a separate certificate for the monitor.
plus separate certificates for clients, OSDs, and monitors will be needed.
Vitastor settings:
- etcd_address: [ "http://mon1:2379", ... ] (addresses of your monitors with port 2379)
@@ -299,18 +297,15 @@ Vitastor settings:
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
- mon_etcd_client_cert: mon_ca.crt
- mon_etcd_client_key: mon_ca.key
Antietcd command-line options:
```
--port 2379 --cert /etc/vitastor/antietcd.crt --key /etc/vitastor/antietcd.key \
--client_cert_auth 1 --auth_filter /usr/lib/vitastor/mon/vitastor_auth_filter.js \
--ca /etc/vitastor/client_ca.crt --admin_ca /etc/vitastor/admin_ca.crt \
--osd_ca /etc/vitastor/osd_ca.crt --mon_ca /etc/vitastor/mon_ca.crt \
--etcd_proxy url1,url2,... --etcd_ca /etc/vitastor/etcd.crt \
--etcd_cert /etc/vitastor/antietcd.crt --etcd_key /etc/vitastor/antietcd.key
--port 2379 \
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt \
--etcd_cert antietcd.crt --etcd_key antietcd.key --etcd_ca etcd.crt
```
etcd command-line options (same as in the previous option):
@@ -331,17 +326,15 @@ Vitastor settings (same as in the previous option):
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
- mon_etcd_client_cert: mon_ca.crt
- mon_etcd_client_key: mon_ca.key
Antietcd command-line options:
```
--port 2379 --cert /etc/vitastor/antietcd.crt --key /etc/vitastor/antietcd.key \
--client_cert_auth 1 --auth_filter /usr/lib/vitastor/mon/vitastor_auth_filter.js \
--ca /etc/vitastor/client_ca.crt --admin_ca /etc/vitastor/admin_ca.crt \
--osd_ca /etc/vitastor/osd_ca.crt --mon_ca /etc/vitastor/mon_ca.crt \
--persist_filter /usr/lib/vitastor/mon/vitastor_persist_filter.js
--port 2379 \
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js \
--persist_filter vitastor_persist_filter.js \
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt
```
### Vault/OpenBao setup
@@ -462,7 +455,7 @@ Step-by-step instructions for setting up a test Vault using OpenBao as an exampl
Below, all key names are given without the common prefix `/vitastor`.
Allowed operations with keys in Antietcd for regular users:
Allowed operations with keys in Antietcd for clients (`type=client`):
- Read-only:
- Always allowed:
- `/config/global`
@@ -483,7 +476,7 @@ Allowed operations with keys in Antietcd for regular users:
- `/config/inode/*`
- `/index/image/*`
Allowed operations with keys in Antietcd for administrators:
Allowed operations with keys in Antietcd for administrators (`type=admin`):
- Read:
- `/stats`
- `/mon/*`
@@ -495,10 +488,7 @@ Allowed operations with keys in Antietcd for administrators:
- `/config/*`
- `/osd/*`
- `/index/*`
- `/pg/config`
- `/pg/history/*`
- `/stats`
- `/history/last_clean_pgs`
Allowed operations with keys in etcd for OSDs:
- Read:
@@ -521,8 +511,6 @@ Allowed operations with keys in etcd for monitors:
- `/history/last_clean_pgs`
- `/mon/*`
- `/pg/history/*`
- `/pg/stats/*`
- `/pgstats/*`
- `/inode/stats/*`
- `/pool/stats/*`
@@ -559,21 +547,20 @@ Cluster operations — allowed only for other OSDs:
[vitastor-cli serve](../usage/cli.en.md#serve) also supports client authentication
via certificates. Only certificates signed by `client_ca` are accepted. A separate
certificate `api_cert` with the key `api_pkey` is used as the server certificate.
certificate `server_cert` with the key `server_pkey` is used as the server certificate.
For `vitastor-cli serve` to work correctly, it must use a certificate of an administrator
(`cert`+`pkey` signed by `admin_ca`) to access Vitastor and serve all requests correctly.
For `vitastor-cli serve` to work correctly, it itself must use a certificate
(`cert`+`pkey`) of a user with administrator rights (`type=admin`) to access Vitastor.
Regular users (with `client_ca`-signed certificates), when accessing the API, are only
allowed operations on images available to them either for reading (for read-only operations)
or for writing (for modification):
Regular clients (users with `type=client`), when accessing the API, are only allowed API operations on images
available to them either for reading (for read-only operations) or for writing (for modification):
- image/list — for images the user can read.
- image/create — for pools in which the user is allowed to create images, or for
creating snapshots of images owned by the user.
- image/delete, image/flatten, image/modify — for images owned by the user.
All other operations are allowed only for administrators.
All other operations are allowed only for administrators (`type=admin`).
## Encryption performance

View File

@@ -75,11 +75,13 @@
3. Запустите `/usr/lib/vitastor/mon/make-etcd` без параметров или с параметром `--antietcd-only`,
если хотите инициализировать кластер только с Antietcd без etcd.
4. Скрипт сгенерирует все необходимые сертификаты и предложит скопировать их на остальные узлы
мониторов. Для этого нужен root ssh доступ к этим узлам. Если он есть - соглашайтесь, скрипт
автоматически скопирует конфигурацию и инициализирует все etcd и мониторы.
5. Если root ssh доступа с первого узла на остальные узлы мониторов нет - скопируйте на них конфигурацию
вручную (vitastor.conf и все сертификаты и ключи, кроме ключей CA `*_ca.key`) и повторите на каждом
узле вызов `/usr/lib/vitastor/mon/make-etcd`.
мониторов (соглашайтесь!).
5. Зайдите на все остальные узлы мониторов и повторите там вызов `/usr/lib/vitastor/mon/make-etcd`.
6. Если у вас будут узлы только с OSD без мониторов, выполните следующую команду, чтобы скопировать
только нужную конфигурацию на эти узлы:
```
/usr/lib/vitastor/mon/make-etcd --copy-to-osd osdnode1,osdnode2,...
```
После этого можете переходить к инициализации OSD.
@@ -129,7 +131,7 @@
Ключи шифрования образов могут храниться либо в etcd, либо во внешнем Vault. Во втором случае
в etcd хранятся только ID ключей, а Vitastor вообще не имеет доступа к данным образов. Для
использования Vault нужно создать образ с опцией `--enc_key vault:ID`, в конфигурации указать
опции vault_url и vault_ca, создать всем клиентам учётные записи в Vault и дать им доступ
опции vault_url, и vault_ca, создать всем клиентам учётные записи в Vault и дать им доступ
к требуемым секретам v1.
Ещё раз повторимся, что если AES-XTS используется с полным шифрованием трафика (`proto_checksums=gcm`),
@@ -160,28 +162,27 @@
контроль доступа, включив опцию `use_perms=true`. При включённой опции каждый пользователь
(и даже OSD и монитор) может выполнять только те операции, которые ему разрешены.
Каждый обычный пользователь, администратор, OSD или монитор должен иметь свой сертификат,
подписанный отдельным корневым сертификатом:
- Для обычных пользователей - `client_ca`. Common Name сертификата при этом должен быть равен
имени пользователя. Обычные пользователи могут только читать и модифицировать явным образом
разрешённые им образы и не могут менять общее состояние кластера.
- Для администраторов - `admin_ca`. Администраторы могут читать и модифицировать все образы,
а также администрировать кластер: смотреть общую статистику и состояние, создавать и удалять
OSD и так далее.
- Для OSD - `osd_ca`, для мониторов - `mon_ca` (если Antietcd запускаются отдельно от мониторов).
OSD и мониторы тоже имеют свои наборы разрешённых операций.
Каждый пользователь (или администратор) должен иметь свой сертификат, подписанный общим
корневым сертификатом для клиентов (`client_ca`), с Common Name, равным имени пользователя.
Настройки привилегий же хранятся в etcd. Для OSD и мониторов учётные записи не нужны,
они аутентифицируются по отдельным сертификатам.
Настройки привилегий для обычных пользователей хранятся в etcd и на данный момент состоят
всего из одного свойства в ключах `/vitastor/config/user/<имя>`:
- `groups` - Список имён групп, членом которых пользователь является.
Привилегии пользователей хранятся в данных etcd в ключах `/vitastor/config/user/<имя>`.
В этом ключе для каждого пользователя задаётся:
- Тип:
- Клиент (`type=client` или не указано) - может читать и модифицировать только явным образом
разрешённые образы.
- Администратор (`type=admin`) - может читать и модифицировать все образы, а также администрировать
кластер: смотреть общую статистику и состояние, создавать и удалять OSD и так далее.
- Список имён групп, членом которых пользователь является.
У образов есть следующие свойства:
- Владелец (`owner`) - имя пользователя, которому разрешено и читать, и менять образ
- Группа владельцев (`owner_group`) - имя группы владельцев
- Группа читателей (`reader_group`) - имя группы пользователей, которым разрешено читать образ
- Владелец (owner) - имя пользователя, которому разрешено и читать, и менять образ
- Группа владельцев (owner_group) - имя группы владельцев
- Группа читателей (reader_group) - имя группы пользователей, которым разрешено читать образ
И также есть свойство у пула:
- Группа создателей (`creator_group`) - имя группы пользователей, которым разрешено создавать образы в пуле
- Группа создателей (creator_group) - имя группы пользователей, которым разрешено создавать образы в пуле
Перечень разрешённых операций с данными образов на стороне OSD смотрите в разделе
[Права доступа к данным OSD](#права-доступа-к-данным-osd).
@@ -213,23 +214,20 @@
### Настройка шифрования соединений OSD
Вам нужно 3 сертификата: один для OSD и по одному для подписи сертификатов клиентов и администраторов.
Вам нужно 2 сертификата: один для OSD и один для подписи сертификатов всех клиентов.
Для OSD можно использовать самоподписанный сертификат (osd_ca.crt) или отдельный сертификат (osd.crt),
подписанный доверенным сертификатом osd_ca.crt. Для клиентов нужно использовать отдельные
сертификаты, подписанные общим доверенным (client_ca.crt), для администраторов - отдельным доверенным
(admin_ca.crt).
сертификаты, подписанные общим доверенным (client_ca.crt).
В конфигурацию Vitastor на серверах OSD нужно добавить:
- use_perms: true
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
- osd_cert: osd_ca.crt
- osd_pkey: osd_ca.key
На стороне клиентов:
- use_perms: true
- osd_ca: osd_ca.crt
- cert: client.crt
- pkey: client.key
@@ -239,7 +237,8 @@
#### Mon со встроенным Antietcd
Самый простой вариант. Кроме сертификатов OSD вам нужен всего 1 сертификат для Antietcd (antietcd.crt).
Самый простой вариант. Вам нужен 1 сертификат для Antietcd (antietcd.crt), плюс
корневые сертификаты для OSD и клиентов.
Настройки Vitastor (`/etc/vitastor/vitastor.conf`):
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
@@ -250,7 +249,6 @@
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
#### Mon в роли Etcd proxy
@@ -258,6 +256,7 @@
Вам понадобится 2 отдельных сертификата: один для etcd (etcd.crt) и один для antietcd (antietcd.crt).
Клиентский порт etcd должен отличаться от стандартного 2379, например, можно выбрать 2381.
Также нужны сертификаты OSD и клиентов.
Настройки Vitastor:
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
@@ -277,7 +276,6 @@
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
Опции командной строки etcd:
```
@@ -291,8 +289,8 @@
Если в дополнение к предыдущему варианту вы хотите разгрузить Antietcd от задач монитора Vitastor,
можно запустить его отдельно.
Аналогично предыдущему варианту нужно 2 сертификата: один для etcd и один для antietcd, плюс понадобится
отдельный сертификат для монитора.
Аналогично предыдущему варианту нужно 2 сертификата: один для etcd и один для antietcd, плюс понадобятся
отдельные сертификаты для клиентов, OSD и монитора.
Настройки Vitastor:
- etcd_address: [ "http://mon1:2379", ... ] (адреса ваших мониторов с портом 2379)
@@ -301,18 +299,15 @@
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
- mon_etcd_client_cert: mon_ca.crt
- mon_etcd_client_key: mon_ca.key
Опции командной строки Antietcd:
```
--port 2379 --cert /etc/vitastor/antietcd.crt --key /etc/vitastor/antietcd.key \
--client_cert_auth 1 --auth_filter /usr/lib/vitastor/mon/vitastor_auth_filter.js \
--ca /etc/vitastor/client_ca.crt --admin_ca /etc/vitastor/admin_ca.crt \
--osd_ca /etc/vitastor/osd_ca.crt --mon_ca /etc/vitastor/mon_ca.crt \
--etcd_proxy url1,url2,... --etcd_ca /etc/vitastor/etcd.crt \
--etcd_cert /etc/vitastor/antietcd.crt --etcd_key /etc/vitastor/antietcd.key
--port 2379 \
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js --etcd_proxy url1,url2,... \
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt \
--etcd_cert antietcd.crt --etcd_key antietcd.key --etcd_ca etcd.crt
```
Опции командной строки etcd (не отличаются от предыдущего варианта):
@@ -333,17 +328,15 @@
- etcd_ca: antietcd.crt
- osd_ca: osd_ca.crt
- client_ca: client_ca.crt
- admin_ca: admin_ca.crt
- mon_etcd_client_cert: mon_ca.crt
- mon_etcd_client_key: mon_ca.key
Опции командной строки Antietcd:
```
--port 2379 --cert /etc/vitastor/antietcd.crt --key /etc/vitastor/antietcd.key \
--client_cert_auth 1 --auth_filter /usr/lib/vitastor/mon/vitastor_auth_filter.js \
--ca /etc/vitastor/client_ca.crt --admin_ca /etc/vitastor/admin_ca.crt \
--osd_ca /etc/vitastor/osd_ca.crt --mon_ca /etc/vitastor/mon_ca.crt \
--persist_filter /usr/lib/vitastor/mon/vitastor_persist_filter.js
--port 2379 \
--client_cert_auth 1 --auth_filter vitastor_auth_filter.js \
--persist_filter vitastor_persist_filter.js \
--cert antietcd.crt --key antietcd.key --ca client_ca.crt --osd_ca osd_ca.crt --mon_ca mon_ca.crt
```
### Настройка Vault/OpenBao
@@ -465,7 +458,7 @@
Ниже все названия ключей приведены без общего префикса `/vitastor`.
Разрешённые операции с ключами в Antietcd для обычных пользователей:
Разрешённые операции с ключами в Antietcd для клиентов (`type=client`):
- Только чтение:
- Разрешено всегда:
- `/config/global`
@@ -486,7 +479,7 @@
- `/config/inode/*`
- `/index/image/*`
Разрешённые операции с ключами в Antietcd для администраторов:
Разрешённые операции с ключами в Antietcd для администраторов (`type=admin`):
- Чтение:
- `/stats`
- `/mon/*`
@@ -498,10 +491,7 @@
- `/config/*`
- `/osd/*`
- `/index/*`
- `/pg/config`
- `/pg/history/*`
- `/stats`
- `/history/last_clean_pgs`
Разрешённые операции с ключами в etcd для OSD:
- Чтение:
@@ -524,8 +514,6 @@
- `/history/last_clean_pgs`
- `/mon/*`
- `/pg/history/*`
- `/pg/stats/*`
- `/pgstats/*`
- `/inode/stats/*`
- `/pool/stats/*`
@@ -563,22 +551,22 @@
[vitastor-cli serve](../usage/cli.ru.md#serve) также поддерживает клиентскую
аутентификацию по сертификатам. Принимаются только сертификаты, подписанные
`client_ca`. В качестве серверного сертификата используется отдельный сертификат
`api_cert` с ключом `api_pkey`.
`server_cert` с ключом `server_pkey`.
При этом для корректной работы `vitastor-cli serve` он сам должен использовать
для доступа в Vitastor сертификат администратора (`cert`+`pkey`, подписанный `admin_ca`),
чтобы корректно обрабатывать любые запросы.
для доступа в Vitastor сертификат (`cert`+`pkey`) пользователя с правами
администратора (`type=admin`).
Обычным пользователям (с сертификатами, подписанными `client_ca`) при доступе к API
разрешаются только API-операции с образами, доступными им либо на чтение (для чтения),
либо на запись (для модификации):
Обычным клиентам (пользователям с `type=client`) при доступе к API разрешаются
только API-операции с образами, доступными им либо на чтение (для чтения), либо
на запись (для модификации):
- image/list - для образов, которые пользователь может читать.
- image/create - для пулов, в которых пользователю разрешено создавать образы, либо
для создания снимков образов, которыми пользователь владеет.
- image/delete, image/flatten, image/modify - для образов, которыми пользователь владеет.
Все остальные операции разрешаются только администраторам.
Все остальные операции разрешаются только администраторам (`type=admin`).
## Производительность шифрования

View File

@@ -85,7 +85,6 @@ class AntiEtcdAdapter
antietcd_config.ca = config.client_ca;
antietcd_config.osd_ca = config.osd_ca;
antietcd_config.mon_ca = config.mon_ca;
antietcd_config.admin_ca = config.admin_ca;
}
for (const key in config)
{

View File

@@ -65,8 +65,6 @@ const etcd_tree = {
placement_levels: { datacenter: 1, rack: 2, host: 3, osd: 4, ... },
use_old_pg_combinator: false,
osd_backfillfull_ratio: 0.99,
mon_metrics_path: '/prometheus',
mon_healthcheck_path: '/healthcheck',
// client and osd
tcp_header_buffer_size: 65536,
use_sync_send_recv: false,

View File

@@ -87,7 +87,7 @@ class Mon
this.http = await create_http_server(this.config, (req, res) =>
{
const u = new URL(req.url, 'http://'+(req.headers.host || 'localhost'));
if (u.pathname.replace(/\/+$/, '') == (this.config.mon_metrics_path||'/metrics'))
if (u.pathname.replace(/\/+$/, '') == (this.config.prometheus_path||'/metrics'))
{
if (!this.watcher_active)
{
@@ -100,19 +100,6 @@ class Mon
res.write(export_prometheus_metrics(this.state));
}
}
else if (u.pathname.replace(/\/+$/, '') == (this.config.mon_healthcheck_path||'/healthcheck'))
{
if (!this.watcher_active)
{
res.writeHead(503);
res.write('Monitor is in standby mode. Please retrieve metrics from master monitor instance\n');
}
else
{
res.setHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
res.write('Monitor is active');
}
}
else
{
res.writeHead(404);

View File

@@ -1,6 +1,6 @@
{
"name": "vitastor-mon",
"version": "3.2.2",
"version": "3.0.15",
"description": "Vitastor SDS monitor service",
"main": "mon-main.js",
"scripts": {
@@ -9,7 +9,7 @@
"author": "Vitaliy Filippov",
"license": "UNLICENSED",
"dependencies": {
"antietcd": "^1.3.3",
"antietcd": "^1.3.1",
"sprintf-js": "^1.1.2",
"ws": "^7.2.5"
},

View File

@@ -3,9 +3,6 @@
const { parse_level_indexes, parse_pg_dsl } = require('./lp_optimizer/dsl_pgs.js');
const MIN_DATA_BLOCK_SIZE = 4096;
const MAX_DATA_BLOCK_SIZE = 128*1024*1024;
function validate_pool_cfg(pool_id, pool_cfg, placement_levels, warn)
{
pool_cfg.pg_size = Math.floor(pool_cfg.pg_size);
@@ -26,8 +23,7 @@ function validate_pool_cfg(pool_id, pool_cfg, placement_levels, warn)
console.log('Pool '+pool_id+' has invalid coding scheme (one of "xor", "replicated", "ec" and "jerasure" required)');
return false;
}
// OSDs don't support pg_size > 64 for now
if (!pool_cfg.pg_size || pool_cfg.pg_size < 1 || pool_cfg.pg_size > 64 ||
if (!pool_cfg.pg_size || pool_cfg.pg_size < 1 || pool_cfg.pg_size > 256 ||
pool_cfg.scheme !== 'replicated' && pool_cfg.pg_size < 3)
{
if (warn)
@@ -54,18 +50,6 @@ function validate_pool_cfg(pool_id, pool_cfg, placement_levels, warn)
console.log('Pool '+pool_id+' has invalid parity_chunks (must be between 1 and pg_size-2)');
return false;
}
if (pool_cfg.block_size &&
(!Number(pool_cfg.block_size) || (pool_cfg.block_size & (pool_cfg.block_size-1)) ||
pool_cfg.block_size < MIN_DATA_BLOCK_SIZE || pool_cfg.block_size > MAX_DATA_BLOCK_SIZE ||
pool_cfg.scheme !== 'replicated' && pool_cfg.block_size*pool_cfg.pg_size > 0xFFFFFFFF))
{
if (warn)
{
console.log('Pool '+pool_id+' has invalid block_size (must be a power of two between '+
MIN_DATA_BLOCK_SIZE+' and '+MAX_DATA_BLOCK_SIZE+'; must not exceed 2^32-1 when multiplied by pg_size)');
}
return false;
}
if (!pool_cfg.pg_count || pool_cfg.pg_count < 1)
{
if (warn)

View File

@@ -9,6 +9,8 @@ const os = require('os');
const path = require('path');
const readline = require('readline');
run().catch(e => { console.error(e); process.exit(1); });
const help_text = `Initialize a Vitastor cluster (etcd, vitastor.conf and TLS certificates)
(c) Vitaliy Filippov, 2026+ (MIT)
@@ -20,8 +22,7 @@ USAGE:
2) Run: ${process.argv[1]} [./vitastor.conf] [--antietcd-only]
You can run it on etcd/monitor nodes or on an external node.
It configures etcd, generates TLS certificates (on the first or external node), copies
them to other etcd/monitor nodes using root ssh, and updates vitastor.conf with TLS options.
You can also copy configuration and certificates manually.
them to other etcd/monitor nodes, and updates vitastor.conf with TLS options.
3) If you have OSD-only nodes, run:
${process.argv[1]} --copy-to-osd-node NODE_NAME ./vitastor.conf
It copies vitastor.conf and required TLS certificates to that node.
@@ -43,7 +44,7 @@ OPTIONS:
async function run()
{
let config_path = '/etc/vitastor/vitastor.conf';
let config_dir = '/etc/vitastor';
let config_dir = '/etc/vitastor/';
let gen_certs = 'auto';
let antietcd_only = false;
let copy = 'ask';
@@ -110,7 +111,7 @@ async function run()
.filter(s => s)
.map(s => ({
scheme: s[1],
addr: s[2].indexOf(':') >= 0 && s[2][0] != '[' ? '['+s[2]+']' : s[2],
addr: s[2].indexOf(':') && s[2][0] != '[' ? '['+s[2]+']' : s[2],
ip: s[2][0] == '[' ? s[2].substr(1, s[2].length-2) : s[2],
port: s[3],
}));
@@ -124,7 +125,7 @@ async function run()
}
if (tls)
{
const etcd_ca = config_dir+'/'+path.basename(config.etcd_ca || '/etc/vitastor/etcd_ca.crt');
const etcd_ca = config_dir+'/'+path.basename(config.etcd_ca);
if (gen_certs === true)
{
console.log('Certificate generation is requested explicitly, generating');
@@ -157,24 +158,18 @@ async function run()
if (gen_certs)
{
if (copy === 'ask')
copy = await ask_copy('Copy certificates and vitastor.conf to other nodes and start them (requires root ssh)? ');
copy = await ask_copy('Copy certificates and vitastor.conf to other nodes after generation?');
copy = (copy === 'y' || copy === 'yes');
await make_certs(config_dir, copy, etcds, num, use_perms, antietcd_only);
await make_certs(config_dir, copy, etcds, use_perms, antietcd_only);
}
}
else if (num <= 0)
{
if (copy === 'ask')
copy = await ask_copy('Copy vitastor.conf to other nodes and start them (requires root ssh)? ');
if (copy === 'y' || copy === 'yes')
await copy_nontls(config_dir, etcds, num);
}
if (num < 0)
{
console.log('No matching IPs in etcd_address from '+config_path);
process.exit(tls && gen_certs ? 0 : 1);
}
await configure_etcd(etcds, num, tls, use_perms);
await enable_mon();
process.exit(0);
}
@@ -206,10 +201,10 @@ async function copy_to_osd_nodes(to, dir, use_perms, antietcd_only)
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
console.warn('Copying configuration to OSD nodes '+to.join(', '));
for (const node of to)
await system("scp "+dir+"/"+osd_to_copy.join(" "+dir+"/")+" root@"+(node.indexOf(':') >= 0 ? "["+node+"]" : node)+":/etc/vitastor/");
await system("scp "+dir+osd_to_copy.join(" "+dir)+" root@"+node+":/etc/vitastor/");
}
async function make_certs(dir, copy, etcds, num, use_perms, antietcd_only)
async function make_certs(dir, copy, etcds, use_perms, antietcd_only)
{
console.log(`-----
Generating certificates in ${dir}
@@ -219,27 +214,26 @@ Generating certificates in ${dir}
const osd_to_copy = [ 'vitastor.conf' ];
if (!antietcd_only)
{
await make_ca("/O=Vitastor etcd CA", dir+"/etcd_ca");
await make_signed("/CN=Vitastor etcd", dir+"/etcd", dir+"/etcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
await make_ca("/O=Vitastor etcd CA", dir+"etcd_ca");
await make_signed("/CN=Vitastor etcd", dir+"etcd", dir+"etcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
to_copy.push('etcd_ca.crt', 'etcd.crt', 'etcd.key');
if (!use_perms)
osd_to_copy.push('etcd_ca.crt');
}
if (use_perms || antietcd_only)
{
await make_ca("/O=Vitastor Antietcd CA", dir+"/antietcd_ca");
await make_signed("/CN=Vitastor Antietcd", dir+"/antietcd", dir+"/antietcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
await make_ca("/O=Vitastor Antietcd CA", dir+"antietcd_ca");
await make_signed("/CN=Vitastor Antietcd", dir+"antietcd", dir+"antietcd_ca", etcds.map(e => "IP:"+e.ip).join(','));
to_copy.push('antietcd_ca.crt', 'antietcd.crt', 'antietcd.key');
osd_to_copy.push('antietcd_ca.crt');
}
if (use_perms)
{
await make_ca("/CN=Vitastor OSD", dir+"/osd");
await make_ca("/O=Vitastor Client CA", dir+"/client_ca");
await make_ca("/O=Vitastor Admin CA", dir+"/admin_ca");
await make_signed("/CN=admin", dir+"/admin", dir+"/admin_ca");
to_copy.push('osd.crt', 'osd.key', 'client_ca.crt', 'admin_ca.crt', 'admin.crt', 'admin.key');
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt', 'admin_ca.crt', 'admin.crt', 'admin.key');
await make_ca("/CN=Vitastor OSD", dir+"osd");
await make_ca("/O=Vitastor Client CA", dir+"client_ca");
await make_signed("/CN=admin", dir+"admin", dir+"client_ca");
to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
osd_to_copy.push('osd.crt', 'osd.key', 'client_ca.crt');
}
console.log(`-----
Certificates generated, commands to copy them:
@@ -249,19 +243,13 @@ Certificates generated, commands to copy them:
cd ${dir} && scp ${to_copy.filter(f => f != 'osd.key').join(' ')} root@NODE:/etc/vitastor/
- OSD node:
cd ${dir} && scp ${osd_to_copy.join(' ')} root@NODE:/etc/vitastor/
Then don't forget to run:
chown vitastor:vitastor /etc/vitastor/*.key && /usr/lib/vitastor/mon/make-etcd
-----
`);
if (copy)
{
for (let i = 0; i < etcds.length; i++)
for (const node of etcds)
{
if (i == num)
continue;
const node = etcds[i];
await system("scp "+dir+"/"+to_copy.join(" "+dir+"/")+" root@"+(node.ip.indexOf(':') >= 0 ? "["+node.ip+"]" : node.ip)+
":/etc/vitastor/ && ssh root@"+node.ip+" 'chown vitastor:vitastor /etc/vitastor/*.key && /usr/lib/vitastor/mon/make-etcd'");
await system("scp "+dir+to_copy.join(" "+dir)+" root@"+node.ip+":/etc/vitastor/");
}
}
else
@@ -270,19 +258,6 @@ chown vitastor:vitastor /etc/vitastor/*.key && /usr/lib/vitastor/mon/make-etcd
}
}
async function copy_nontls(dir, etcds, num)
{
const to_copy = [ 'vitastor.conf' ];
for (let i = 0; i < etcds.length; i++)
{
if (i == num)
continue;
const node = etcds[i];
await system("scp "+dir+"/"+to_copy.join(" "+dir+"/")+" root@"+(node.ip.indexOf(':') >= 0 ? "["+node.ip+"]" : node.ip)+
":/etc/vitastor/ && ssh root@"+node.ip+" '/usr/lib/vitastor/mon/make-etcd'");
}
}
async function write_auth_config(config, config_path, etcds, use_perms, antietcd_only)
{
const auth = {};
@@ -292,7 +267,7 @@ async function write_auth_config(config, config_path, etcds, use_perms, antietcd
if (!antietcd_only)
{
auth["etcd_proxy"] = {
urls: etcds.map(e => (e.ip.indexOf(':') >= 0 ? '['+e.ip+']' : e.ip)+':2381'),
urls: etcds.map(e => e.ip+':2381'),
cert: "/etc/vitastor/antietcd.crt",
key: "/etc/vitastor/antietcd.key",
ca: "/etc/vitastor/etcd_ca.crt",
@@ -305,7 +280,6 @@ async function write_auth_config(config, config_path, etcds, use_perms, antietcd
auth["osd_pkey"] = "/etc/vitastor/osd.key";
auth["osd_ca"] = "/etc/vitastor/osd.crt";
auth["client_ca"] = "/etc/vitastor/client_ca.crt";
auth["admin_ca"] = "/etc/vitastor/admin_ca.crt";
auth["cert"] = "/etc/vitastor/admin.crt";
auth["pkey"] = "/etc/vitastor/admin.key";
}
@@ -347,10 +321,9 @@ async function configure_etcd(etcds, num, tls, use_perms)
{
const in_docker = fs.existsSync("/etc/vitastor/etcd.conf") &&
fs.existsSync("/etc/vitastor/docker.conf");
if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service") &&
fs.existsSync("/etc/vitastor/etcd.conf"))
if (!in_docker && fs.existsSync("/etc/systemd/system/vitastor-etcd.service"))
{
console.log("/etc/systemd/system/vitastor-etcd.service and /etc/vitastor/etcd.conf already exists. Remove them to reinitialize the cluster");
console.log("/etc/systemd/system/vitastor-etcd.service already exists");
process.exit(1);
}
if (!in_docker && fs.existsSync("/etc/systemd/system/etcd.service"))
@@ -381,14 +354,14 @@ async function configure_etcd(etcds, num, tls, use_perms)
if (use_perms)
{
options['client_cert_auth'] = '1';
options['trusted_ca_file'] = '/etc/vitastor/antietcd_ca.crt';
options['trusted_ca_file'] = '/etc/vitastor/antietcd.crt';
}
options['peer_cert_file'] = '/etc/vitastor/etcd.crt';
options['peer_key_file'] = '/etc/vitastor/etcd.key';
if (use_perms)
{
options['peer_client_cert_auth'] = '1';
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd_ca.crt';
options['peer_trusted_ca_file'] = '/etc/vitastor/etcd.crt';
}
}
let etcd_conf = fs.existsSync("/etc/vitastor/etcd.conf")
@@ -429,15 +402,14 @@ RestartSec=10
WantedBy=multi-user.target
`);
await system(`useradd etcd`);
await system(`chown vitastor:vitastor /etc/vitastor/*.key`);
if (options['key_file'])
{
await system(`chown etcd `+options['key_file']);
}
await system(`systemctl daemon-reload`);
// Disable distribution etcd unit and enable our one
await system(`systemctl disable --now etcd`);
await system(`systemctl enable --now vitastor-etcd`);
}
async function enable_mon()
{
await system(`systemctl enable --now vitastor-mon`);
}
@@ -445,9 +417,7 @@ function replace_env(text, key, value)
{
let found = false;
text = text.replace(new RegExp('^'+key+'\\s*=.*', 'm'), () => { found = true; return key+'='+value; });
if (found)
return text;
return (text != '' ? text.replace(/\s*$/, '\n') : '')+key+'='+value+'\n';
return found ? text : text.replace(/\s*$/, '\n')+key+'='+value+'\n';
}
function select_local_etcd(etcds)
@@ -476,13 +446,11 @@ function select_local_etcd(etcds)
async function system(cmd)
{
console.log('Running '+cmd);
process.stdin.pause();
const cp = child_process.spawn(cmd, { shell: true, stdio: [ 0, 1, 2 ] });
let finish_cb;
cp.on('exit', () => finish_cb && finish_cb());
if (cp.exitCode == null)
await new Promise(ok => finish_cb = ok);
process.stdin.resume();
return cp.exitCode;
}
@@ -497,9 +465,7 @@ async function make_signed(subj, f, ca, san)
{
if (await system(`openssl req -subj '${subj}' ${san ? "-addext 'subjectAltName="+san+"'" : ""} -nodes -new -keyout ${f}.key -out ${f}.csr`))
process.exit(1);
if (await system(`openssl x509 -req -days 3650 -copy_extensions copy -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
if (await system(`openssl x509 -req -days 3650 -CA ${ca}.crt -CAkey ${ca}.key -CAcreateserial -in ${f}.csr -out ${f}.crt`))
process.exit(1);
fs.unlinkSync(f+".csr");
}
run().catch(e => { console.error(e); process.exit(1); });

View File

@@ -5,7 +5,10 @@
// Permissions are based on:
// 1. Users.
// Stored in /vitastor/config/user/<username>.
// Has 1 property:
// Has 2 properties:
// - type, one of: admin, client.
// admin has full access to all images and also to cluster config.
// client has r/w access to owned images and r/o access to images with reader_group.
// - groups, a list of group names the user is included in.
// 2. Images.
// Stored in /vitastor/config/inode/<pool>/<inode>. Has the following properties:
@@ -13,9 +16,7 @@
// - owner_group (group name)
// - reader_group
// 3. Certificates.
// - osd_ca identifies OSDs.
// - admin_ca (optional) identifies administrators.
// - mon_ca (optional) identifies monitors.
// - osd, mon use their own trusted certificates.
const { X509Certificate } = require('node:crypto');
@@ -32,11 +33,11 @@ const static_perms = {
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
prefixes: {
'/config/': false, '/osd/': false, '/mon/': true, '/pg/history/': true,
'/pg/stats/': true, '/pgstats/': true, '/inode/stats/': true, '/pool/stats/': true,
'/pgstats/': false, '/inode/stats/': true, '/pool/stats/': true,
},
},
admin: {
keys: { '/pg/config': true, '/stats': true, '/history/last_clean_pgs': true },
keys: { '/stats': false },
prefixes: {
'/config/': true, '/osd/': true, '/index/': true, '/pg/history/': true,
'/mon/': false, '/pg/': false, '/pgstats/': false, '/inode/stats/': false, '/pool/stats/': false,
@@ -70,7 +71,7 @@ class VitastorAuthFilter
if (!this.cfg.cert || !this.cfg.key || !this.cfg.ca || !this.cfg.osd_ca || !this.cfg.client_cert_auth)
{
throw new Error('Authenticated Vitastor setups require enabled client_cert_auth, cert, key'+
' and separate ca (client CA), osd_ca and optionally admin_ca and mon_ca');
' and separate ca (client CA), osd_ca and optionally mon_ca');
}
this.osd_ca = await this.antietcd.readPEM(this.cfg.osd_ca);
this.osd_ca_obj = new X509Certificate(this.osd_ca);
@@ -78,31 +79,27 @@ class VitastorAuthFilter
if (this.cfg.mon_ca)
{
this.mon_ca = await this.antietcd.readPEM(this.cfg.mon_ca);
this.mon_ca_obj = new X509Certificate(this.mon_ca);
this.mon_ca_obj = new X509Certificate(this.mon_ca_obj);
this.antietcd.tls.ca.push(this.mon_ca);
}
if (this.cfg.admin_ca)
{
this.admin_ca = await this.antietcd.readPEM(this.cfg.admin_ca);
this.admin_ca_obj = new X509Certificate(this.admin_ca);
this.antietcd.tls.ca.push(this.admin_ca);
}
}
init_context(context, socket)
init_context(context, clientCert)
{
let cert = socket.getPeerX509Certificate();
if (cert.issuer == this.osd_ca_obj.subject && cert.verify(this.osd_ca_obj.publicKey))
let cert = clientCert;
while (cert)
{
context.user_type = 'osd';
}
else if (this.mon_ca_obj && cert.issuer == this.mon_ca_obj.subject && cert.verify(this.mon_ca_obj.publicKey))
{
context.user_type = 'mon';
}
else if (this.admin_ca_obj && cert.issuer == this.admin_ca_obj.subject && cert.verify(this.admin_ca_obj.publicKey))
{
context.user_type = 'admin';
if (cert.fingerprint256 == this.osd_ca_obj.fingerprint256)
{
context.user_type = 'osd';
break;
}
if (this.mon_ca_obj && cert.fingerprint256 == this.mon_ca_obj.fingerprint256)
{
context.user_type = 'mon';
break;
}
cert = cert.issuerCertificate;
}
}
@@ -149,7 +146,7 @@ class VitastorAuthFilter
return cur;
}
// userInfo: { name: string, perms: static_perms[type], groups: { [string]: true } }
// userInfo: { name: string, type: string, perms: static_perms[type], groups: { [string]: true } }
_check_compare(check, userInfo, checked)
{
let key = String(check.key);
@@ -177,7 +174,7 @@ class VitastorAuthFilter
// Allowed to check that a key does not exist
if (check.target == 'VERSION' && check.version == 0)
{
checked['C'+key] = true;
checked['M'+key] = true;
return true;
}
else if (check.target == 'MOD')
@@ -354,25 +351,6 @@ class VitastorAuthFilter
// Allowed to modify known images with CAS checks
return true;
}
if (key.substr(0, 14) == '/config/inode/' &&
checked['C'+key])
{
// Allowed to create an image with CAS checks and with read access
// to the parent image, if specified
const data = this._decode(put.key, put.value);
if (data.parent_id)
{
const [ cur_pool_id ] = put.key.substr(14).split('/');
const inode = this._get([ ...this.prefix_parts, 'config', 'inode', data.parent_pool || cur_pool_id, data.parent_id ], true);
if (inode && (inode.reader_group && userInfo.groups[inode.reader_group] ||
inode.owner_group && userInfo.groups[inode.owner_group] ||
inode.owner === userInfo.name))
{
return true;
}
}
return true;
}
}
return false;
}
@@ -425,7 +403,7 @@ class VitastorAuthFilter
_get_user(context)
{
if (context.user_type === 'osd' || context.user_type === 'mon' || context.user_type === 'admin')
if (context.user_type === 'osd' || context.user_type === 'mon')
{
return {
name: context.user_type,
@@ -440,7 +418,11 @@ class VitastorAuthFilter
let userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
if (!userInfo)
{
userInfo = {};
userInfo = { type: 'client' };
}
else if (userInfo.type !== 'client' && userInfo.type !== 'admin')
{
userInfo.type = 'client';
}
userInfo.perms = static_perms[userInfo.type] || static_perms['invalid'];
userInfo.name = context.username;
@@ -458,10 +440,18 @@ class VitastorAuthFilter
filter_api(context, api/*, data*/)
{
let type = 'client';
if (context.user_type === 'osd' || context.user_type === 'mon' || context.user_type === 'admin')
if (context.user_type === 'osd' || context.user_type === 'mon')
{
type = context.user_type;
}
else if (context.username)
{
const userInfo = this._get([ ...this.prefix_parts, 'config', 'user', context.username ], true);
if (userInfo && userInfo.type === 'admin')
{
type = 'admin';
}
}
return api_perms[type] && api_perms[type][api];
}

View File

@@ -1,6 +1,6 @@
{
"name": "vitastor",
"version": "3.2.2",
"version": "3.0.15",
"description": "Low-level native bindings to Vitastor client library",
"main": "index.js",
"keywords": [

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.2.2'
VITASTOR_VERSION = '3.0.15'
LOG = logging.getLogger(__name__)
@@ -608,7 +608,7 @@ class VitastorDriver(driver.CloneableImageVD,
"""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', size)
self._cli('extend volume', 'modify', vol_name, '--resize', new_size)
LOG.debug(
"Extend volume from %(old_size)s GB to %(new_size)s GB.",
{'old_size': volume.size, 'new_size': new_size}

View File

@@ -1,699 +0,0 @@
From 560382fdf86fdbbd08c38187ef602c04e0abece0 Mon Sep 17 00:00:00 2001
From: Vitaliy Filippov <vitalif@yourcmc.ru>
Date: Sat, 5 Sep 2026 20:41:38 +0300
Subject: [PATCH] Add Vitastor support
---
include/libvirt/libvirt-storage.h | 1 +
src/conf/domain_conf.c | 4 +-
src/conf/domain_validate.c | 11 +-
src/conf/schemas/domaincommon.rng | 30 +++++
src/conf/storage_conf.c | 20 ++-
src/conf/storage_conf.h | 2 +
src/conf/storage_source_conf.c | 2 +
src/conf/storage_source_conf.h | 1 +
src/conf/virstorageobj.c | 3 +
src/libvirt-storage.c | 1 +
src/libxl/libxl_conf.c | 1 +
src/libxl/xen_xl.c | 1 +
src/qemu/qemu_block.c | 45 +++++++
src/qemu/qemu_domain.c | 5 +-
src/qemu/qemu_nbdkit.c | 2 +
src/qemu/qemu_snapshot.c | 2 +
src/storage/storage_driver.c | 1 +
.../storage_source_backingstore.c | 123 ++++++++++++++++++
src/test/test_driver.c | 1 +
.../storagepoolcapsschemadata/poolcaps-fs.xml | 7 +
.../poolcaps-full.xml | 7 +
tests/storagepoolxml2argvtest.c | 1 +
tools/virsh-pool.c | 3 +
23 files changed, 268 insertions(+), 6 deletions(-)
diff --git a/include/libvirt/libvirt-storage.h b/include/libvirt/libvirt-storage.h
index aaad4a3da1..5f5daa8341 100644
--- a/include/libvirt/libvirt-storage.h
+++ b/include/libvirt/libvirt-storage.h
@@ -326,6 +326,7 @@ typedef enum {
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS = 1 << 17, /* (Since: 1.2.8) */
VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE = 1 << 18, /* (Since: 3.1.0) */
VIR_CONNECT_LIST_STORAGE_POOLS_ISCSI_DIRECT = 1 << 19, /* (Since: 5.6.0) */
+ VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR = 1 << 20, /* (Since: 5.0.0) */
} virConnectListAllStoragePoolsFlags;
int virConnectListAllStoragePools(virConnectPtr conn,
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 541dad5bdc..5cb94e1fed 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -7453,7 +7453,8 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
src->configFile = virXPathString("string(./config/@file)", ctxt);
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
- src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS)
+ src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
+ src->protocol == VIR_STORAGE_NET_PROTOCOL_VITASTOR)
src->query = virXMLPropString(node, "query");
if (virDomainStorageNetworkParseHosts(node, ctxt, &src->hosts, &src->nhosts) < 0)
@@ -32196,6 +32197,7 @@ virDomainStorageSourceTranslateSourcePool(virStorageSource *src,
case VIR_STORAGE_POOL_MPATH:
case VIR_STORAGE_POOL_RBD:
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_SHEEPDOG:
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_LAST:
diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index 4558e7b210..3c6b2a8ee8 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -520,6 +520,7 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
case VIR_STORAGE_NET_PROTOCOL_RBD:
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
@@ -592,7 +593,7 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
}
}
- /* internal snapshots and config files are currently supported only with rbd: */
+ /* internal snapshots are currently supported only with rbd: */
if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK &&
src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD) {
if (src->snapshot) {
@@ -600,10 +601,14 @@ virDomainDiskDefValidateSourceChainOne(const virStorageSource *src)
_("<snapshot> element is currently supported only with 'rbd' disks"));
return -1;
}
-
+ }
+ /* config files are currently supported only with rbd and vitastor: */
+ if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK &&
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_RBD &&
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_VITASTOR) {
if (src->configFile) {
virReportError(VIR_ERR_XML_ERROR, "%s",
- _("<config> element is currently supported only with 'rbd' disks"));
+ _("<config> element is currently supported only with 'rbd' and 'vitastor' disks"));
return -1;
}
}
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 1f9ac102a0..51b55ea72b 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -2092,6 +2092,35 @@
</element>
</define>
+ <define name="diskSourceNetworkProtocolVitastor">
+ <element name="source">
+ <interleave>
+ <attribute name="protocol">
+ <value>vitastor</value>
+ </attribute>
+ <ref name="diskSourceCommon"/>
+ <optional>
+ <attribute name="name"/>
+ </optional>
+ <optional>
+ <attribute name="query"/>
+ </optional>
+ <zeroOrMore>
+ <ref name="diskSourceNetworkHost"/>
+ </zeroOrMore>
+ <optional>
+ <element name="config">
+ <attribute name="file">
+ <ref name="absFilePath"/>
+ </attribute>
+ <empty/>
+ </element>
+ </optional>
+ <empty/>
+ </interleave>
+ </element>
+ </define>
+
<define name="diskSourceNetworkProtocolISCSI">
<element name="source">
<attribute name="protocol">
@@ -2442,6 +2471,7 @@
<ref name="diskSourceNetworkProtocolSimple"/>
<ref name="diskSourceNetworkProtocolVxHS"/>
<ref name="diskSourceNetworkProtocolNFS"/>
+ <ref name="diskSourceNetworkProtocolVitastor"/>
</choice>
</define>
diff --git a/src/conf/storage_conf.c b/src/conf/storage_conf.c
index 1dc9365bf2..a8a736be81 100644
--- a/src/conf/storage_conf.c
+++ b/src/conf/storage_conf.c
@@ -56,7 +56,7 @@ VIR_ENUM_IMPL(virStoragePool,
"logical", "disk", "iscsi",
"iscsi-direct", "scsi", "mpath",
"rbd", "sheepdog", "gluster",
- "zfs", "vstorage",
+ "zfs", "vstorage", "vitastor",
);
VIR_ENUM_IMPL(virStoragePoolFormatFileSystem,
@@ -242,6 +242,18 @@ static virStoragePoolTypeInfo poolTypeInfo[] = {
.formatToString = virStorageFileFormatTypeToString,
}
},
+ {.poolType = VIR_STORAGE_POOL_VITASTOR,
+ .poolOptions = {
+ .flags = (VIR_STORAGE_POOL_SOURCE_HOST |
+ VIR_STORAGE_POOL_SOURCE_NETWORK |
+ VIR_STORAGE_POOL_SOURCE_NAME),
+ },
+ .volOptions = {
+ .defaultFormat = VIR_STORAGE_FILE_RAW,
+ .formatFromString = virStorageVolumeFormatFromString,
+ .formatToString = virStorageFileFormatTypeToString,
+ }
+ },
{.poolType = VIR_STORAGE_POOL_SHEEPDOG,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
@@ -538,6 +550,11 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
_("element 'name' is mandatory for RBD pool"));
return -1;
}
+ if (pool_type == VIR_STORAGE_POOL_VITASTOR && source->name == NULL) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("element 'name' is mandatory for Vitastor pool"));
+ return -1;
+ }
if (options->formatFromString) {
g_autofree char *format = NULL;
@@ -1127,6 +1144,7 @@ virStoragePoolDefFormatBuf(virBuffer *buf,
/* RBD, Sheepdog, Gluster and Iscsi-direct devices are not local block devs nor
* files, so they don't have a target */
if (def->type != VIR_STORAGE_POOL_RBD &&
+ def->type != VIR_STORAGE_POOL_VITASTOR &&
def->type != VIR_STORAGE_POOL_SHEEPDOG &&
def->type != VIR_STORAGE_POOL_GLUSTER &&
def->type != VIR_STORAGE_POOL_ISCSI_DIRECT) {
diff --git a/src/conf/storage_conf.h b/src/conf/storage_conf.h
index fc67957cfe..720c07ef74 100644
--- a/src/conf/storage_conf.h
+++ b/src/conf/storage_conf.h
@@ -103,6 +103,7 @@ typedef enum {
VIR_STORAGE_POOL_GLUSTER, /* Gluster device */
VIR_STORAGE_POOL_ZFS, /* ZFS */
VIR_STORAGE_POOL_VSTORAGE, /* Virtuozzo Storage */
+ VIR_STORAGE_POOL_VITASTOR, /* Vitastor */
VIR_STORAGE_POOL_LAST,
} virStoragePoolType;
@@ -454,6 +455,7 @@ VIR_ENUM_DECL(virStoragePartedFs);
VIR_CONNECT_LIST_STORAGE_POOLS_SCSI | \
VIR_CONNECT_LIST_STORAGE_POOLS_MPATH | \
VIR_CONNECT_LIST_STORAGE_POOLS_RBD | \
+ VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR | \
VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG | \
VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER | \
VIR_CONNECT_LIST_STORAGE_POOLS_ZFS | \
diff --git a/src/conf/storage_source_conf.c b/src/conf/storage_source_conf.c
index 087de1eaf2..acc046816b 100644
--- a/src/conf/storage_source_conf.c
+++ b/src/conf/storage_source_conf.c
@@ -89,6 +89,7 @@ VIR_ENUM_IMPL(virStorageNetProtocol,
"ssh",
"vxhs",
"nfs",
+ "vitastor",
);
@@ -1313,6 +1314,7 @@ virStorageSourceNetworkDefaultPort(virStorageNetProtocol protocol)
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
return 24007;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_RBD:
/* we don't provide a default for RBD */
return 0;
diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h
index fc868b31af..1c9fbc02b9 100644
--- a/src/conf/storage_source_conf.h
+++ b/src/conf/storage_source_conf.h
@@ -130,6 +130,7 @@ typedef enum {
VIR_STORAGE_NET_PROTOCOL_SSH,
VIR_STORAGE_NET_PROTOCOL_VXHS,
VIR_STORAGE_NET_PROTOCOL_NFS,
+ VIR_STORAGE_NET_PROTOCOL_VITASTOR,
VIR_STORAGE_NET_PROTOCOL_LAST
} virStorageNetProtocol;
diff --git a/src/conf/virstorageobj.c b/src/conf/virstorageobj.c
index 59fa5da372..4739167f5f 100644
--- a/src/conf/virstorageobj.c
+++ b/src/conf/virstorageobj.c
@@ -1438,6 +1438,7 @@ virStoragePoolObjSourceFindDuplicateCb(const void *payload,
return 1;
break;
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_ISCSI_DIRECT:
case VIR_STORAGE_POOL_RBD:
case VIR_STORAGE_POOL_LAST:
@@ -1921,6 +1922,8 @@ virStoragePoolObjMatch(virStoragePoolObj *obj,
(obj->def->type == VIR_STORAGE_POOL_MPATH)) ||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_RBD) &&
(obj->def->type == VIR_STORAGE_POOL_RBD)) ||
+ (MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR) &&
+ (obj->def->type == VIR_STORAGE_POOL_VITASTOR)) ||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG) &&
(obj->def->type == VIR_STORAGE_POOL_SHEEPDOG)) ||
(MATCH(VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER) &&
diff --git a/src/libvirt-storage.c b/src/libvirt-storage.c
index db7660aac4..561df34709 100644
--- a/src/libvirt-storage.c
+++ b/src/libvirt-storage.c
@@ -94,6 +94,7 @@ virStoragePoolGetConnect(virStoragePoolPtr pool)
* VIR_CONNECT_LIST_STORAGE_POOLS_SCSI
* VIR_CONNECT_LIST_STORAGE_POOLS_MPATH
* VIR_CONNECT_LIST_STORAGE_POOLS_RBD
+ * VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR
* VIR_CONNECT_LIST_STORAGE_POOLS_SHEEPDOG
* VIR_CONNECT_LIST_STORAGE_POOLS_GLUSTER
* VIR_CONNECT_LIST_STORAGE_POOLS_ZFS
diff --git a/src/libxl/libxl_conf.c b/src/libxl/libxl_conf.c
index 2b988157fa..9d0eb47b25 100644
--- a/src/libxl/libxl_conf.c
+++ b/src/libxl/libxl_conf.c
@@ -1069,6 +1069,7 @@ libxlMakeNetworkDiskSrcStr(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
virReportError(VIR_ERR_NO_SUPPORT,
diff --git a/src/libxl/xen_xl.c b/src/libxl/xen_xl.c
index b2ff0edcf2..5d9f50c88c 100644
--- a/src/libxl/xen_xl.c
+++ b/src/libxl/xen_xl.c
@@ -1461,6 +1461,7 @@ xenFormatXLDiskSrcNet(virStorageSource *src)
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
virReportError(VIR_ERR_NO_SUPPORT,
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
index a7062d3e96..4f2b6cc0e6 100644
--- a/src/qemu/qemu_block.c
+++ b/src/qemu/qemu_block.c
@@ -743,6 +743,38 @@ qemuBlockStorageSourceGetRBDProps(virStorageSource *src,
}
+static virJSONValue *
+qemuBlockStorageSourceGetVitastorProps(virStorageSource *src)
+{
+ virJSONValue *ret = NULL;
+ virStorageNetHostDef *host;
+ size_t i;
+ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+ g_autofree char *etcd = NULL;
+
+ for (i = 0; i < src->nhosts; i++) {
+ host = src->hosts + i;
+ if ((virStorageNetHostTransport)host->transport != VIR_STORAGE_NET_HOST_TRANS_TCP) {
+ return NULL;
+ }
+ virBufferAsprintf(&buf, i > 0 ? ",%s:%u" : "%s:%u", host->name, host->port);
+ }
+ if (src->nhosts > 0) {
+ etcd = virBufferContentAndReset(&buf);
+ }
+
+ if (virJSONValueObjectAdd(&ret,
+ "S:etcd-host", etcd,
+ "S:etcd-prefix", src->query,
+ "S:config-path", src->configFile,
+ "s:image", src->path,
+ NULL) < 0)
+ return NULL;
+
+ return ret;
+}
+
+
static virJSONValue *
qemuBlockStorageSourceGetSshProps(virStorageSource *src)
{
@@ -1093,6 +1125,12 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
return NULL;
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
+ driver = "vitastor";
+ if (!(fileprops = qemuBlockStorageSourceGetVitastorProps(src)))
+ return NULL;
+ break;
+
case VIR_STORAGE_NET_PROTOCOL_SSH:
driver = "ssh";
if (!(fileprops = qemuBlockStorageSourceGetSshProps(src)))
@@ -1996,6 +2034,7 @@ qemuBlockGetBackingStoreString(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
case VIR_STORAGE_NET_PROTOCOL_SSH:
@@ -2376,6 +2415,12 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src,
return -1;
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
+ driver = "vitastor";
+ if (!(location = qemuBlockStorageSourceGetVitastorProps(src)))
+ return -1;
+ break;
+
case VIR_STORAGE_NET_PROTOCOL_SSH:
if (srcPriv->nbdkitProcess) {
/* disk creation not yet supported with nbdkit, and even if it
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index ac56fc7cb4..b80b311eea 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -4677,7 +4677,8 @@ qemuDomainValidateStorageSource(virStorageSource *src,
if (src->query &&
(actualType != VIR_STORAGE_TYPE_NETWORK ||
(src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS &&
- src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP))) {
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP &&
+ src->protocol != VIR_STORAGE_NET_PROTOCOL_VITASTOR))) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("query is supported only with HTTP(S) protocols"));
return -1;
@@ -4693,6 +4694,7 @@ qemuDomainValidateStorageSource(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
break;
@@ -9103,6 +9105,7 @@ qemuDomainPrepareStorageSourceTLS(virStorageSource *src,
break;
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c
index c1bc6bc363..9d3433bf75 100644
--- a/src/qemu/qemu_nbdkit.c
+++ b/src/qemu/qemu_nbdkit.c
@@ -869,6 +869,7 @@ qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps WITHOUT_NBDKIT_UNUSED,
case VIR_STORAGE_NET_PROTOCOL_NONE:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
@@ -1133,6 +1134,7 @@ qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc)
case VIR_STORAGE_NET_PROTOCOL_NONE:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
index 302775af92..9d8fa0ded2 100644
--- a/src/qemu/qemu_snapshot.c
+++ b/src/qemu/qemu_snapshot.c
@@ -665,6 +665,7 @@ qemuSnapshotPrepareDiskExternalInactive(virDomainSnapshotDiskDef *snapdisk,
case VIR_STORAGE_NET_PROTOCOL_NONE:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
@@ -890,6 +891,7 @@ qemuSnapshotPrepareDiskInternal(virDomainDiskDef *disk,
case VIR_STORAGE_NET_PROTOCOL_NONE:
case VIR_STORAGE_NET_PROTOCOL_NBD:
case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c
index e19e032427..59f91f4710 100644
--- a/src/storage/storage_driver.c
+++ b/src/storage/storage_driver.c
@@ -1626,6 +1626,7 @@ storageVolLookupByPathCallback(virStoragePoolObj *obj,
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_RBD:
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_SHEEPDOG:
case VIR_STORAGE_POOL_ZFS:
case VIR_STORAGE_POOL_LAST:
diff --git a/src/storage_file/storage_source_backingstore.c b/src/storage_file/storage_source_backingstore.c
index 821378883c..2211f6891b 100644
--- a/src/storage_file/storage_source_backingstore.c
+++ b/src/storage_file/storage_source_backingstore.c
@@ -264,6 +264,75 @@ virStorageSourceParseRBDColonString(const char *rbdstr,
}
+static int
+virStorageSourceParseVitastorColonString(const char *colonstr,
+ virStorageSource *src)
+{
+ char *p, *e, *next;
+ g_autofree char *options = NULL;
+
+ /* optionally skip the "vitastor:" prefix if provided */
+ if (STRPREFIX(colonstr, "vitastor:"))
+ colonstr += strlen("vitastor:");
+
+ options = g_strdup(colonstr);
+
+ p = options;
+ while (*p) {
+ /* find : delimiter or end of string */
+ for (e = p; *e && *e != ':'; ++e) {
+ if (*e == '\\') {
+ e++;
+ if (*e == '\0')
+ break;
+ }
+ }
+ if (*e == '\0') {
+ next = e; /* last kv pair */
+ } else {
+ next = e + 1;
+ *e = '\0';
+ }
+
+ if (STRPREFIX(p, "image=")) {
+ src->path = g_strdup(p + strlen("image="));
+ } else if (STRPREFIX(p, "etcd-prefix=")) {
+ src->query = g_strdup(p + strlen("etcd-prefix="));
+ } else if (STRPREFIX(p, "config-path=")) {
+ src->configFile = g_strdup(p + strlen("config-path="));
+ } else if (STRPREFIX(p, "etcd-host=")) {
+ char *h, *sep;
+
+ h = p + strlen("etcd-host=");
+ while (h < e) {
+ for (sep = h; sep < e; ++sep) {
+ if (*sep == '\\' && (sep[1] == ',' ||
+ sep[1] == ';' ||
+ sep[1] == ' ')) {
+ *sep = '\0';
+ sep += 2;
+ break;
+ }
+ }
+
+ if (virStorageSourceRBDAddHost(src, h) < 0)
+ return -1;
+
+ h = sep;
+ }
+ }
+
+ p = next;
+ }
+
+ if (!src->path) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
static int
virStorageSourceParseNBDColonString(const char *nbdstr,
virStorageSource *src)
@@ -379,6 +448,11 @@ virStorageSourceParseBackingColon(virStorageSource *src,
return -1;
break;
+ case VIR_STORAGE_NET_PROTOCOL_VITASTOR:
+ if (virStorageSourceParseVitastorColonString(path, src) < 0)
+ return -1;
+ break;
+
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
@@ -953,6 +1027,54 @@ virStorageSourceParseBackingJSONRBD(virStorageSource *src,
return 0;
}
+static int
+virStorageSourceParseBackingJSONVitastor(virStorageSource *src,
+ virJSONValue *json,
+ const char *jsonstr G_GNUC_UNUSED,
+ int opaque G_GNUC_UNUSED)
+{
+ const char *filename;
+ const char *image = virJSONValueObjectGetString(json, "image");
+ const char *conf = virJSONValueObjectGetString(json, "config-path");
+ const char *etcd_prefix = virJSONValueObjectGetString(json, "etcd-prefix");
+ virJSONValue *servers = virJSONValueObjectGetArray(json, "server");
+ size_t nservers;
+ size_t i;
+
+ src->type = VIR_STORAGE_TYPE_NETWORK;
+ src->protocol = VIR_STORAGE_NET_PROTOCOL_VITASTOR;
+
+ /* legacy syntax passed via 'filename' option */
+ if ((filename = virJSONValueObjectGetString(json, "filename")))
+ return virStorageSourceParseVitastorColonString(filename, src);
+
+ if (!image) {
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
+ _("missing image name in Vitastor backing volume "
+ "JSON specification"));
+ return -1;
+ }
+
+ src->path = g_strdup(image);
+ src->configFile = g_strdup(conf);
+ src->query = g_strdup(etcd_prefix);
+
+ if (servers) {
+ nservers = virJSONValueArraySize(servers);
+
+ src->hosts = g_new0(virStorageNetHostDef, nservers);
+ src->nhosts = nservers;
+
+ for (i = 0; i < nservers; i++) {
+ if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i,
+ virJSONValueArrayGet(servers, i)) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
static int
virStorageSourceParseBackingJSONRaw(virStorageSource *src,
virJSONValue *json,
@@ -1130,6 +1252,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = {
{"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0},
{"ssh", false, virStorageSourceParseBackingJSONSSH, 0},
{"rbd", false, virStorageSourceParseBackingJSONRBD, 0},
+ {"vitastor", false, virStorageSourceParseBackingJSONVitastor, 0},
{"raw", true, virStorageSourceParseBackingJSONRaw, 0},
{"nfs", false, virStorageSourceParseBackingJSONNFS, 0},
{"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0},
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index 1165689de7..bba846351c 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -7345,6 +7345,7 @@ testStorageVolumeTypeForPool(int pooltype)
case VIR_STORAGE_POOL_ISCSI_DIRECT:
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_RBD:
+ case VIR_STORAGE_POOL_VITASTOR:
return VIR_STORAGE_VOL_NETWORK;
case VIR_STORAGE_POOL_LOGICAL:
case VIR_STORAGE_POOL_DISK:
diff --git a/tests/storagepoolcapsschemadata/poolcaps-fs.xml b/tests/storagepoolcapsschemadata/poolcaps-fs.xml
index eee75af746..8bd0a57bdd 100644
--- a/tests/storagepoolcapsschemadata/poolcaps-fs.xml
+++ b/tests/storagepoolcapsschemadata/poolcaps-fs.xml
@@ -204,4 +204,11 @@
</enum>
</volOptions>
</pool>
+ <pool type='vitastor' supported='no'>
+ <volOptions>
+ <defaultFormat type='raw'/>
+ <enum name='targetFormatType'>
+ </enum>
+ </volOptions>
+ </pool>
</storagepoolCapabilities>
diff --git a/tests/storagepoolcapsschemadata/poolcaps-full.xml b/tests/storagepoolcapsschemadata/poolcaps-full.xml
index 805950a937..852df0de16 100644
--- a/tests/storagepoolcapsschemadata/poolcaps-full.xml
+++ b/tests/storagepoolcapsschemadata/poolcaps-full.xml
@@ -204,4 +204,11 @@
</enum>
</volOptions>
</pool>
+ <pool type='vitastor' supported='yes'>
+ <volOptions>
+ <defaultFormat type='raw'/>
+ <enum name='targetFormatType'>
+ </enum>
+ </volOptions>
+ </pool>
</storagepoolCapabilities>
diff --git a/tests/storagepoolxml2argvtest.c b/tests/storagepoolxml2argvtest.c
index d5c2531ab8..b19308ac38 100644
--- a/tests/storagepoolxml2argvtest.c
+++ b/tests/storagepoolxml2argvtest.c
@@ -57,6 +57,7 @@ testCompareXMLToArgvFiles(bool shouldFail,
case VIR_STORAGE_POOL_GLUSTER:
case VIR_STORAGE_POOL_ZFS:
case VIR_STORAGE_POOL_VSTORAGE:
+ case VIR_STORAGE_POOL_VITASTOR:
case VIR_STORAGE_POOL_LAST:
default:
VIR_TEST_DEBUG("pool type '%s' has no xml2argv test", defTypeStr);
diff --git a/tools/virsh-pool.c b/tools/virsh-pool.c
index 2010ef1356..072e2ff9e8 100644
--- a/tools/virsh-pool.c
+++ b/tools/virsh-pool.c
@@ -1187,6 +1187,9 @@ cmdPoolList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
case VIR_STORAGE_POOL_VSTORAGE:
flags |= VIR_CONNECT_LIST_STORAGE_POOLS_VSTORAGE;
break;
+ case VIR_STORAGE_POOL_VITASTOR:
+ flags |= VIR_CONNECT_LIST_STORAGE_POOLS_VITASTOR;
+ break;
case VIR_STORAGE_POOL_LAST:
break;
}
--
2.51.0

View File

@@ -1,91 +0,0 @@
--- qemu-kvm.spec.orig 2026-08-24 00:00:00.000000000 +0000
+++ qemu-kvm.spec 2026-09-05 16:48:22.226624885 +0000
@@ -102,8 +102,6 @@
%global target_list %{kvm_target}-softmmu
-%global block_drivers_rw_list qcow2,raw,file,host_device,nbd,iscsi,rbd,blkdebug,luks,null-co,nvme,copy-on-read,throttle,compress,virtio-blk-vhost-vdpa,virtio-blk-vfio-pci,virtio-blk-vhost-user,io_uring,nvme-io_uring
-%global block_drivers_ro_list vdi,vmdk,vhdx,vpc,https
%define qemudocdir %{_docdir}/%{name}
%global firmwaredirs "%{_datadir}/qemu-firmware:%{_datadir}/ipxe/qemu:%{_datadir}/seavgabios:%{_datadir}/seabios"
@@ -134,6 +132,7 @@ Requires: %{name}-block-blkio = %{epoch}
%if %{have_block_rbd} \
Requires: %{name}-block-rbd = %{epoch}:%{version}-%{release} \
%endif \
+Requires: %{name}-block-vitastor = %{epoch}:%{version}-%{release}\
Requires: %{name}-audio-pa = %{epoch}:%{version}-%{release}
# Since SPICE is removed from RHEL-9, the following Obsoletes:
@@ -158,7 +157,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}
Summary: QEMU is a machine emulator and virtualizer
Name: qemu-kvm
Version: 10.1.0
-Release: 16%{?rcrel}%{?dist}%{?cc_suffix}.5.alma.1
+Release: 16.vitastor%{?rcrel}%{?dist}%{?cc_suffix}.5.alma.1
# Epoch because we pushed a qemu-1.0 package. AIUI this can't ever be dropped
# Epoch 15 used for RHEL 8
# Epoch 17 used for RHEL 9 (due to release versioning offset in RHEL 8.5)
@@ -181,6 +180,7 @@ Source28: 95-kvm-memlock.conf
Source30: kvm-s390x.conf
Source31: kvm-x86.conf
Source36: README.tests
+Source37: qemu-vitastor.c
Patch0004: 0004-Initial-redhat-build.patch
@@ -471,6 +471,7 @@ Patch149: kvm-file-posix-populate-pwrite
Patch150: kvm-block-use-pwrite_zeroes_alignment-when-writing-first.patch
# For RHEL-207389 - qemu-img create/convert fails on target block device with 4k sector size [rhel-10.2.z]
Patch151: kvm-iotests-add-Linux-loop-device-image-creation-test.patch
+Patch152: qemu-10.1-vitastor.patch
# AlmaLinux Patch
Patch2001: 2001-Add-ppc64-support.patch
@@ -509,6 +510,7 @@ BuildRequires: libcurl-devel
%if %{have_block_rbd}
BuildRequires: librbd-devel
%endif
+BuildRequires: vitastor-client-devel
# We need both because the 'stap' binary is probed for by configure
BuildRequires: systemtap
BuildRequires: systemtap-sdt-devel
@@ -729,6 +731,13 @@ using the rbd protocol.
%endif
+%package block-vitastor
+Summary: QEMU Vitastor block driver
+Requires: %{name}-common%{?_isa} = %{epoch}:%{version}-%{release}
+%description block-vitastor
+This package provides the additional Vitastor block driver for QEMU.
+
+
%package audio-pa
Summary: QEMU PulseAudio audio driver
Requires: %{name}-common%{?_isa} = %{epoch}:%{version}-%{release}
@@ -831,6 +840,7 @@ This package provides the additional D-B
%prep
%setup -q -n qemu-%{version}%{?rcstr}
%autopatch -p1
+cp %{SOURCE37} ./block/vitastor.c
%global qemu_kvm_build qemu_kvm_build
mkdir -p %{qemu_kvm_build}
@@ -1067,6 +1077,7 @@ run_configure \
%if %{have_block_rbd}
--enable-rbd \
%endif
+ --enable-vitastor \
%if %{have_librdma}
--enable-rdma \
%endif
@@ -1564,6 +1575,8 @@ useradd -r -u 107 -g qemu -G kvm -d / -s
%files block-rbd
%{_libdir}/%{name}/block-rbd.so
%endif
+%files block-vitastor
+%{_libdir}/%{name}/block-vitastor.so
%files audio-pa
%{_libdir}/%{name}/audio-pa.so

View File

@@ -11,10 +11,7 @@ WORKDIR /root
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
RUN dnf -y install epel-release dnf-plugins-core
RUN dnf -y install https://vitastor.io/rpms/centos/10/vitastor-release-1.0-1.el10.noarch.rpm
RUN sed -i 's/metalink=/#metalink=/' /etc/yum.repos.d/epel.repo
RUN sed -i 's!#baseurl=.*epel/!baseurl=https://mirror.yandex.ru/epel/!' /etc/yum.repos.d/epel.repo
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel openssl-devel libisa-l_crypto-devel \
gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel isa-l-devel gf-complete-devel rdma-core-devel cmake libnl3-devel c-ares-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec

View File

@@ -1,18 +1,17 @@
Name: vitastor
Version: 3.2.2
Version: 3.0.15
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.2.2.el10.tar.gz
Source0: vitastor-3.0.15.el10.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
BuildRequires: nodejs >= 10
BuildRequires: jerasure-devel
BuildRequires: isa-l-devel
BuildRequires: libisa-l_crypto-devel
BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
@@ -162,8 +161,6 @@ chown vitastor:vitastor /var/lib/vitastor
%_bindir/vitastor-nbd
%_bindir/vitastor-ublk
%_bindir/vitastor-nfs
%_sbindir/mount.vitastorfs
%_sbindir/mount.vitastorblk
%_bindir/vitastor-cli
%_bindir/vitastor-rm
%_bindir/vitastor-kv

View File

@@ -13,11 +13,9 @@ RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=http://mirror.centos.org/centos/\$releasever!baseurl=http://vault.centos.org/7.9.2009!' /etc/yum.repos.d/*.repo
RUN yum -y --enablerepo=extras install centos-release-scl epel-release yum-utils rpm-build
RUN perl -i -pe 's!mirrorlist=!#mirrorlist=!s; s!#\s*baseurl=http://mirror.centos.org!baseurl=http://vault.centos.org!' /etc/yum.repos.d/CentOS-SCLo-scl*.repo
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-2.el7.noarch.rpm
RUN sed -i 's/metalink=/#metalink=/' /etc/yum.repos.d/epel.repo
RUN sed -i 's!#baseurl=.*epel/!baseurl=https://archives.fedoraproject.org/pub/archive/epel/!' /etc/yum.repos.d/epel.repo
RUN yum -y install https://vitastor.io/rpms/centos/7/vitastor-release-1.0-1.el7.noarch.rpm
RUN yum -y install devtoolset-9-gcc-c++ devtoolset-9-libatomic-devel gcc make cmake gperftools-devel \
fio rh-nodejs12 jerasure-devel libisa-l-devel openssl3-devel libisa-l_crypto-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
fio rh-nodejs12 jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libnl3-devel c-ares-devel
RUN yumdownloader --disablerepo=centos-sclo-rh --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN rm -f /etc/yum.repos.d/CentOS-Media.repo

View File

@@ -1,25 +1,23 @@
Name: vitastor
Version: 3.2.2
Version: 3.0.15
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.2.2.el7.tar.gz
Source0: vitastor-3.0.15.el7.tar.gz
BuildRequires: gperftools-devel
BuildRequires: devtoolset-9-gcc-c++
BuildRequires: rh-nodejs12
BuildRequires: rh-nodejs12-npm
BuildRequires: jerasure-devel
BuildRequires: openssl3-devel
BuildRequires: libisa-l-devel
BuildRequires: libisa-l_crypto-devel
BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake3
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel >= 1.16
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
@@ -107,7 +105,7 @@ Vitastor storage plugin for OpenNebula.
%build
. /opt/rh/devtoolset-9/enable
%cmake3 . -DOPENSSL_CRYPTO_LIBRARY=%{_libdir}/openssl3/libcrypto.so -DOPENSSL_SSL_LIBRARY=%{_libdir}/openssl3/libssl.so -DOPENSSL_INCLUDE_DIR=%{_includedir}/openssl3
%cmake3 .
%make_build
@@ -169,8 +167,6 @@ chown vitastor:vitastor /var/lib/vitastor
%_bindir/vitastor-nbd
%_bindir/vitastor-ublk
%_bindir/vitastor-nfs
%_sbindir/mount.vitastorfs
%_sbindir/mount.vitastorblk
%_bindir/vitastor-cli
%_bindir/vitastor-rm
%_bindir/vitastor-kv

View File

@@ -11,11 +11,9 @@ RUN rm -f /etc/yum.repos.d/CentOS-Media.repo
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=http://mirror.centos.org/!baseurl=http://vault.centos.org/!' /etc/yum.repos.d/*.repo
RUN dnf -y install centos-release-advanced-virtualization epel-release dnf-plugins-core
RUN sed -i 's/^mirrorlist=/#mirrorlist=/; s!#baseurl=.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/$basearch/$avdir/!; s!^baseurl=.*Source/.*!baseurl=http://vault.centos.org/centos/8.4.2105/virt/Source/advanced-virtualization/!' /etc/yum.repos.d/CentOS-Advanced-Virtualization.repo
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-2.el8.noarch.rpm
RUN sed -i 's/metalink=/#metalink=/' /etc/yum.repos.d/epel.repo
RUN sed -i 's!#baseurl=.*epel/!baseurl=https://mirror.yandex.ru/epel/!' /etc/yum.repos.d/epel.repo
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel \
libisa-l-devel openssl3-devel libisa-l_crypto-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
RUN yum -y install https://vitastor.io/rpms/centos/8/vitastor-release-1.0-1.el8.noarch.rpm
RUN dnf -y install gcc-toolset-9 gcc-toolset-9-gcc-c++ gperftools-devel \
fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel libibverbs-devel libarchive cmake libnl3-devel c-ares-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --enablerepo=powertools --spec fio.spec

View File

@@ -1,24 +1,22 @@
Name: vitastor
Version: 3.2.2
Version: 3.0.15
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.2.2.el8.tar.gz
Source0: vitastor-3.0.15.el8.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-toolset-9-gcc-c++
BuildRequires: nodejs >= 10
BuildRequires: jerasure-devel
BuildRequires: openssl3-devel
BuildRequires: libisa-l-devel
BuildRequires: libisa-l_crypto-devel
BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel >= 1.16
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
@@ -105,7 +103,7 @@ Vitastor storage plugin for OpenNebula.
%build
. /opt/rh/gcc-toolset-9/enable
%cmake . -DOPENSSL_CRYPTO_LIBRARY=%{_libdir}/openssl3/libcrypto.so -DOPENSSL_SSL_LIBRARY=%{_libdir}/openssl3/libssl.so -DOPENSSL_INCLUDE_DIR=%{_includedir}/openssl3
%cmake .
%make_build
@@ -166,8 +164,6 @@ chown vitastor:vitastor /var/lib/vitastor
%_bindir/vitastor-nbd
%_bindir/vitastor-ublk
%_bindir/vitastor-nfs
%_sbindir/mount.vitastorfs
%_sbindir/mount.vitastorblk
%_bindir/vitastor-cli
%_bindir/vitastor-rm
%_bindir/vitastor-kv

View File

@@ -10,10 +10,7 @@ WORKDIR /root
RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/*.repo
RUN dnf -y install epel-release dnf-plugins-core
RUN dnf -y install https://vitastor.io/rpms/centos/9/vitastor-release-1.0-1.el9.noarch.rpm
RUN sed -i 's/metalink=/#metalink=/' /etc/yum.repos.d/epel.repo
RUN sed -i 's!#baseurl=.*epel/!baseurl=https://mirror.yandex.ru/epel/!' /etc/yum.repos.d/epel.repo
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel \
openssl-devel libisa-l_crypto-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
RUN dnf -y install gcc-c++ gperftools-devel fio nodejs rpm-build jerasure-devel libisa-l-devel gf-complete-devel rdma-core-devel libarchive cmake libnl3-devel c-ares-devel
RUN dnf download --source fio
RUN rpm --nomd5 -i fio*.src.rpm
RUN cd ~/rpmbuild/SPECS && dnf builddep -y --spec fio.spec

View File

@@ -1,24 +1,22 @@
Name: vitastor
Version: 3.2.2
Version: 3.0.15
Release: 1%{?dist}
Summary: Vitastor, a fast software-defined clustered block storage
License: Vitastor Network Public License 1.1
URL: https://vitastor.io/
Source0: vitastor-3.2.2.el9.tar.gz
Source0: vitastor-3.0.15.el9.tar.gz
BuildRequires: gperftools-devel
BuildRequires: gcc-c++
BuildRequires: nodejs >= 10
BuildRequires: jerasure-devel
BuildRequires: openssl-devel
BuildRequires: libisa-l-devel
BuildRequires: libisa-l_crypto-devel
BuildRequires: gf-complete-devel
BuildRequires: rdma-core-devel
BuildRequires: cmake
BuildRequires: libnl3-devel
BuildRequires: c-ares-devel >= 1.16
BuildRequires: c-ares-devel
Requires: vitastor-osd = %{version}-%{release}
Requires: vitastor-mon = %{version}-%{release}
Requires: vitastor-client = %{version}-%{release}
@@ -163,8 +161,6 @@ chown vitastor:vitastor /var/lib/vitastor
%_bindir/vitastor-nbd
%_bindir/vitastor-ublk
%_bindir/vitastor-nfs
%_sbindir/mount.vitastorfs
%_sbindir/mount.vitastorblk
%_bindir/vitastor-cli
%_bindir/vitastor-rm
%_bindir/vitastor-kv

View File

@@ -11,7 +11,6 @@ set(WITH_QEMU false CACHE BOOL "Build QEMU driver inside Vitastor source tree")
set(WITH_FIO true CACHE BOOL "Build FIO driver")
set(QEMU_PLUGINDIR qemu CACHE STRING "QEMU plugin directory suffix (qemu-kvm on RHEL)")
set(WITH_ASAN false CACHE BOOL "Build with AddressSanitizer")
set(WITH_UBSAN false CACHE BOOL "Build with UBSanitizer")
set(WITH_SYSTEM_LIBURING false CACHE BOOL "Use system liburing")
if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
if(EXISTS "/etc/debian_version")
@@ -21,17 +20,13 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
endif()
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
add_definitions(-DVITASTOR_VERSION="3.2.2")
add_definitions(-DVITASTOR_VERSION="3.0.15")
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
add_link_options(-fno-omit-frame-pointer)
if (${WITH_ASAN})
add_definitions(-fsanitize=address)
add_link_options(-fsanitize=address -fno-omit-frame-pointer)
endif (${WITH_ASAN})
if (${WITH_UBSAN})
add_definitions(-fsanitize=undefined -fno-sanitize=alignment)
add_link_options(-fsanitize=undefined -fno-sanitize=alignment)
endif (${WITH_UBSAN})
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fvisibility-inlines-hidden")
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -fvisibility-inlines-hidden")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fvisibility-inlines-hidden")
@@ -55,13 +50,9 @@ string(REGEX REPLACE "([\\/\\-]D) *NDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_F
string(REGEX REPLACE "([\\/\\-]D) *NDEBUG" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}")
string(REGEX REPLACE "([\\/\\-]D) *NDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
macro(install_symlink _filepath _sympath)
get_filename_component(_symname ${_sympath} NAME)
get_filename_component(_installdir ${_sympath} PATH)
install(CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink
${_filepath} ${CMAKE_CURRENT_BINARY_DIR}/${_symname}.link)")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${_symname}.link DESTINATION ${_installdir} RENAME ${_symname})
install(CODE "file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${_symname}.link)")
macro(install_symlink filepath sympath)
install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${filepath} \$ENV{DESTDIR}${sympath})")
install(CODE "message(\"-- Created symlink: ${sympath} -> ${filepath}\")")
endmacro(install_symlink)
check_include_file("linux/nbd-netlink.h" HAVE_NBD_NETLINK_H)
@@ -87,11 +78,10 @@ if (RDMACM_LIBRARIES)
add_definitions(-DWITH_RDMACM)
endif (RDMACM_LIBRARIES)
find_package(OpenSSL 3.0 REQUIRED)
find_package(OpenSSL REQUIRED)
add_definitions(-DWITH_OPENSSL)
include_directories(${OPENSSL_INCLUDE_DIR})
pkg_check_modules(CARES REQUIRED libcares>=1.16)
pkg_check_modules(CARES REQUIRED libcares)
include_directories(${CARES_INCLUDE_DIRS})
if (${WITH_SYSTEM_LIBURING})
@@ -126,7 +116,6 @@ add_subdirectory(kv)
add_subdirectory(nfs)
add_subdirectory(osd)
add_subdirectory(test)
add_subdirectory(util)
### Install
@@ -134,8 +123,6 @@ install(TARGETS vitastor-osd vitastor-disk vitastor-nbd vitastor-ublk vitastor-n
install_symlink(vitastor-disk ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/vitastor-dump-journal)
install_symlink(vitastor-cli ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/vitastor-rm)
install_symlink(vitastor-cli ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/vita)
install_symlink(../${CMAKE_INSTALL_BINDIR}/vitastor-nfs ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SBINDIR}/mount.vitastorfs)
install_symlink(../${CMAKE_INSTALL_BINDIR}/vitastor-nfs ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SBINDIR}/mount.vitastorblk)
install(
TARGETS vitastor_client vitastor_kv
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}

View File

@@ -4,6 +4,7 @@ project(vitastor)
# libvitastor_blk.a
add_library(vitastor_blk STATIC
../util/allocator.cpp ../util/crc32c.c ../util/ringloop.cpp
multilist.cpp blockstore_heap.cpp blockstore_disk.cpp
blockstore.cpp blockstore_impl.cpp blockstore_init.cpp blockstore_open.cpp
blockstore_flush.cpp blockstore_read.cpp blockstore_stable.cpp blockstore_sync.cpp blockstore_write.cpp
@@ -11,8 +12,10 @@ add_library(vitastor_blk STATIC
)
target_compile_options(vitastor_blk PUBLIC -fPIC)
target_link_libraries(vitastor_blk
util
crc32c
${LIBURING_LIBRARIES}
${ISAL_LIBRARIES}
# for timerfd_manager
vitastor_common
)
set_target_properties(vitastor_blk PROPERTIES VERSION ${VITASTOR_VERSION} SOVERSION 0)
@@ -20,9 +23,9 @@ if (${WITH_FIO})
# libfio_vitastor_blk.so
add_library(fio_vitastor_blk SHARED
fio_engine.cpp
../../json11/json11.cpp
)
target_link_libraries(fio_vitastor_blk
vitastor_blk
util
)
endif (${WITH_FIO})

View File

@@ -77,11 +77,8 @@ Output:
- retval = number of bytes actually read/written or negative error number
-EINVAL = invalid input parameters
-ENOENT = requested object/version does not exist for reads
-EEXIST = requested version to write already exists
-ENOSPC = no space left in the store for writes
-EAGAIN = temporary failure, operation should be retried (notably, writes may be retried
after stabilizing some journaled entries and freeing up journal space)
-EDOM = checksum error
-EDOM = checksum error.
- version = the version actually read or written
## BS_OP_DELETE
@@ -93,7 +90,7 @@ Input:
- version = requested version. Treated the same as with BS_OP_WRITE
Output:
- retval = 0 or negative error number (-EINVAL, -EAGAIN)
- retval = 0 or negative error number (-EINVAL)
- version = the version actually written (delete is initially written as an object version)
## BS_OP_SYNC
@@ -102,7 +99,7 @@ Make sure all previously issued modifications reach physical media.
Input: Nothing except opcode
Output:
- retval = 0 or negative error number (-EINVAL, -EAGAIN)
- retval = 0 or negative error number (-EINVAL)
## BS_OP_STABLE / BS_OP_ROLLBACK
@@ -132,9 +129,7 @@ Input:
- max_oid = max inode/stripe or 0 to list all objects
Output:
- retval = total obj_ver_id count or negative error number
-ENOMEM = could not allocate memory for the listing
-EAGAIN = pool sharding settings are incorrect, please reshard and retry
- retval = total obj_ver_id count
- version = stable obj_ver_id count
- buf = obj_ver_id array allocated by the blockstore. Stable versions come first.
You must free it yourself after usage with free().
@@ -171,7 +166,7 @@ struct __attribute__ ((visibility("default"))) blockstore_op_t
};
uint8_t *buf = NULL;
uint8_t *bitmap = NULL;
int64_t retval = 0;
int retval = 0;
uint8_t private_data[BS_OP_PRIVATE_DATA_SIZE];
};

View File

@@ -12,9 +12,23 @@
#include "blockstore_disk.h"
#include "blockstore_heap.h"
#include "str_util.h"
#include "disk_util.h"
#include "allocator.h"
static uint32_t is_power_of_two(uint64_t value)
{
uint32_t l = 0;
while (value > 1)
{
if (value & 1)
{
return 64;
}
value = value >> 1;
l++;
}
return l;
}
void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config)
{
// Parse
@@ -112,7 +126,7 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
{
data_block_size = (1 << DEFAULT_DATA_BLOCK_ORDER);
}
if ((data_block_size & (data_block_size-1)) || data_block_size < MIN_DATA_BLOCK_SIZE || data_block_size >= MAX_DATA_BLOCK_SIZE)
if (is_power_of_two(data_block_size) >= 64 || data_block_size < MIN_DATA_BLOCK_SIZE || data_block_size >= MAX_DATA_BLOCK_SIZE)
{
throw std::runtime_error("Bad block size");
}
@@ -210,32 +224,6 @@ void blockstore_disk_t::parse_config(std::map<std::string, std::string> & config
}
}
// A metadata entry must fit into one metadata block; a large block_size with a small
// csum_block_size otherwise inflates the entry past it and makes the callers divide by zero.
static uint64_t entries_per_meta_block(uint64_t entry_size, uint64_t meta_block_size,
uint64_t data_block_size, uint32_t bitmap_granularity, uint32_t csum_block_size)
{
if (entry_size && entry_size <= meta_block_size)
{
return meta_block_size / entry_size;
}
std::string msg = "Metadata entry size ("+std::to_string(entry_size)+" bytes) exceeds meta_block_size ("+
std::to_string(meta_block_size)+" bytes) with block_size="+std::to_string(data_block_size)+
", bitmap_granularity="+std::to_string(bitmap_granularity);
if (csum_block_size)
{
msg += ", csum_block_size="+std::to_string(csum_block_size)+" ("+
std::to_string(data_block_size/csum_block_size)+" checksums per block)";
}
msg += ". Decrease block_size";
if (csum_block_size)
{
msg += ", increase csum_block_size";
}
msg += " or increase meta_block_size";
throw std::runtime_error(msg);
}
void blockstore_disk_t::calc_lengths(bool skip_meta_check)
{
// data
@@ -285,20 +273,17 @@ void blockstore_disk_t::calc_lengths(bool skip_meta_check)
clean_entry_bitmap_size = (data_block_size / bitmap_granularity + 7) / 8;
clean_dyn_size = clean_entry_bitmap_size*2 + (csum_block_size
? data_block_size/csum_block_size*(data_csum_type & 0xFF) : 0);
uint64_t entries_per_block = 0;
recalc:
if (meta_format == BLOCKSTORE_META_FORMAT_HEAP)
{
entries_per_block = entries_per_meta_block(sizeof(heap_big_write_t) + clean_dyn_size,
meta_block_size, data_block_size, bitmap_granularity, csum_block_size);
uint32_t entries_per_block = meta_block_size / (sizeof(heap_big_write_t) + clean_dyn_size);
min_meta_len = (block_count+entries_per_block-1) / entries_per_block * meta_block_size;
}
else if (meta_format == BLOCKSTORE_META_FORMAT_V1)
{
clean_entry_size = 24 /*sizeof(clean_disk_entry)*/ + 2*clean_entry_bitmap_size;
entries_per_block = entries_per_meta_block(clean_entry_size, meta_block_size,
data_block_size, bitmap_granularity, 0);
min_meta_len = (1 + (block_count - 1 + entries_per_block) / entries_per_block) * meta_block_size;
min_meta_len = (1 + (block_count - 1 + meta_block_size / clean_entry_size)
/ (meta_block_size / clean_entry_size)) * meta_block_size;
if (!skip_meta_check && meta_area_size < min_meta_len)
{
too_small:
@@ -310,9 +295,7 @@ too_small:
{
meta_format = BLOCKSTORE_META_FORMAT_V2;
clean_entry_size = 24 /*sizeof(clean_disk_entry)*/ + clean_dyn_size + 4 /*entry_csum*/;
entries_per_block = entries_per_meta_block(clean_entry_size, meta_block_size,
data_block_size, bitmap_granularity, csum_block_size);
min_meta_len = (1 + (block_count - 1 + entries_per_block) / entries_per_block) * meta_block_size;
min_meta_len = (1 + (block_count - 1 + meta_block_size / clean_entry_size) / (meta_block_size / clean_entry_size)) * meta_block_size;
if (!skip_meta_check && meta_area_size < min_meta_len)
{
if (!data_csum_type)
@@ -355,6 +338,41 @@ void blockstore_disk_t::check_lengths()
}
}
// FIXME: Move to utils
static void check_size(int fd, uint64_t *size, uint64_t *sectsize, std::string name)
{
int sect;
struct stat st;
if (fstat(fd, &st) < 0)
{
throw std::runtime_error("Failed to stat "+name);
}
if (S_ISREG(st.st_mode))
{
*size = st.st_size;
if (sectsize)
{
*sectsize = st.st_blksize;
}
}
else if (S_ISBLK(st.st_mode))
{
if (ioctl(fd, BLKGETSIZE64, size) < 0 ||
ioctl(fd, BLKSSZGET, &sect) < 0)
{
throw std::runtime_error("Failed to get "+name+" size or block size: "+strerror(errno));
}
if (sectsize)
{
*sectsize = sect;
}
}
else
{
throw std::runtime_error(name+" is neither a file nor a block device");
}
}
static int bs_openmode(const std::string & mode)
{
if (mode == "directsync")
@@ -473,7 +491,7 @@ void blockstore_disk_t::open_journal()
journal_fd = meta_fd;
journal_device_sect = meta_device_sect;
journal_device_size = 0;
if (journal_offset >= (meta_device_size > 0 ? meta_device_size : data_device_size))
if (journal_offset >= data_device_size)
{
throw std::runtime_error("journal_offset exceeds device size");
}

View File

@@ -80,16 +80,6 @@ int journal_flusher_t::get_syncing_buffer()
return syncing_buffer;
}
uint64_t journal_flusher_t::get_flushing_lsn(object_id oid)
{
auto it = flushing.find(oid);
if (it == flushing.end())
{
return 0;
}
return it->second;
}
bool journal_flusher_t::is_active()
{
return active_flushers > 0;
@@ -106,20 +96,6 @@ void journal_flusher_t::release_trim()
force_start--;
}
// Ask for an fsync. Used when a new entry depends on another not-yet-durable entry
void journal_flusher_t::request_fsync()
{
force_start++;
force_fsync++;
bs->ringloop->wakeup();
}
void journal_flusher_t::release_fsync()
{
force_fsync--;
force_start--;
}
void journal_flusher_t::dump_diagnostics()
{
printf(
@@ -196,42 +172,30 @@ bool journal_flusher_co::loop()
else if (wait_state == 17) goto resume_17;
else if (wait_state == 18) goto resume_18;
else if (wait_state == 19) goto resume_19;
else if (wait_state == 20) goto resume_20;
else if (wait_state == 21) goto resume_21;
else if (wait_state == 22) goto resume_22;
else if (wait_state == 23) goto resume_23;
else if (wait_state == 24) goto resume_24;
else if (wait_state == 25) goto resume_25;
else if (wait_state == 26) goto resume_26;
else if (wait_state == 27) goto resume_27;
else if (wait_state == 28) goto resume_28;
else if (wait_state == 29) goto resume_29;
else if (wait_state == 30) goto resume_30;
else if (wait_state == 31) goto resume_31;
resume_0:
wait_state = 0;
wait_count = 0;
cur_oid = {};
res = bs->heap->get_next_compact(cur_oid);
if ((bs->intent_write_counter >= bs->journal_trim_interval || flusher->force_fsync > 0) && co_id == 0)
if ((bs->intent_write_counter >= bs->journal_trim_interval) && co_id == 0)
{
// Advance fsynced_lsn every <journal_trim_interval> intent writes
bs->intent_write_counter = 0;
resume_17:
resume_18:
resume_19:
if (!fsync_buffer(17))
{
return false;
}
if (bs->intent_write_counter >= bs->journal_trim_interval)
{
// Advance fsynced_lsn every <journal_trim_interval> intent writes
bs->intent_write_counter = 0;
resume_29:
resume_30:
resume_31:
if (!trim_lsn(29))
{
return false;
}
}
resume_20:
resume_21:
if (!trim_lsn(20))
return false;
}
if (res == ENOENT && flusher->force_start > 0 && co_id == 0 &&
(!bs->dsk.disable_journal_fsync || !bs->dsk.disable_meta_fsync || !bs->dsk.disable_data_fsync))
@@ -267,6 +231,7 @@ resume_16:
}
assert(false);
}
flusher->flushing.insert(cur_oid);
resume_1:
wait_state = 1;
should_repeat = false;
@@ -274,6 +239,7 @@ resume_1:
if (!cur_obj)
{
// Object does not exist
flusher->flushing.erase(cur_oid);
goto resume_0;
}
// Scan versions to flush
@@ -306,30 +272,11 @@ resume_1:
if (!compact_info.compact_lsn)
{
// Flushing is aborted
flusher->flushing.erase(cur_oid);
bs->heap->unlock_entry(cur_oid);
goto resume_0;
}
flusher->flushing[cur_oid] = compact_info.compact_lsn;
flusher->active_flushers++;
if (!compact_info.do_delete && compact_info.clean_wr &&
compact_info.clean_wr->type() == BS_HEAP_BIG_INTENT &&
(!bs->dsk.csum_block_size || bs->dsk.csum_block_size > bs->dsk.bitmap_granularity))
{
// Compaction merges newer writes into the data block in place, which changes bytes that
// <clean_wr>'s checksums still describe. Reads never take those bytes from it, but the
// startup recheck does, and it can only skip the overwritten parts when a checksum block
// covers exactly one write granule. With a larger block an overwritten part poisons the
// checksum of the whole block, and with checksums disabled a big_intent still carries a
// single checksum of its whole extent - it's the only way to tell an unfinished in-place
// write from a finished one. Either way see blockstore_heap_t::recheck_verify().
// So make sure the entry is out of the recheck's scope by persisting completed_lsn first.
// It is below fsynced_lsn already - iterate_compaction() doesn't return anything above it
resume_26:
resume_27:
resume_28:
if (!trim_lsn(26))
return false;
}
for (i = 0; i < read_vec.size(); i++)
{
if ((read_vec[i].copy_flags & COPY_BUF_JOURNAL) &&
@@ -348,13 +295,7 @@ resume_28:
{
printf("Compacting %jx:%jx up to l%ju (delete)\n", cur_oid.inode, cur_oid.stripe, compact_info.compact_lsn);
}
// Delete discards everything, so reset to the empty state
clean_loc = UINT64_MAX;
read_vec.clear();
csum_copy.clear();
copy_count = 0;
memset(new_bmp, 0, bs->dsk.clean_entry_bitmap_size);
memset(new_ext_bmp, 0, bs->dsk.clean_entry_bitmap_size);
}
else
{
@@ -541,12 +482,12 @@ resume_13:
printf("Compacted %jx:%jx l%ju (%d writes)\n", cur_oid.inode, cur_oid.stripe, compact_info.compact_lsn, copy_count);
}
flusher->active_flushers--;
flusher->flushing.erase(cur_oid);
if (should_repeat)
{
// Flush the same object again
goto resume_1;
}
flusher->flushing.erase(cur_oid);
// All done
goto resume_0;
}
@@ -592,7 +533,7 @@ void journal_flusher_co::fill_partial_checksum_blocks()
}
auto & vec = read_vec[read_vec.size()-1];
read_vec.insert(read_vec.begin()+vec_pos, (copy_buffer_t){
.copy_flags = COPY_BUF_DATA|COPY_BUF_COALESCED,
.copy_flags = COPY_BUF_JOURNAL|COPY_BUF_COALESCED,
.offset = hole_start,
.len = hole_end - hole_start,
.disk_offset = hole_start,
@@ -767,11 +708,9 @@ bool journal_flusher_co::read_buffered(int wait_base)
else if (wait_state == wait_base+1)
goto resume_1;
wait_count = 0;
if (bs->dsk.inmemory_journal && bs->dsk.csum_block_size <= bs->dsk.bitmap_granularity)
if (bs->dsk.inmemory_journal && !read_to_fill_incomplete)
{
// Happy path: nothing to read :)
// We may have to read intent writes to recalculate block checksums, so proceed
// even if read_to_fill_incomplete is false, but csum_block_size is > bitmap_granularity
return true;
}
for (i = 0; i < read_vec.size(); i++)
@@ -940,40 +879,27 @@ bool journal_flusher_co::trim_lsn(int wait_base)
{
if (wait_state == wait_base) goto resume_0;
else if (wait_state == wait_base+1) goto resume_1;
else if (wait_state == wait_base+2) goto resume_2;
resume_0:
if (flusher->trimming_lsn)
{
// Allow only one coroutine to update the superblock at a time to prevent parallel
// block updates or the device may in theory apply them in any order.
wait_state = wait_base;
return false;
}
fsynced_lsn = bs->heap->get_fsynced_lsn();
if (((blockstore_meta_header_v3_t*)bs->meta_superblock)->completed_lsn == fsynced_lsn)
{
return true;
}
flusher->trimming_lsn = true;
flusher->active_flushers++;
((blockstore_meta_header_v3_t*)bs->meta_superblock)->completed_lsn = fsynced_lsn;
((blockstore_meta_header_v3_t*)bs->meta_superblock)->set_crc32c();
await_sqe(1);
await_sqe(0);
data->iov = (struct iovec){ bs->meta_superblock, (size_t)bs->dsk.meta_block_size };
data->callback = simple_callback_w;
io_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
// Update superblock with datasync
sqe->rw_flags = RWF_DSYNC;
wait_count++;
resume_2:
resume_1:
if (wait_count > 0)
{
wait_state = wait_base+2;
wait_state = wait_base+1;
return false;
}
// Superblock is updated, resume everyone who was waiting for it
flusher->trimming_lsn = false;
bs->ringloop->wakeup();
flusher->active_flushers--;
return true;
}

View File

@@ -93,30 +93,25 @@ public:
class journal_flusher_t
{
int force_start = 0;
int force_fsync = 0;
int min_flusher_count = 0, max_flusher_count = 0, cur_flusher_count = 0, target_flusher_count = 0;
journal_flusher_co *co;
blockstore_impl_t *bs;
friend class journal_flusher_co;
robin_hood::unordered_flat_map<object_id, uint64_t> flushing;
robin_hood::unordered_flat_set<object_id> flushing;
int active_flushers = 0;
std::list<flusher_data_sync_t> data_syncs;
int wanting_meta_fsync = 0;
bool fsyncing_meta = false;
int syncing_buffer = 0;
bool trimming_lsn = false;
public:
journal_flusher_t(blockstore_impl_t *bs);
~journal_flusher_t();
void loop();
int get_syncing_buffer();
uint64_t get_flushing_lsn(object_id oid);
bool is_active();
void request_trim();
void release_trim();
void request_fsync();
void release_fsync();
void dump_diagnostics();
};

View File

@@ -2,7 +2,6 @@
// Copyright (c) Vitaliy Filippov, 2025+
// License: VNPL-1.1 (see README.md for details)
#undef NDEBUG
#include <assert.h>
#include <string.h>
#include <stddef.h>
@@ -197,10 +196,7 @@ uint32_t *heap_entry_t::get_checksum(blockstore_heap_t *heap)
{
if (type() == BS_HEAP_SMALL_WRITE || type() == BS_HEAP_INTENT_WRITE)
{
// a zero-length small_write still has a checksum slot reserved by
// get_small_entry_size when block checksums are off, and its entry
// checksum covers it - so it has to be filled with a defined value
if (heap->dsk->csum_block_size)
if (heap->dsk->csum_block_size || small().len == 0)
return NULL;
return (uint32_t*)((uint8_t*)this + sizeof(heap_small_write_t) + heap->dsk->clean_entry_bitmap_size);
}
@@ -303,14 +299,6 @@ blockstore_heap_t::~blockstore_heap_t()
void blockstore_heap_t::start_load(uint64_t completed_lsn)
{
this->completed_lsn = completed_lsn;
// The persisted completed_lsn may be larger than the lsn of the last entry which survived:
// entries above it were still in flight when the power went away. New entries must never
// reuse an lsn which is at or below completed_lsn, or on the next load they'd look already
// fsynced and escape the rollback of unfinished writes
if (completed_lsn > next_lsn)
{
next_lsn = completed_lsn;
}
}
int blockstore_heap_t::read_blocks(uint64_t disk_offset, uint64_t disk_size, uint8_t *buf, bool allow_corrupted,
@@ -433,7 +421,7 @@ corrupted_object:
if ((wr->type() == BS_HEAP_BIG_INTENT || wr->type() == BS_HEAP_BIG_WRITE) &&
wr->big().block_num >= dsk->block_count)
{
fprintf(stderr, "Error: big_write or big_intent entry %jx:%jx v%ju block_num is too large: %u > %ju. Metadata is incompatible with current parameters. ",
fprintf(stderr, "Error: big_write or big_intent entry %jx:%jx v%ju block_num is too large: %u > %lu. Metadata is incompatible with current parameters. ",
wr->inode, wr->stripe, wr->version, wr->big_intent().block_num, dsk->block_count);
goto corrupted_object;
}
@@ -592,52 +580,6 @@ void blockstore_heap_t::finish_load()
}
}
// Count the newest entries of the object which have to be rechecked after a restart, i.e.
// the ones whose data may be absent from the disk even though the entry itself is there.
// Those are buffered small writes - their data is written in parallel with the entry - and
// the newest intent write if it wasn't fsynced yet.
// A big-redirect intent may also be hidden under a series of small writes: it is acked as
// soon as its entry is written, without an explicit data fsync, so a power outage may drop
// its data from the disk cache while keeping the entry. Its data block belongs to it alone,
// so rechecking it is always safe. In-place intent writes are only rechecked when they're
// the newest entry - the block under them may have been rewritten by a later compaction,
// which would make the recheck fail on perfectly good data.
// <need_data> is set if any of them requires reading the data device, i.e. if the recheck
// can't be done synchronously in memory.
// Commit and rollback entries carry no data of their own, but they're kept in the object's
// chain, so a stabilize or a rollback right after a write leaves one sitting on top of - or
// in between - the writes whose data still has to be rechecked. Skip over them.
heap_entry_t *blockstore_heap_t::skip_commits(heap_entry_t *wr)
{
while (wr && (wr->type() == BS_HEAP_COMMIT || wr->type() == BS_HEAP_ROLLBACK))
{
wr = prev(wr);
}
return wr;
}
int blockstore_heap_t::count_recheck_entries(heap_entry_t *obj, bool & need_data)
{
int count = 0;
need_data = false;
auto wr = skip_commits(obj);
auto newest = wr;
for (; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = skip_commits(prev(wr)))
{
// Zero-sized writes have nothing to read and verify
if (wr->small().len > 0)
count++;
}
if (wr && wr->lsn > completed_lsn &&
(wr->type() == BS_HEAP_BIG_INTENT || wr == newest && wr->type() == BS_HEAP_INTENT_WRITE) &&
(wr->type() == BS_HEAP_BIG_INTENT ? wr->big_intent().len : wr->small().len) > 0)
{
count++;
need_data = true;
}
return count;
}
void blockstore_heap_t::fill_recheck_queue()
{
for (auto & pgp: block_index)
@@ -647,8 +589,9 @@ void blockstore_heap_t::fill_recheck_queue()
inode_map_iterate(ip.second, [&](heap_list_item_t *li)
{
auto obj = &li->entry;
bool need_data = false;
if (count_recheck_entries(obj, need_data) > 0)
// Recheck only the latest intent_write (if after completed_lsn) or a series of small_writes
if ((obj->type() == BS_HEAP_INTENT_WRITE || obj->type() == BS_HEAP_BIG_INTENT)
&& obj->lsn > completed_lsn || obj->type() == BS_HEAP_SMALL_WRITE)
{
recheck_queue.push_back(obj);
}
@@ -690,9 +633,8 @@ int blockstore_heap_t::mark_used_blocks()
li = NULL;
}
bool overwritten = false;
for (; li; li = li->prev)
for (; li; li = li->prev, wr = &li->entry)
{
auto wr = &li->entry;
if (overwritten)
{
wr->set_garbage();
@@ -721,9 +663,10 @@ int blockstore_heap_t::mark_used_blocks()
{
if (dsk->skip_double_claim)
{
// There was a bug in 3.0.x before 3.0.16 - deletion entries were sometimes
// removed from the disk earlier than previous big_writes. Provide a workaround
// for stores affected by this bug: ignore such objects on start.
// There is a BUG currently:
// Sometimes (under unknown conditions) deletion entries are removed from the disk
// earlier than previous big_writes.
// Until it's fixed, we provide a way to ignore such objects on start.
auto prev_li = used_by[wr->big().block_num];
assert(prev_li);
// Newer LSN must be trusted. Remove the older object.
@@ -912,7 +855,7 @@ void blockstore_heap_t::recheck_full_gc()
if (inf.entries[i]->entry.is_garbage())
{
collected_garbage += inf.entries[i]->entry.size;
unlink_list_item(inf.entries[i]);
remove_list_item(inf.entries[i]);
}
else
{
@@ -933,27 +876,13 @@ void blockstore_heap_t::recheck_full_gc()
}
}
// The entries which were hiding under the ones just dropped are the newest ones of the object
// now, and their data may be unfinished just as well - a redirect intent acked without a data
// fsync, or a buffered small write, both of which the outage may have left without their data.
// So look at what became visible and recheck it too
void blockstore_heap_t::recheck_requeue(object_id oid)
{
auto obj = read_entry(oid);
if (obj)
{
recheck_queue.push_back(obj);
}
}
void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr)
{
// write entry is invalid, erase it and all newer entries
int bad_count = 1;
for (auto wr = obj; wr && wr != bad_wr; wr = prev(wr))
{
if (wr->type() != BS_HEAP_COMMIT && wr->type() != BS_HEAP_ROLLBACK)
bad_count++;
bad_count++;
}
auto prev_wr = prev(bad_wr);
if (prev_wr)
@@ -969,94 +898,21 @@ void blockstore_heap_t::recheck_drop_entries(heap_entry_t *obj, heap_entry_t *ba
while (li && prev_wr != &li->entry)
{
auto prev = li->prev;
auto type = li->entry.type();
if ((type == BS_HEAP_COMMIT || type == BS_HEAP_ROLLBACK) && li->entry.version < bad_wr->version)
{
// A commit or a rollback of an older version refers to versions which are below
// the unfinished write and survive it, so it has to survive too - otherwise a
// version the client was told is stable would silently become unstable again
li = prev;
continue;
}
// Small writes may be stacked on top of an intent write, so the types may differ
assert(type == BS_HEAP_SMALL_WRITE || type == BS_HEAP_INTENT_WRITE ||
type == BS_HEAP_BIG_INTENT || type == BS_HEAP_COMMIT || type == BS_HEAP_ROLLBACK);
assert(li->entry.type() == bad_wr->type());
init_erase_bad_entry(li);
unlink_list_item(li);
li = prev;
}
}
// Verify the data of a rechecked entry, skipping the checksum blocks which newer entries of the
// same object overwrite. Compaction merges those newer writes into the data block in place: it
// changes bytes which <wr>'s checksums still describe, without touching <wr> itself. Nothing ever
// reads those bytes from <wr> - they're shadowed - so a mismatch there means nothing.
// A shadow only holds while the entry casting it stays, though: the very same recheck may find
// that newer entry unfinished and drop it, and then the bytes under it become visible again.
// So entries which this recheck is about to drop cast no shadow. This is why the entry needing
// a data read is always verified last, see recheck_start_reads() - by then it is already known
// which of the entries above it survive.
// This is only exact while a checksum block isn't larger than the write granularity. With larger
// blocks an overwritten part poisons the checksum of the whole block, and skipping the block
// would hide a genuinely unfinished write in the rest of it - so there the flusher instead makes
// sure such an entry is below the persisted completed_lsn and never gets rechecked at all,
// see the big_intent trim in journal_flusher_co::loop()
bool blockstore_heap_t::recheck_verify(heap_recheck_state_t *st, heap_entry_t *wr, uint8_t *buf)
void blockstore_heap_t::recheck_start_reads(heap_recheck_state_t *st)
{
auto obj = st->obj;
if (!dsk->csum_block_size || dsk->csum_block_size > dsk->bitmap_granularity ||
wr == obj || wr->type() == BS_HEAP_SMALL_WRITE)
{
// The newest entry isn't shadowed by anything, and a small write has its own copy
// of the data in the buffer area which compaction only ever reads
return calc_checksums(wr, buf, false);
}
uint32_t start = 0, end = 0;
uint8_t *bitmap = NULL;
if (wr->type() == BS_HEAP_BIG_INTENT)
{
start = wr->big_intent().offset;
end = start + wr->big_intent().len;
bitmap = wr->get_int_bitmap(this);
}
else
{
assert(wr->type() == BS_HEAP_INTENT_WRITE);
start = wr->small().offset;
end = start + wr->small().len;
}
uint32_t csum_size = (dsk->data_csum_type & 0xFF);
uint8_t *csums = wr->get_checksums(this);
if (wr->type() == BS_HEAP_BIG_INTENT)
csums += start/dsk->csum_block_size * csum_size;
bool ok = true;
for (uint32_t pos = start; pos < end; pos += dsk->csum_block_size)
{
bool shadowed = false;
for (auto newer = obj; newer && newer != wr && !shadowed; newer = prev(newer))
{
if (newer->type() != BS_HEAP_SMALL_WRITE && newer->type() != BS_HEAP_INTENT_WRITE ||
st->bad_wr && newer->lsn >= st->bad_wr->lsn)
continue;
shadowed = newer->small().offset < pos+dsk->csum_block_size &&
newer->small().offset+newer->small().len > pos;
}
if (shadowed)
continue;
ok = calc_block_checksums((uint32_t*)(csums + (pos-start)/dsk->csum_block_size * csum_size),
buf + (pos-start), bitmap, pos, pos+dsk->csum_block_size, false, NULL) && ok;
}
return ok;
}
int blockstore_heap_t::recheck_start_reads(heap_recheck_state_t *st)
{
int started = 0;
if (st->sent_reads >= st->total_reads)
return 0;
return;
while (recheck_in_progress < recheck_queue_depth)
{
auto wr = skip_commits(st->next_wr);
auto wr = st->next_wr;
st->next_wr = prev(st->next_wr);
uint64_t loc = 0, len = 0;
bool from_data = false;
if (wr->type() == BS_HEAP_SMALL_WRITE)
@@ -1091,57 +947,32 @@ int blockstore_heap_t::recheck_start_reads(heap_recheck_state_t *st)
len = wr->small().len;
from_data = true;
}
if (!len)
{
// Zero-sized entries have nothing to read and verify
st->next_wr = prev(wr);
continue;
}
if (from_data && st->checked_reads < st->sent_reads)
{
// Part of this entry's data may be shadowed by the newer entries of the same
// object, and whether it really is depends on whether they survive the recheck.
// So verify it only when all of them are already verified - see recheck_verify()
break;
}
st->next_wr = prev(wr);
st->oldest_wr = wr;
uint8_t *buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, len);
st->sent_reads++;
recheck_in_progress++;
recheck_pending_reads--;
bool is_last = st->sent_reads >= st->total_reads;
recheck_cb(from_data, loc, len, [this, st, wr](uint8_t *buf)
recheck_cb(from_data, loc, len, buf, [this, st, wr, buf]()
{
st->checked_reads++;
if (!recheck_verify(st, wr, buf))
if (!calc_checksums(wr, buf, false))
st->bad_wr = !st->bad_wr || st->bad_wr->lsn > wr->lsn ? wr : st->bad_wr;
if (st->checked_reads >= st->total_reads)
{
auto bad_wr = st->bad_wr;
auto obj = st->obj;
// Entries below the oldest one we read are unverified, the rest of the set
// is already known to be good
bool check_more = (bad_wr && bad_wr == st->oldest_wr);
recheck_states.erase(obj);
if (bad_wr)
{
object_id oid = { .inode = obj->inode, .stripe = obj->stripe };
recheck_drop_entries(obj, bad_wr);
if (check_more)
recheck_requeue(oid);
}
if (st->bad_wr)
recheck_drop_entries(st->obj, st->bad_wr);
recheck_states.erase(st->obj);
}
free(buf);
recheck_in_progress--;
recheck_small_writes(NULL, 0);
});
started++;
if (is_last)
break;
}
return started;
}
bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, uint64_t offset, uint64_t len, std::function<void(uint8_t* buf)>)> read_buffer, int queue_depth)
bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> read_buffer, int queue_depth)
{
if (in_recheck)
{
@@ -1162,52 +993,26 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
in_recheck = true;
while (recheck_pending_reads > 0 && recheck_in_progress < recheck_queue_depth)
{
int started = 0;
for (auto & sp: recheck_states)
started += recheck_start_reads(&sp.second);
if (!started)
{
// Everything left is postponed until the reads in flight complete
break;
}
recheck_start_reads(&sp.second);
}
while (recheck_queue.size() > 0 && recheck_in_progress < recheck_queue_depth)
{
heap_entry_t *obj = recheck_queue.front();
recheck_queue.pop_front();
bool need_data = false;
int recheck_count = count_recheck_entries(obj, need_data);
if (!recheck_count)
{
// Nothing left to recheck - the object was requeued after a drop which exposed
// entries that are already known to be good
continue;
}
if (!need_data && buffer_area)
if (obj->type() == BS_HEAP_SMALL_WRITE && buffer_area)
{
// Check this object synchronously
heap_entry_t *bad_wr = NULL, *oldest_wr = NULL;
int checked = 0;
auto wr = skip_commits(obj);
for (; wr && wr->type() == BS_HEAP_SMALL_WRITE && checked < recheck_count; wr = skip_commits(prev(wr)))
heap_entry_t *bad_wr = NULL;
for (auto wr = obj; wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
{
if (!wr->small().len)
continue;
fprintf(stderr, "Notice: rechecking %jx:%jx l%ju - %u bytes at %ju in buffer area\n",
wr->inode, wr->stripe, wr->lsn, wr->small().len, wr->small().location);
oldest_wr = wr;
checked++;
if (!calc_checksums(wr, buffer_area + wr->small().location, false))
bad_wr = wr;
}
if (bad_wr)
{
object_id oid = { .inode = obj->inode, .stripe = obj->stripe };
bool check_more = (bad_wr == oldest_wr);
recheck_drop_entries(obj, bad_wr);
if (check_more)
recheck_requeue(oid);
}
}
else
{
@@ -1215,7 +1020,10 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
auto & st = recheck_states[obj];
st.obj = obj;
st.next_wr = obj;
st.total_reads = recheck_count;
st.total_reads = 1;
if (obj->type() == BS_HEAP_SMALL_WRITE)
for (auto wr = prev(obj); wr && wr->type() == BS_HEAP_SMALL_WRITE; wr = prev(wr))
st.total_reads++;
recheck_pending_reads += st.total_reads;
recheck_start_reads(&st);
}
@@ -1228,7 +1036,7 @@ bool blockstore_heap_t::recheck_small_writes(std::function<void(bool is_data, ui
recheck_queue_depth = 0;
if (cb)
{
cb(false, 0, 0, NULL);
cb(false, 0, 0, NULL, NULL);
}
return true;
}
@@ -1256,8 +1064,6 @@ int blockstore_heap_t::finish_recheck()
}
completed_lsn = next_lsn;
first_inflight_lsn = next_lsn+1;
// Everything we just loaded from disk is durable by definition
fsynced_lsn = next_lsn;
std::sort(compact_queue.begin(), compact_queue.end(), [this](const object_id & a, const object_id & b)
{
auto ao = read_entry(a);
@@ -1287,12 +1093,6 @@ bool blockstore_heap_t::calc_checksums(heap_entry_t *wr, uint8_t *data, bool set
len = wr->big_intent().len;
else
assert(0);
if (!len)
{
if (set)
*wr_csum = 0;
return true;
}
uint32_t real_csum = 0;
if (dsk->data_csum_type == BLOCKSTORE_CSUM_XXH3_32)
real_csum = (uint32_t)XXH3_64bits(data, len);
@@ -1370,9 +1170,9 @@ bool blockstore_heap_t::calc_block_checksums(uint32_t *block_csums, uint8_t *bit
uint32_t pos = start;
uint32_t block_end = (start/dsk->csum_block_size + 1)*dsk->csum_block_size;
uint32_t block_crc = 0;
bool isset = false;
while (pos < end)
{
bool isset = false;
uint32_t blk_start = pos;
if (bitmap)
{
@@ -1671,24 +1471,8 @@ void blockstore_heap_t::gc_block(heap_block_info_t & inf)
}
}
// When the previous object's entry is not fsynced yet, we can't write a new one EXCEPT into
// the same block which is passed here as <prefer_block>. If prefer_block is set but we can't
// write into it right now, we return EAGAIN.
int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free, uint32_t prefer_block)
int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free)
{
if (prefer_block != UINT32_MAX)
{
auto & inf = block_info.at(prefer_block);
// A block accumulates a consecutive LSN range only, see modify_alloc() below
if (inf.is_writing ||
inf.mod_lsn_to && inf.mod_lsn_to != next_lsn ||
inf.used_space - inf.garbage_space + entry_size > dsk->meta_block_size)
{
return EAGAIN;
}
last_allocated_block = prefer_block;
goto allocated;
}
if (last_allocated_block != UINT32_MAX)
{
// First try to write into the same block as the previous time
@@ -1751,7 +1535,6 @@ int blockstore_heap_t::allocate_entry(uint32_t entry_size, uint32_t *block_num,
return ENOSPC;
}
}
allocated:
// Write into the same block
*block_num = last_allocated_block;
modify_alloc(last_allocated_block, [&](heap_block_info_t & inf)
@@ -1818,15 +1601,11 @@ void blockstore_heap_t::insert_list_items(heap_list_item_t** v, size_t count, bo
}
}
// <prev_obj> is the newest entry of the object the new entry is added to, or NULL if there is
// none. It is used to keep all not-yet-durable entries of one object in the same metadata block,
// see allocate_entry(). May return EAGAIN or EBUSY, meaning "retry later" - see there.
int blockstore_heap_t::add_entry(uint32_t wr_size, heap_entry_t *prev_obj, uint32_t *modified_block, bool prefer_same,
int blockstore_heap_t::add_entry(uint32_t wr_size, uint32_t *modified_block,
bool allow_last_free, bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry)
{
uint32_t block_num;
int res = allocate_entry(wr_size, &block_num, allow_last_free, prefer_same && prev_obj
&& prev_obj->lsn > get_fsynced_lsn() ? list_item(prev_obj)->block_num : UINT32_MAX);
int res = allocate_entry(wr_size, &block_num, allow_last_free);
if (res != 0)
{
return res;
@@ -1871,7 +1650,7 @@ int blockstore_heap_t::add_small_write(object_id oid, heap_entry_t **obj_ptr, ui
}
uint32_t wr_size = get_small_entry_size(offset, len);
// Small writes are written in parallel with buffered data so they require explicit_complete
return add_entry(wr_size, obj, modified_block, true, false, true, [&](heap_entry_t *wr)
return add_entry(wr_size, modified_block, false, true, [&](heap_entry_t *wr)
{
wr->entry_type = type;
wr->inode = oid.inode;
@@ -1912,7 +1691,7 @@ int blockstore_heap_t::add_big_write(object_id oid, heap_entry_t *old_head, bool
}
uint32_t wr_size = get_big_entry_size();
// Big writes are written after writing data so they don't require explicit_complete
return add_entry(wr_size, old_head, modified_block, false, false, false, [&](heap_entry_t *wr)
return add_entry(wr_size, modified_block, false, false, [&](heap_entry_t *wr)
{
wr->entry_type = BS_HEAP_BIG_WRITE | (stable ? BS_HEAP_STABLE : 0);
wr->inode = oid.inode;
@@ -1938,7 +1717,7 @@ int blockstore_heap_t::add_redirect_intent(object_id oid, heap_entry_t **obj_ptr
{
uint32_t wr_size = get_big_intent_entry_size();
// Big-redirect intents, just like regular big writes, are written after writing data so they don't require explicit_complete
return add_entry(wr_size, *obj_ptr, modified_block, false, false, false, [&](heap_entry_t *wr)
return add_entry(wr_size, modified_block, false, false, [&](heap_entry_t *wr)
{
wr->entry_type = BS_HEAP_BIG_INTENT|BS_HEAP_STABLE;
wr->inode = oid.inode;
@@ -1974,7 +1753,7 @@ int blockstore_heap_t::add_big_intent(object_id oid, heap_entry_t **obj_ptr, uin
}
uint32_t wr_size = get_big_intent_entry_size();
// Big intents are written before writing data so they require explicit_complete
return add_entry(wr_size, obj, modified_block, false, false, true, [&](heap_entry_t *wr)
return add_entry(wr_size, modified_block, false, true, [&](heap_entry_t *wr)
{
wr->entry_type = BS_HEAP_BIG_INTENT | BS_HEAP_STABLE;
wr->inode = oid.inode;
@@ -2018,9 +1797,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
}
if (do_delete)
{
// Compaction only ever touches durable entries and reuses their LSNs, so it doesn't
// need the "same block as the previous entry of the object" placement
return add_entry(get_simple_entry_size(), NULL, modified_block, false, false, false, [&](heap_entry_t *wr)
return add_entry(get_simple_entry_size(), modified_block, false, false, [&](heap_entry_t *wr)
{
wr->entry_type = BS_HEAP_DELETE|BS_HEAP_STABLE;
wr->inode = obj->inode;
@@ -2031,7 +1808,7 @@ int blockstore_heap_t::add_compact(heap_entry_t *obj, uint64_t compact_version,
}
uint32_t wr_size = get_big_entry_size();
// Compaction entry is added after copying data so it doesn't require explicit_complete
return add_entry(wr_size, NULL, modified_block, false, true, false, [&](heap_entry_t *new_wr)
return add_entry(wr_size, modified_block, true, false, [&](heap_entry_t *new_wr)
{
new_wr->entry_type = BS_HEAP_BIG_WRITE|BS_HEAP_STABLE;
new_wr->inode = obj->inode;
@@ -2064,11 +1841,11 @@ int blockstore_heap_t::punch_holes(heap_entry_t *wr, uint8_t *new_bitmap, uint8_
return 0;
}
int blockstore_heap_t::add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type, bool prefer_same)
int blockstore_heap_t::add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type)
{
uint32_t wr_size = get_simple_entry_size();
// Simple entries don't have data so they don't require explicit_complete
return add_entry(wr_size, obj, modified_block, prefer_same, false, false, [&](heap_entry_t *wr)
return add_entry(wr_size, modified_block, false, false, [&](heap_entry_t *wr)
{
wr->entry_type = entry_type;
wr->inode = obj->inode;
@@ -2088,7 +1865,7 @@ int blockstore_heap_t::add_commit(heap_entry_t *obj, uint64_t version, uint32_t
{
auto rollback_version = wr->version;
wr = prev(wr);
while (wr && wr->version > rollback_version)
while (wr->version > rollback_version)
{
assert(!(wr->entry_type & BS_HEAP_STABLE));
wr = prev(wr);
@@ -2098,19 +1875,13 @@ int blockstore_heap_t::add_commit(heap_entry_t *obj, uint64_t version, uint32_t
if (wr->type() == BS_HEAP_COMMIT)
{
if (commit_version < wr->version)
{
commit_version = wr->version;
}
wr = prev(wr);
continue;
}
if (wr->version >= version)
if (wr->version == version)
{
// A version may be already committed, flushed, and removed, and it's not a bug
found = true;
}
if (wr->version <= version)
{
if (!(wr->entry_type & BS_HEAP_STABLE) && wr->version > commit_version)
{
uncommitted = true;
@@ -2131,7 +1902,7 @@ int blockstore_heap_t::add_commit(heap_entry_t *obj, uint64_t version, uint32_t
{
return 0;
}
return add_simple(obj, version, modified_block, BS_HEAP_COMMIT, true);
return add_simple(obj, version, modified_block, BS_HEAP_COMMIT);
}
int blockstore_heap_t::add_rollback(heap_entry_t *obj, uint64_t version, uint32_t *modified_block)
@@ -2190,13 +1961,13 @@ int blockstore_heap_t::add_rollback(heap_entry_t *obj, uint64_t version, uint32_
{
return 0;
}
return add_simple(obj, version, modified_block, BS_HEAP_ROLLBACK, true);
return add_simple(obj, version, modified_block, BS_HEAP_ROLLBACK);
}
int blockstore_heap_t::add_delete(heap_entry_t *obj, uint32_t *modified_block)
{
assert(obj);
return add_simple(obj, 0, modified_block, BS_HEAP_DELETE|BS_HEAP_STABLE, false);
return add_simple(obj, 0, modified_block, BS_HEAP_DELETE|BS_HEAP_STABLE);
}
// 2nd step: mark the block as being written (to prevent further in-memory updates to it),
@@ -2307,19 +2078,12 @@ void blockstore_heap_t::mark_garbage_up_to(heap_entry_t *wr)
return;
}
assert(wr->is_overwrite());
// <wr> survives, so its data block must not be freed no matter how far down the chain
// another entry referring to it turns out to be: compaction merges newer writes into the
// base block in place and gives its location to the entry it adds, and a rolled back big
// write of its own may well sit in between the two.
// <used_big> additionally keeps the walk from freeing the same block twice when several
// entries in a row share it
uint32_t keep_big = (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT ? wr->big().block_num : UINT32_MAX);
uint32_t used_big = keep_big;
uint32_t used_big = (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT ? wr->big().block_num : UINT32_MAX);
wr = prev(wr);
while (wr && !wr->is_garbage())
{
auto prev_wr = prev(wr);
mark_garbage(list_item(wr)->block_num, wr, used_big, keep_big);
mark_garbage(list_item(wr)->block_num, wr, used_big);
if (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT)
{
used_big = wr->big().block_num;
@@ -2328,7 +2092,7 @@ void blockstore_heap_t::mark_garbage_up_to(heap_entry_t *wr)
}
}
void blockstore_heap_t::mark_garbage(uint32_t block_num, heap_entry_t *prev_wr, uint32_t used_big, uint32_t keep_big)
void blockstore_heap_t::mark_garbage(uint32_t block_num, heap_entry_t *prev_wr, uint32_t used_big)
{
prev_wr->set_garbage();
garbage_entries++;
@@ -2338,8 +2102,7 @@ void blockstore_heap_t::mark_garbage(uint32_t block_num, heap_entry_t *prev_wr,
{
free_buffer_area(prev_wr->inode, prev_wr->small().location, prev_wr->small().len);
}
else if ((prev_wr->type() == BS_HEAP_BIG_WRITE || prev_wr->type() == BS_HEAP_BIG_INTENT) &&
prev_wr->big().block_num != used_big && prev_wr->big().block_num != keep_big)
else if ((prev_wr->type() == BS_HEAP_BIG_WRITE || prev_wr->type() == BS_HEAP_BIG_INTENT) && prev_wr->big().block_num != used_big)
{
free_data(prev_wr->inode, prev_wr->big_location(this));
}
@@ -2364,17 +2127,6 @@ int blockstore_heap_t::get_next_compact(object_id & oid)
return 0;
}
bool blockstore_heap_t::is_absent(heap_entry_t *obj)
{
bool present = false;
iterate_with_stable(obj, UINT64_MAX, [&](heap_entry_t *wr, bool stable)
{
present = (wr->type() != BS_HEAP_DELETE);
return false;
});
return !present;
}
void blockstore_heap_t::iterate_with_stable(heap_entry_t *obj, uint64_t max_lsn, std::function<bool(heap_entry_t*, bool)> cb)
{
auto old_wr = obj;
@@ -2438,11 +2190,10 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
{
if (wr->type() == BS_HEAP_ROLLBACK && wr->lsn <= fsynced_lsn)
{
// compact_version in commit/rollback is taken from actual write entries
// to handle lsn=0 reset correctly
if (!res.compact_lsn)
{
res.compact_lsn = wr->lsn;
res.compact_version = wr->version;
}
if (rollback_version > wr->version)
{
@@ -2452,15 +2203,14 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
}
if (wr->type() == BS_HEAP_COMMIT && wr->lsn <= fsynced_lsn)
{
res.do_delete = false;
if (!res.compact_lsn)
{
res.compact_lsn = wr->lsn;
res.compact_version = wr->version;
}
res.do_delete = false;
if (commit_version < wr->version)
{
commit_version = wr->version;
}
continue;
}
bool rolled_back = (wr->version > rollback_version);
@@ -2473,15 +2223,12 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
if (!stable && !committed || wr->lsn > fsynced_lsn)
{
// Unstable and non-fsynced writes can't be compacted yet
// Reset newer compact_lsn to place compact entry correctly
res.do_delete = false;
res.compact_lsn = 0;
res.compact_version = 0;
if ((!under_pressure || stable) && (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_BIG_INTENT || wr->type() == BS_HEAP_DELETE))
if (!under_pressure && (wr->type() == BS_HEAP_BIG_WRITE || wr->type() == BS_HEAP_DELETE))
{
// We may postpone compaction if we have an unstable overwrite when not under pressure
// We MUST postpone if the overwrite is STABLE (even if under pressure),
// because add_compact will reject STABLE overwrites with EBUSY.
return res;
}
continue;
@@ -2492,9 +2239,6 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
if (!stable && !res.compact_lsn)
{
res.compact_lsn = wr->lsn;
}
if (res.compact_version < wr->version)
{
res.compact_version = wr->version;
}
res.clean_wr = wr;
@@ -2505,45 +2249,18 @@ heap_compact_t blockstore_heap_t::iterate_compaction(heap_entry_t *obj, uint64_t
{
// Object is deleted
assert(!has_small && stable); // unstable deletes are not supported
if (!res.do_delete && res.compact_lsn)
{
// A COMMIT above the delete reset do_delete, but all writes between
// them are lost (incorrect chain / no fsync). Restore do_delete.
res.do_delete = true;
res.compact_version = 0;
}
return res;
}
assert(wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE);
if (!res.compact_lsn)
{
res.compact_lsn = wr->lsn;
}
if (res.compact_version < wr->version)
{
res.compact_version = wr->version;
}
res.do_delete = false;
has_small = true;
small_wr_cb(wr);
}
if (!res.clean_wr && !res.do_delete && res.compact_lsn)
{
if (has_small)
{
// The chain holds no big entry surviving the rollbacks and no executed
// delete: neither a merge nor a delete is possible. The chain is invalid
// but we can't just remove it, so abort compaction and leave it as is.
res.compact_lsn = 0;
}
else
{
// Otherwise it means that there are no entries: it's either a bare COMMIT
// or a COMMIT over a ROLLBACK which masks all previous entries. Remove everything.
res.do_delete = true;
}
res.compact_version = 0;
}
return res;
}
@@ -2921,21 +2638,26 @@ void blockstore_heap_t::apply_inflight(heap_inflight_lsn_t & inflight)
{
// Remove entry
auto li = list_item(wr);
if (!li->next)
{
// The last freed entry must be a deletion
assert(!li->prev);
assert((li->entry.entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
}
else if (!li->prev && li->next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
{
// free BS_HEAP_DELETEs when all previous entries are also freed
mark_garbage(li->next->block_num, &li->next->entry, UINT32_MAX, UINT32_MAX);
}
unlink_list_item(li);
remove_list_item(li);
}
}
void blockstore_heap_t::remove_list_item(heap_list_item_t *li)
{
if (!li->next)
{
// The last freed entry must be a deletion
assert(!li->prev);
assert((li->entry.entry_type & ~BS_HEAP_GARBAGE) == (BS_HEAP_DELETE|BS_HEAP_STABLE));
}
else if (!li->prev && li->next->entry.entry_type == (BS_HEAP_DELETE|BS_HEAP_STABLE))
{
// free BS_HEAP_DELETEs when all previous entries are also freed
mark_garbage(li->next->block_num, &li->next->entry, UINT32_MAX);
}
unlink_list_item(li);
}
void blockstore_heap_t::unlink_list_item(heap_list_item_t *li)
{
auto prev = li->prev;

View File

@@ -167,9 +167,6 @@ struct heap_recheck_state_t
size_t sent_reads = 0;
size_t checked_reads = 0;
heap_entry_t *bad_wr = NULL;
// The oldest entry a read was issued for. Everything below it is unverified, so if it
// turns out to be bad the object has to be looked at again after dropping it
heap_entry_t *oldest_wr = NULL;
};
using i64hash_t = robin_hood::hash<uint64_t>;
@@ -230,15 +227,14 @@ class blockstore_heap_t
size_t recheck_pending_reads = 0;
int recheck_in_progress = 0;
bool in_recheck = false;
std::function<void(bool is_data, uint64_t offset, uint64_t len, std::function<void(uint8_t*)>)> recheck_cb;
std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> recheck_cb;
int recheck_queue_depth = 0;
uint64_t get_pg_id(inode_t inode, uint64_t stripe);
bool validate_object(heap_entry_t *obj);
void fill_recheck_queue();
void recheck_requeue(object_id oid);
void recheck_drop_entries(heap_entry_t *obj, heap_entry_t *bad_wr);
int recheck_start_reads(heap_recheck_state_t *st);
void recheck_start_reads(heap_recheck_state_t *st);
int mark_used_blocks();
void init_free_bad_entry(heap_entry_t *wr);
void init_erase_bad_entry(heap_list_item_t *li);
@@ -249,21 +245,19 @@ class blockstore_heap_t
void reshard_add(heap_reshard_state_t *st, heap_list_item_t *li);
void gc_block(heap_block_info_t & inf);
int allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free, uint32_t prefer_block);
int allocate_entry(uint32_t entry_size, uint32_t *block_num, bool allow_last_free);
void insert_list_items(heap_list_item_t** v, size_t count, bool postpone);
void remove_list_item(heap_list_item_t *li);
void unlink_list_item(heap_list_item_t *li);
int add_entry(uint32_t wr_size, heap_entry_t *prev_obj, uint32_t *modified_block, bool prefer_same,
bool allow_last_free, bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry);
int add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type, bool prefer_same);
int add_entry(uint32_t wr_size, uint32_t *modified_block, bool allow_last_free,
bool explicit_complete, std::function<void(heap_entry_t *wr)> fill_entry);
int add_simple(heap_entry_t *obj, uint64_t version, uint32_t *modified_block, uint32_t entry_type);
uint32_t meta_alloc_pos(const heap_block_info_t & inf);
void modify_alloc(uint32_t block_num, std::function<void(heap_block_info_t &)> change_cb);
void mark_garbage_up_to(heap_entry_t *wr);
void mark_garbage(uint32_t block_num, heap_entry_t *prev_wr, uint32_t used_big, uint32_t keep_big);
void mark_garbage(uint32_t block_num, heap_entry_t *prev_wr, uint32_t used_big);
void push_inflight_lsn(uint64_t lsn, heap_entry_t *wr, uint64_t flags);
void mark_completed_lsns(uint64_t mod_lsn);
heap_entry_t *skip_commits(heap_entry_t *wr);
int count_recheck_entries(heap_entry_t *obj, bool & need_data);
bool recheck_verify(heap_recheck_state_t *st, heap_entry_t *wr, uint8_t *buf);
void apply_inflight(heap_inflight_lsn_t & inflight);
public:
blockstore_heap_t(blockstore_disk_t *dsk, uint8_t *buffer_area, int log_level = 0);
@@ -281,7 +275,7 @@ public:
// before finishing initialization if not R/O
std::vector<uint32_t> get_recheck_modified_blocks();
// recheck small write data after reading the database from disk
bool recheck_small_writes(std::function<void(bool is_data, uint64_t offset, uint64_t len, std::function<void(uint8_t* buf)>)> read_buffer, int queue_depth);
bool recheck_small_writes(std::function<void(bool is_data, uint64_t offset, uint64_t len, uint8_t* buf, std::function<void()>)> read_buffer, int queue_depth);
int finish_recheck();
// reshard database according to the pool's PG count
void* reshard_start(pool_id_t pool, uint32_t pg_count, uint32_t pg_stripe_size, uint64_t chunk_limit);
@@ -294,9 +288,6 @@ public:
heap_entry_t *lock_and_read_entry(object_id oid);
// read an object entry without locking it
heap_entry_t *read_entry(object_id oid);
// true if a read of the chain would return ENOENT: the head is a DELETE entry
// or all entries are cut off by a ROLLBACK to version 0
bool is_absent(heap_entry_t *obj);
// unlock an entry
bool unlock_entry(object_id oid);
// set or verify checksums in a write request

View File

@@ -43,24 +43,6 @@ blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *
blockstore_impl_t::~blockstore_impl_t()
{
// Operations which were queued or in flight when we were destroyed still have their
// private state constructed, and a read also owns its read vector. Nobody is going to
// complete them now, so release it here
for (auto op: submit_queue)
if (op)
PRIV(op)->~blockstore_op_private_t();
submit_queue.clear();
for (auto op: reads_in_flight)
{
free_read_buffers(PRIV(op)->read_vec);
PRIV(op)->~blockstore_op_private_t();
}
reads_in_flight.clear();
// Metadata block buffers are normally freed by the write completion callback,
// so anything still here was queued or in flight when we were destroyed
for (auto & mb: modified_blocks)
free(mb.second.buf);
modified_blocks.clear();
if (flusher)
delete flusher;
if (heap)
@@ -286,19 +268,6 @@ void blockstore_impl_t::check_wait(blockstore_op_t *op)
flusher->release_trim();
PRIV(op)->wait_for = 0;
}
else if (PRIV(op)->wait_for == WAIT_META_FSYNC)
{
if (heap->get_fsynced_lsn() < PRIV(op)->wait_detail)
{
// do not submit
#ifdef BLOCKSTORE_DEBUG
printf("Still waiting for LSN %ju to become durable\n", PRIV(op)->wait_detail);
#endif
return;
}
flusher->release_fsync();
PRIV(op)->wait_for = 0;
}
else
{
throw std::runtime_error("BUG: op->wait_for value is unexpected");
@@ -376,7 +345,7 @@ void blockstore_impl_t::process_list(blockstore_op_t *op)
std::sort(result, result + stable_count);
if (stable_count > op->list_stable_limit)
{
memmove(result + op->list_stable_limit, result + stable_count, unstable_count*sizeof(obj_ver_id));
memmove(result + op->list_stable_limit, result + stable_count, unstable_count);
stable_count = op->list_stable_limit;
}
}

View File

@@ -9,9 +9,12 @@
#include "ondisk_formats.h"
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <linux/fs.h>
#include <vector>
#include <list>
@@ -39,7 +42,7 @@ struct blockstore_op_private_t
int op_state;
// Write, sync, stabilize
uint32_t modified_block;
uint32_t modified_block, modified_block2;
// Read
std::vector<copy_buffer_t> read_vec;
@@ -52,9 +55,7 @@ struct blockstore_op_private_t
uint32_t write_type;
// Stabilize, rollback
heap_entry_t *obj;
int stab_pos, res;
uint32_t modified_block2;
int stab_pos;
// Write
timespec tv_begin;
@@ -108,9 +109,6 @@ public:
uint8_t* meta_superblock = NULL;
uint8_t *buffer_area = NULL;
std::vector<blockstore_op_t*> submit_queue;
// Reads waiting for their disk requests. They're out of submit_queue already, but we
// still own their private state, including the read vector - see the destructor
robin_hood::unordered_flat_set<blockstore_op_t*> reads_in_flight;
int unsynced_data_write_count = 0, unsynced_buffer_write_count = 0, unsynced_meta_write_count = 0;
int unsynced_queued_ops = 0;

View File

@@ -17,20 +17,6 @@
throw std::runtime_error("io_uring is full during initialization");\
data = ((ring_data_t*)sqe->user_data)
struct blockstore_recheck_io_t
{
bool is_data;
uint64_t offset;
uint64_t len;
uint8_t *buf;
std::function<void(uint8_t *buf)> cb;
blockstore_recheck_io_t(bool is_data, uint64_t offset, uint64_t len, std::function<void(uint8_t *buf)>&& cb):
is_data(is_data), offset(offset), len(len), buf(NULL), cb(std::move(cb))
{
}
};
blockstore_init_meta::blockstore_init_meta(blockstore_impl_t *bs)
{
this->bs = bs;
@@ -253,41 +239,29 @@ resume_4:
// asynchronous recheck
resume_6:
wait_state = 6;
bs->heap->recheck_small_writes([this](bool is_data, uint64_t offset, uint64_t len, std::function<void(uint8_t*)> cb)
bs->heap->recheck_small_writes([this](bool is_data, uint64_t offset, uint64_t len, uint8_t *buf, std::function<void()> cb)
{
if (!len)
if (!buf)
{
wait_state = 7;
bs->ringloop->wakeup();
return;
}
recheck_ios.push_back(new blockstore_recheck_io_t(is_data, offset, len, std::move(cb)));
}, bs->meta_write_recheck_parallelism);
while (recheck_ios.size())
{
blockstore_recheck_io_t* rc = recheck_ios.back();
sqe = bs->get_sqe();
if (!sqe)
break;
if (!rc->buf)
rc->buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, rc->len);
data = ((ring_data_t*)sqe->user_data);
data->iov = (iovec){ rc->buf, rc->len };
data->callback = [this, rc](ring_data_t *data)
GET_SQE();
data->iov = (iovec){ buf, len };
data->callback = [offset, cb](ring_data_t *data)
{
if (data->res < 0)
{
fprintf(stderr, "Buffer area read failed at offset %ju: %d\n", rc->offset, data->res);
fprintf(stderr, "Buffer area read failed at offset %ju: %d\n", offset, data->res);
exit(1);
}
rc->cb(rc->buf);
free(rc->buf);
delete rc;
cb();
};
io_uring_prep_readv(sqe, (rc->is_data ? bs->dsk.data_fd : bs->dsk.journal_fd), &data->iov, 1,
(rc->is_data ? bs->dsk.data_offset : bs->dsk.journal_offset) + rc->offset);
recheck_ios.pop_back();
}
io_uring_prep_readv(sqe, (is_data ? bs->dsk.data_fd : bs->dsk.journal_fd), &data->iov, 1,
(is_data ? bs->dsk.data_offset : bs->dsk.journal_offset) + offset);
bs->ringloop->submit();
}, bs->meta_write_recheck_parallelism);
return 1;
resume_7:
if (bs->heap->finish_recheck() != 0)

View File

@@ -11,8 +11,6 @@ struct blockstore_init_meta_buf
int state = 0;
};
struct blockstore_recheck_io_t;
class blockstore_init_meta
{
blockstore_impl_t *bs;
@@ -30,7 +28,6 @@ class blockstore_init_meta
uint64_t last_read_offset = 0;
uint64_t entries_loaded = 0;
std::vector<uint32_t> recheck_mod;
std::vector<blockstore_recheck_io_t*> recheck_ios;
int i = 0, j = 0;
bool handle_meta_block(uint8_t *buf, uint64_t count, uint64_t done_cnt);
void handle_event(ring_data_t *data, int buf_num, const char *op);

View File

@@ -43,20 +43,6 @@
#define WAIT_SQE 1
// Suspend operation until there are <wait_detail> bytes of free space in the journal on disk
#define WAIT_COMPACTION 2
// Suspend operation until LSN <wait_detail> becomes durable
#define WAIT_META_FSYNC 3
// In non-immediate_commit setups bs_heap may require us to write into the same block as the
// previous object's entry block. EAGAIN is returned if it's impossible, in that case we
// request an fsync and fsync lifts the placement restriction entirely.
#define BS_SUBMIT_CHECK_PLACEMENT(res, prev_lsn) \
if ((res) == EAGAIN)\
{\
PRIV(op)->wait_for = WAIT_META_FSYNC;\
PRIV(op)->wait_detail = (prev_lsn);\
flusher->request_fsync();\
return 0;\
}
#define COPY_BUF_JOURNAL 0x01
#define COPY_BUF_DATA 0x02

View File

@@ -105,12 +105,12 @@ void blockstore_impl_t::parse_config(blockstore_config_t & config, bool init)
{
meta_write_recheck_parallelism = 16;
}
if (immediate_commit != IMMEDIATE_NONE && (!dsk.disable_journal_fsync || !dsk.disable_meta_fsync))
if (immediate_commit != IMMEDIATE_NONE && !dsk.disable_journal_fsync)
{
throw std::runtime_error("immediate_commit requires disable_journal_fsync and disable_meta_fsync");
throw std::runtime_error("immediate_commit requires disable_journal_fsync");
}
if (immediate_commit == IMMEDIATE_ALL && !dsk.disable_data_fsync)
{
throw std::runtime_error("immediate_commit=all requires disable_data_fsync");
throw std::runtime_error("immediate_commit=all requires disable_journal_fsync and disable_data_fsync");
}
}

View File

@@ -75,12 +75,10 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *op)
return false;
}
}
if (need_skip && (wr->type() == BS_HEAP_SMALL_WRITE || wr->type() == BS_HEAP_INTENT_WRITE) &&
if (need_skip && wr->type() == BS_HEAP_SMALL_WRITE &&
wr->small().offset < blk_end && wr->small().offset+wr->small().len > blk_start)
{
// Small write may mutate big write checksums during flush, and an intent write
// overwrites part of the data block in place right away. Either way the checksum
// of a checksum block they only partially cover no longer matches the disk
// Small write may mutate big write checksums during flush
skip_csum = COPY_BUF_SKIP_CSUM;
}
return true;
@@ -119,7 +117,6 @@ undo_wait:
return 2;
}
op->retval = 0;
reads_in_flight.insert(op);
return 2;
}
@@ -137,8 +134,7 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *op)
}
else if ((vec.copy_flags & COPY_BUF_JOURNAL) && dsk.inmemory_journal)
{
// disk_loc may be negative - make ubsan happy
memcpy(op->buf + vec.offset - op->offset, buffer_area + (vec.disk_loc + vec.disk_offset), vec.len);
memcpy(op->buf + vec.offset - op->offset, buffer_area + vec.disk_loc + vec.disk_offset, vec.len);
}
else
{
@@ -259,7 +255,7 @@ uint32_t blockstore_impl_t::prepare_read_simple(std::vector<copy_buffer_t> & rea
blk_end = blk_end > wr->small().offset+wr->small().len ? wr->small().offset+wr->small().len : blk_end;
}
if ((blk_end-1)/dsk.csum_block_size == blk_start/dsk.csum_block_size ||
(blk_end-1)/dsk.csum_block_size == blk_start/dsk.csum_block_size+1 && blk_end != end && blk_start != start ||
blk_end/dsk.csum_block_size == blk_start/dsk.csum_block_size+1 && blk_end != end && blk_start != start ||
blk_end == end && blk_start == start)
{
// single block, exactly two partial blocks, or any number of full blocks
@@ -408,7 +404,6 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
op->retval = op->len;
heap->unlock_entry(op->oid);
free_read_buffers(PRIV(op)->read_vec);
reads_in_flight.erase(op);
FINISH_OP(op);
}
}

View File

@@ -14,8 +14,6 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
else if (priv->op_state == 3) goto resume_3;
else if (priv->op_state == 4) goto resume_4;
else if (priv->op_state == 5) goto resume_5;
else if (priv->op_state == 6) goto resume_6;
else if (priv->op_state == 7) goto resume_7;
assert(!priv->op_state);
op->retval = 0;
PRIV(op)->lsn = 0;
@@ -23,50 +21,30 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
for (priv->stab_pos = 0; priv->stab_pos < op->len; priv->stab_pos++)
{
{
priv->obj = heap->read_entry(v[priv->stab_pos].oid);
if (!priv->obj)
auto obj = heap->read_entry(v[priv->stab_pos].oid);
if (!obj)
{
op->retval = -ENOENT;
FINISH_OP(op);
return 2;
}
priv->modified_block2 = UINT32_MAX;
priv->res = op->opcode == BS_OP_STABLE
? heap->add_commit(priv->obj, v[priv->stab_pos].version, &priv->modified_block2)
: heap->add_rollback(priv->obj, v[priv->stab_pos].version, &priv->modified_block2);
if (priv->res != 0)
{
// Flush the previous modified block on error or nobody may flush it at all
resume_6:
if (priv->modified_block != UINT32_MAX)
{
priv->op_state = 6;
BS_SUBMIT_CHECK_SQES(1);
prepare_meta_block_write(priv->modified_block);
resume_7:
if (meta_block_is_pending(priv->modified_block))
{
priv->op_state = 7;
return 1;
}
priv->modified_block = UINT32_MAX;
priv->op_state = 0;
}
}
BS_SUBMIT_CHECK_PLACEMENT(priv->res, priv->obj->lsn);
if (priv->res == EBUSY)
int res = op->opcode == BS_OP_STABLE
? heap->add_commit(obj, v[priv->stab_pos].version, &priv->modified_block2)
: heap->add_rollback(obj, v[priv->stab_pos].version, &priv->modified_block2);
if (res == EBUSY)
{
op->retval = -EBUSY;
FINISH_OP(op);
return 2;
}
if (priv->res == ENOENT)
if (res == ENOENT)
{
op->retval = -ENOENT;
FINISH_OP(op);
return 2;
}
if (priv->res == ENOSPC)
if (res == ENOSPC)
{
if (!heap->get_to_compact_count())
{
@@ -80,12 +58,11 @@ resume_7:
flusher->request_trim();
return 0;
}
assert(priv->res == 0);
assert(res == 0);
}
resume_1:
if (priv->modified_block != UINT32_MAX && priv->modified_block2 != priv->modified_block)
{
priv->op_state = 1;
BS_SUBMIT_CHECK_SQES(1);
prepare_meta_block_write(priv->modified_block);
resume_2:

View File

@@ -34,7 +34,6 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
assert(it != modified_blocks.end());
free(it->second.buf);
modified_blocks.erase(it);
unsynced_meta_write_count++;
heap->complete_block_write(modified_block);
ringloop->wakeup();
};
@@ -42,6 +41,7 @@ void blockstore_impl_t::prepare_meta_block_write(uint32_t modified_block)
io_uring_prep_writev(
sqe, dsk.meta_fd, &data->iov, 1, dsk.meta_offset + ((uint64_t)modified_block+1)*dsk.meta_block_size
);
unsynced_meta_write_count++;
pending_modified_blocks.push_back(modified_block);
modified_blocks[modified_block] = { .sent = false, .buf = buf };
}
@@ -77,36 +77,34 @@ bool blockstore_impl_t::intent_write_allowed(blockstore_op_t *op, heap_entry_t *
{
return false;
}
bool ok = false;
bool ok = true;
heap->iterate_with_stable(obj, obj->lsn, [&](heap_entry_t *wr, bool stable)
{
// Intent writes are not allowed over buffered writes
auto t = wr->type();
if (!stable)
if (t == BS_HEAP_SMALL_WRITE)
{
// Intent writes are not allowed over unstable writes
ok = false;
return false;
}
else if (t == BS_HEAP_INTENT_WRITE || t == BS_HEAP_BIG_INTENT)
// Intent writes are not allowed over unstable writes
if (!stable)
{
// Intent writes are not allowed over unfinished intent writes
ok = (wr->lsn <= heap->get_fsynced_lsn());
ok = false;
return false;
}
// Intent writes are allowed over BIG_WRITEs even with enable data_fsync
// because BIG_WRITEs are always fsynced before adding the entry
else if (t == BS_HEAP_BIG_WRITE)
// Intent writes are not allowed over unfinished intent writes
if ((t == BS_HEAP_INTENT_WRITE || t == BS_HEAP_BIG_INTENT) && wr->lsn > heap->get_fsynced_lsn())
{
// But sadly with one exception: a BIG_WRITE may be a result of compaction, with a
// reused older LSN, still in progress of being written. In that case it can still
// disappear after a reboot, and if it disappears, an older small_write may be replayed
// again over our intent write data if it's there. So we have to check the previous write too...
if (wr->lsn == flusher->get_flushing_lsn({ .inode = wr->inode, .stripe = wr->stripe }))
{
return true;
}
ok = true;
ok = false;
return false;
}
// Intent writes are not allowed over buffered writes and other write types
return false;
// Intent writes are allowed over BIG_WRITEs even with fsyncs because BIG_WRITE is always counted as fsynced
if (t == BS_HEAP_BIG_WRITE || t == BS_HEAP_BIG_INTENT)
{
return false;
}
return true;
});
return ok;
}
@@ -127,8 +125,7 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
if (op->opcode == BS_OP_DELETE)
{
// Delete
// An absent chain (deleted or fully rolled back) is already "not there"
if (!obj || heap->is_absent(obj))
if (!obj || obj->type() == BS_HEAP_DELETE)
{
// Already deleted
op->retval = 0;
@@ -138,7 +135,6 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
PRIV(op)->write_type = BS_HEAP_DELETE;
BS_SUBMIT_CHECK_SQES(1);
int res = heap->add_delete(obj, &PRIV(op)->modified_block);
assert(res != EAGAIN);
if (res == ENOSPC)
goto enospc;
assert(res == 0);
@@ -149,7 +145,7 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
}
// FIXME: Allow to do initial writes as buffered, not redirected
// FIXME: Allow to do direct writes over holes
else if (!obj || heap->is_absent(obj) || op->offset == 0 && op->len == dsk.data_block_size)
else if (!obj || obj->type() == BS_HEAP_DELETE || op->offset == 0 && op->len == dsk.data_block_size)
{
// Big (redirect) write
PRIV(op)->write_type = dsk.disable_data_fsync || op->opcode != BS_OP_WRITE_STABLE ? BS_HEAP_BIG_WRITE : _REDIRECT_INTENT;
@@ -184,8 +180,7 @@ enospc:
data->callback = [this, op](ring_data_t *data) { handle_write_event(data, op); };
assert(loc+op->offset+op->len <= dsk.block_count*dsk.data_block_size);
io_uring_prep_writev(sqe, dsk.data_fd, &data->iov, 1, dsk.data_offset + loc + op->offset);
// redirect_intent writes skip the data fsync (resume_2), so they must not take a slot
if (!dsk.disable_data_fsync && PRIV(op)->write_type == BS_HEAP_BIG_WRITE)
if (!dsk.disable_data_fsync)
{
// use PRIV->lsn for fsync_data_id
PRIV(op)->lsn = ++data_fsync_next;
@@ -213,7 +208,6 @@ enospc:
PRIV(op)->location = obj->big_location(heap);
res = heap->add_big_intent(op->oid, &obj, op->version, op->offset, op->len, op->bitmap,
(uint8_t*)op->buf, NULL, &PRIV(op)->modified_block);
assert(res != EAGAIN);
if (res == ENOSPC)
goto enospc;
assert(res == 0);
@@ -231,7 +225,6 @@ enospc:
PRIV(op)->location = wr->big_location(heap);
res = heap->add_small_write(op->oid, &obj, (BS_HEAP_INTENT_WRITE | (op->opcode == BS_OP_WRITE_STABLE ? BS_HEAP_STABLE : 0)),
op->version, op->offset, op->len, 0, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
BS_SUBMIT_CHECK_PLACEMENT(res, obj->lsn);
if (res == ENOSPC)
goto enospc;
assert(res == 0);
@@ -250,12 +243,6 @@ enospc:
uint64_t loc = !op->len ? 0 : heap->find_free_buffer_area(op->len);
if (loc == UINT64_MAX)
{
if (!heap->get_to_compact_count())
{
op->retval = -EAGAIN;
FINISH_OP(op);
return 2;
}
PRIV(op)->wait_for = WAIT_COMPACTION;
PRIV(op)->wait_detail = heap->get_compacted_count();
flusher->request_trim();
@@ -265,7 +252,6 @@ enospc:
BS_SUBMIT_CHECK_SQES(1 + (op->len > 0 ? 1 : 0));
int res = heap->add_small_write(op->oid, &obj, (BS_HEAP_SMALL_WRITE | (op->opcode == BS_OP_WRITE_STABLE ? BS_HEAP_STABLE : 0)),
op->version, op->offset, op->len, loc, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
BS_SUBMIT_CHECK_PLACEMENT(res, obj->lsn);
if (res == ENOSPC)
goto enospc;
assert(res == 0);
@@ -357,9 +343,6 @@ resume_11:
data->iov = { 0 };
data->callback = [this, op, fs = data_fsync_cur](ring_data_t *data)
{
live = true;
if (data->res != 0)
disk_error_abort("sync data", data->res, 0);
if (fs > data_fsync_done)
{
data_fsync_done = fs;
@@ -389,7 +372,6 @@ resume_4:
res = heap->add_big_write(op->oid, obj, op->opcode == BS_OP_WRITE_STABLE,
op->version, op->offset, op->len, PRIV(op)->location, op->bitmap, (uint8_t*)op->buf, &PRIV(op)->modified_block);
}
assert(res != EAGAIN);
if (res == ENOSPC)
{
if (!heap->get_to_compact_count())

View File

@@ -262,7 +262,7 @@ static enum fio_q_status _bs_queue(struct thread_data *td, struct io_u *io, bool
bsd->inflight--;
bsd->completed.push_back(io);
if (bsd->trace)
printf("--- OP_READ %zx n=%d retval=%jd\n", (size_t)op, n, op->retval);
printf("--- OP_READ %zx n=%d retval=%d\n", (size_t)op, n, op->retval);
delete op;
};
break;
@@ -283,7 +283,7 @@ static enum fio_q_status _bs_queue(struct thread_data *td, struct io_u *io, bool
{
bs_data *bsd = ((bs_data*)((thread_data*)io->engine_data)->io_ops_data);
if (bsd->trace)
printf("--- OP_WRITE %zx n=%d retval=%jd\n", (size_t)op, n, op->retval);
printf("--- OP_WRITE %zx n=%d retval=%d\n", (size_t)op, n, op->retval);
if (op->retval < 0)
{
io->error = op->retval < 0 ? -op->retval : 0;
@@ -305,7 +305,7 @@ static enum fio_q_status _bs_queue(struct thread_data *td, struct io_u *io, bool
{
bs_data *bsd = ((bs_data*)((thread_data*)io->engine_data)->io_ops_data);
if (bsd->trace)
printf("--- OP_STABLE %zx n=%d retval=%jd\n", (size_t)op, n, op->retval);
printf("--- OP_STABLE %zx n=%d retval=%d\n", (size_t)op, n, op->retval);
io->error = op->retval < 0 ? -op->retval : 0;
bsd->inflight--;
bsd->completed.push_back(io);
@@ -323,7 +323,7 @@ static enum fio_q_status _bs_queue(struct thread_data *td, struct io_u *io, bool
{
bs_data *bsd = ((bs_data*)((thread_data*)io->engine_data)->io_ops_data);
if (bsd->trace)
printf("--- OP_WRITE_STABLE %zx n=%d retval=%jd\n", (size_t)op, n, op->retval);
printf("--- OP_WRITE_STABLE %zx n=%d retval=%d\n", (size_t)op, n, op->retval);
io->error = op->retval < 0 ? -op->retval : 0;
bsd->inflight--;
bsd->completed.push_back(io);
@@ -342,7 +342,7 @@ static enum fio_q_status _bs_queue(struct thread_data *td, struct io_u *io, bool
bsd->completed.push_back(io);
bsd->inflight--;
if (bsd->trace)
printf("--- OP_SYNC %zx n=%d retval=%jd\n", (size_t)op, n, op->retval);
printf("--- OP_SYNC %zx n=%d retval=%d\n", (size_t)op, n, op->retval);
delete op;
};
bsd->last_sync = true;

View File

@@ -2,7 +2,6 @@
// Copyright (c) Vitaliy Filippov, 2025+
// License: VNPL-1.1 (see README.md for details)
#undef NDEBUG
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

View File

@@ -35,6 +35,7 @@ journal_flusher_t::journal_flusher_t(blockstore_impl_t *bs)
journal_flusher_co::journal_flusher_co()
{
wait_state = 0;
simple_callback_r = [this](ring_data_t* data)
{
bs->live = true;
@@ -58,24 +59,6 @@ journal_flusher_co::journal_flusher_co()
};
}
journal_flusher_co::~journal_flusher_co()
{
// A flush interrupted by an emergency shutdown still holds its read buffers and
// references to entry bitmaps/checksums. free_buffers() can't be used here because
// it also touches the shared metadata sector map, which may already be gone.
for (auto & vi: v)
{
if (vi.buf && (vi.copy_flags == COPY_BUF_JOURNAL || (vi.copy_flags & COPY_BUF_CSUM_FILL)) &&
(!bs->journal.inmemory || vi.buf < bs->journal.buffer ||
vi.buf >= (uint8_t*)bs->journal.buffer + bs->journal.len))
{
free(vi.buf);
}
bs->free_dirty_dyn_data(vi);
}
v.clear();
}
journal_flusher_t::~journal_flusher_t()
{
if (!bs->journal.inmemory)
@@ -93,16 +76,6 @@ size_t journal_flusher_t::get_queue_size()
return flush_queue.size();
}
bool journal_flusher_t::may_advance()
{
return flush_queue.size() || active_flushers > 0 || trim_possible;
}
bool journal_flusher_t::may_advance_except_one()
{
return flush_queue.size() || active_flushers > 1 || trim_possible;
}
void journal_flusher_t::loop()
{
target_flusher_count = bs->write_iodepth*2;
@@ -217,7 +190,6 @@ void journal_flusher_t::request_trim()
void journal_flusher_t::mark_trim_possible()
{
trim_possible = true;
if (trim_wanted > 0)
{
dequeuing = true;
@@ -680,10 +652,9 @@ resume_2:
flusher->sync_to_repeat.erase(repeat_it);
trim_journal:
// Clear unused part of the journal every <journal_trim_interval> flushes
if ((bs->journal_trim_interval && !((++flusher->journal_trim_counter) % bs->journal_trim_interval) ||
flusher->trim_wanted > 0) && flusher->trim_possible)
if (bs->journal_trim_interval && !((++flusher->journal_trim_counter) % bs->journal_trim_interval) ||
flusher->trim_wanted > 0)
{
flusher->trim_possible = false;
resume_30:
resume_31:
resume_32:
@@ -800,8 +771,6 @@ void journal_flusher_co::free_buffers()
{
free(it->buf);
}
// prepare_read() takes a reference to the entry's bitmap/checksums, drop it
bs->free_dirty_dyn_data(*it);
}
v.clear();
}
@@ -1527,19 +1496,10 @@ bool journal_flusher_co::trim_journal(int wait_base)
printf("Journal flushed\n");
exit(0);
}
bs->wakeup_wait_journal();
}
else if (!flusher->may_advance_except_one())
{
bs->wakeup_wait_journal();
}
flusher->journal_trim_counter = 0;
flusher->trimming = false;
}
else if (!flusher->may_advance_except_one())
{
bs->wakeup_wait_journal();
}
return true;
}

View File

@@ -3,34 +3,34 @@
struct copy_buffer_t
{
int copy_flags = 0;
uint64_t offset = 0, len = 0, disk_offset = 0;
uint64_t journal_sector = 0; // only for reads: sector+1 if used and !journal.inmemory, otherwise 0
void *buf = NULL;
uint8_t *csum_buf = NULL;
int *dyn_data = NULL;
int copy_flags;
uint64_t offset, len, disk_offset;
uint64_t journal_sector; // only for reads: sector+1 if used and !journal.inmemory, otherwise 0
void *buf;
uint8_t *csum_buf;
int *dyn_data;
};
struct meta_sector_t
{
uint64_t offset = 0, len = 0;
int state = 0;
void *buf = NULL;
int usage_count = 0;
uint64_t offset, len;
int state;
void *buf;
int usage_count;
};
struct flusher_sync_t
{
bool fsync_meta = false;
int ready_count = 0;
int state = 0;
bool fsync_meta;
int ready_count;
int state;
};
struct flusher_meta_write_t
{
uint64_t sector = 0, pos = 0;
bool submitted = 0;
void *buf = NULL;
uint64_t sector, pos;
bool submitted;
void *buf;
std::map<uint64_t, meta_sector_t>::iterator it;
};
@@ -39,37 +39,36 @@ class journal_flusher_t;
// Journal flusher coroutine
class journal_flusher_co
{
blockstore_impl_t *bs = NULL;
journal_flusher_t *flusher = NULL;
int wait_state = 0, wait_count = 0, wait_journal_count = 0;
struct io_uring_sqe *sqe = NULL;
struct ring_data_t *data = NULL;
blockstore_impl_t *bs;
journal_flusher_t *flusher;
int wait_state, wait_count, wait_journal_count;
struct io_uring_sqe *sqe;
struct ring_data_t *data;
std::list<flusher_sync_t>::iterator cur_sync;
obj_ver_id cur = {};
obj_ver_id cur;
std::map<obj_ver_id, dirty_entry>::iterator dirty_it, dirty_start, dirty_end;
std::map<object_id, uint64_t>::iterator repeat_it;
std::function<void(ring_data_t*)> simple_callback_r, simple_callback_rj, simple_callback_w;
bool try_trim = false;
bool skip_copy = false, has_delete = false, has_writes = false;
~journal_flusher_co();
bool skip_copy, has_delete, has_writes;
std::vector<copy_buffer_t> v;
std::vector<copy_buffer_t>::iterator it;
int i = 0;
bool fill_incomplete = false, cleared_incomplete = false;
int read_to_fill_incomplete = 0;
int copy_count = 0;
uint64_t clean_loc = 0, clean_ver = 0, old_clean_loc = 0, old_clean_ver = 0;
int i;
bool fill_incomplete, cleared_incomplete;
int read_to_fill_incomplete;
int copy_count;
uint64_t clean_loc, clean_ver, old_clean_loc, old_clean_ver;
flusher_meta_write_t meta_old, meta_new;
bool clean_init_bitmap = false;
uint64_t clean_bitmap_offset = 0, clean_bitmap_len = 0;
uint8_t *clean_init_dyn_ptr = NULL;
uint8_t *new_clean_bitmap = NULL;
bool clean_init_bitmap;
uint64_t clean_bitmap_offset, clean_bitmap_len;
uint8_t *clean_init_dyn_ptr;
uint8_t *new_clean_bitmap;
std::unordered_set<uint32_t> mangle_csum_blocks;
uint64_t new_trim_pos = 0;
uint64_t new_trim_pos;
friend class journal_flusher_t;
void scan_dirty();
@@ -95,20 +94,19 @@ public:
class journal_flusher_t
{
int trim_wanted = 0;
bool trim_possible = false;
bool dequeuing = false;
int min_flusher_count = 0, max_flusher_count = 0, cur_flusher_count = 0, target_flusher_count = 0;
int flusher_start_threshold = 0;
journal_flusher_co *co = NULL;
blockstore_impl_t *bs = NULL;
bool dequeuing;
int min_flusher_count, max_flusher_count, cur_flusher_count, target_flusher_count;
int flusher_start_threshold;
journal_flusher_co *co;
blockstore_impl_t *bs;
friend class journal_flusher_co;
int journal_trim_counter = 0;
bool trimming = false;
void* journal_superblock = NULL;
int journal_trim_counter;
bool trimming;
void* journal_superblock;
int active_flushers = 0;
int syncing_flushers = 0;
int active_flushers;
int syncing_flushers;
std::list<flusher_sync_t> syncs;
std::map<object_id, uint64_t> sync_to_repeat;
@@ -119,14 +117,12 @@ class journal_flusher_t
bool try_find_older(std::map<obj_ver_id, dirty_entry>::iterator & dirty_end, obj_ver_id & cur);
bool try_find_other(std::map<obj_ver_id, dirty_entry>::iterator & dirty_end, obj_ver_id & cur);
bool may_advance_except_one();
public:
journal_flusher_t(blockstore_impl_t *bs);
~journal_flusher_t();
void loop();
bool is_trim_wanted() { return trim_wanted; }
bool may_advance();
bool is_active();
size_t get_queue_size();
void mark_trim_possible();

View File

@@ -24,7 +24,6 @@ blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *
calc_lengths();
alloc_dyn_data = dsk.clean_dyn_size > sizeof(void*) || dsk.csum_block_size > 0;
zero_object = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, dsk.data_block_size);
memset(zero_object, 0, dsk.data_block_size);
data_alloc = new allocator_t(dsk.block_count);
}
catch (std::exception & e)
@@ -35,47 +34,13 @@ blockstore_impl_t::blockstore_impl_t(blockstore_config_t & config, ring_loop_i *
flusher = new journal_flusher_t(this);
}
// Submit an operation the blockstore created for itself, and remember it so that it can
// be cleaned up if we're destroyed before it completes
void blockstore_impl_t::enqueue_own_op(blockstore_op_t *op)
{
own_ops.insert(op);
auto cb = op->callback;
op->callback = [this, cb](blockstore_op_t *op)
{
own_ops.erase(op);
cb(op);
};
enqueue_op(op);
}
blockstore_impl_t::~blockstore_impl_t()
{
// Destroy private state of operations still in the queue
for (auto op: submit_queue)
{
if (op && !own_ops.count(op))
PRIV(op)->~blockstore_op_private_t();
}
submit_queue.clear();
// Reads which were waiting for the disk own their read vector, and nobody is going to
// complete them now
for (auto op: reads_in_flight)
{
release_read_vec(op);
PRIV(op)->~blockstore_op_private_t();
}
reads_in_flight.clear();
// Destroy our own operations
for (auto op: own_ops)
{
PRIV(op)->~blockstore_op_private_t();
delete op;
}
own_ops.clear();
// Clear dyn_data in dirty_db
for (auto& obj: dirty_db)
free_dirty_dyn_data(obj.second);
{
if (obj.second.dyn_data)
free(obj.second.dyn_data);
}
delete data_alloc;
delete flusher;
if (zero_object)
@@ -160,7 +125,6 @@ void blockstore_impl_t::loop()
// has_writes == 1 - some writes in progress
// has_writes == 2 - tried to submit some writes, but failed
int has_writes = 0, op_idx = 0, new_idx = 0;
bool has_unfinished_sync = false;
for (; op_idx < submit_queue.size(); op_idx++, new_idx++)
{
auto op = submit_queue[op_idx];
@@ -218,22 +182,7 @@ void blockstore_impl_t::loop()
// sync only completed writes?
// wait for the data device fsync to complete, then submit journal writes for big writes
// then submit an fsync operation
//
// Only one sync at a time: continue_sync() takes the unsynced writes over into
// its own state, so a second sync started while the first one is still in flight
// would find nothing left to sync and report success without anything having
// reached the disk - while the writes it was supposed to cover are still in the
// first sync's hands.
// A deletion takes them over in the very same way, see
// forget_unstable_before_delete() - so wait for it to be journaled and acked,
// and it lands in the unsynced lists itself by then
if (!has_unfinished_sync && !unsynced_forgotten)
{
wr_st = continue_sync(op);
has_unfinished_sync = (wr_st != 2);
}
else
wr_st = 0;
wr_st = continue_sync(op);
}
else if (op->opcode == BS_OP_STABLE)
{
@@ -262,6 +211,10 @@ void blockstore_impl_t::loop()
// ring is full, stop submission
break;
}
else if (PRIV(op)->wait_for == WAIT_JOURNAL)
{
PRIV(op)->wait_detail2 = (unstable_writes.size()+unstable_unsynced);
}
}
}
if (op_idx != new_idx)
@@ -318,7 +271,7 @@ bool blockstore_impl_t::is_safe_to_stop()
{
delete op;
};
enqueue_own_op(op);
enqueue_op(op);
stop_sync_submitted = true;
}
return false;
@@ -326,12 +279,6 @@ bool blockstore_impl_t::is_safe_to_stop()
return true;
}
void blockstore_impl_t::wakeup_wait_journal()
{
wait_journal_counter++;
ringloop->wakeup();
}
void blockstore_impl_t::check_wait(blockstore_op_t *op)
{
if (PRIV(op)->wait_for == WAIT_SQE)
@@ -348,11 +295,12 @@ void blockstore_impl_t::check_wait(blockstore_op_t *op)
}
else if (PRIV(op)->wait_for == WAIT_JOURNAL)
{
if (wait_journal_counter == PRIV(op)->wait_detail)
if (journal.used_start == PRIV(op)->wait_detail &&
(unstable_writes.size()+unstable_unsynced) == PRIV(op)->wait_detail2)
{
// do not submit
#ifdef BLOCKSTORE_DEBUG
printf("Still waiting for flush journal counter %ju\n", PRIV(op)->wait_detail);
printf("Still waiting to flush journal offset %08jx\n", PRIV(op)->wait_detail);
#endif
return;
}
@@ -428,7 +376,6 @@ void blockstore_impl_t::init_op(blockstore_op_t *op)
PRIV(op)->wait_for = 0;
PRIV(op)->op_state = 0;
PRIV(op)->pending_ops = 0;
PRIV(op)->forgot_unsynced = false;
}
static bool replace_stable(object_id oid, uint64_t version, int search_start, int search_end, obj_ver_id* list)

View File

@@ -3,8 +3,6 @@
#pragma once
#include <set>
#include "blockstore.h"
#include "blockstore_disk.h"
#include "ondisk_formats.h"
@@ -95,7 +93,7 @@ struct blockstore_op_private_t
{
// Wait status
int wait_for;
uint64_t wait_detail;
uint64_t wait_detail, wait_detail2;
int pending_ops;
int op_state;
@@ -111,9 +109,6 @@ struct blockstore_op_private_t
// Warning: must not have a default value here because it's written to before calling constructor in blockstore_write.cpp O_o
uint64_t real_version;
timespec tv_begin;
// Set on a deletion which dropped preceding unsynced writes of the same object,
// see forget_unstable_before_delete()
bool forgot_unsynced;
// Sync
std::vector<obj_ver_id> sync_big_writes, sync_small_writes;
@@ -168,15 +163,8 @@ class blockstore_impl_t: public blockstore_i
uint8_t *clean_bitmaps = NULL;
blockstore_dirty_db_t dirty_db;
std::vector<blockstore_op_t*> submit_queue;
// Reads waiting for their disk requests. They're out of submit_queue already, but we
// still own their private state, including the read vector - see the destructor
std::set<blockstore_op_t*> reads_in_flight;
std::set<blockstore_op_t*> own_ops;
std::vector<obj_ver_id> unsynced_big_writes, unsynced_small_writes;
int unsynced_big_write_count = 0, unstable_unsynced = 0;
// Number of deletions which took over the durability of preceding unsynced writes but
// aren't journaled themselves yet, see forget_unstable_before_delete()
int unsynced_forgotten = 0;
int unsynced_queued_ops = 0;
allocator_t *data_alloc = NULL;
uint64_t used_blocks = 0;
@@ -211,6 +199,9 @@ class blockstore_impl_t: public blockstore_i
friend class journal_flusher_co;
void calc_lengths();
void open_data();
void open_meta();
void open_journal();
uint8_t* get_clean_entry_bitmap(uint64_t block_loc, int offset);
blockstore_clean_db_t& clean_db_shard(object_id oid);
@@ -218,7 +209,6 @@ class blockstore_impl_t: public blockstore_i
// Journaling
void prepare_journal_sector_write(int sector, blockstore_op_t *op);
void mark_not_dirty(int journal_sector);
void handle_journal_write(ring_data_t *data, uint64_t flush_id);
void disk_error_abort(const char *op, int retval, int expected);
@@ -228,15 +218,12 @@ class blockstore_impl_t: public blockstore_i
blockstore_init_meta* metadata_init_reader;
blockstore_init_journal* journal_init_reader;
uint64_t wait_journal_counter = 0;
void wakeup_wait_journal();
void check_wait(blockstore_op_t *op);
void init_op(blockstore_op_t *op);
// Read
int dequeue_read(blockstore_op_t *read_op);
void release_clean(blockstore_op_t *op);
void release_read_vec(blockstore_op_t *read_op);
void find_holes(std::vector<copy_buffer_t> & read_vec, uint32_t item_start, uint32_t item_end,
std::function<int(int, bool, uint32_t, uint32_t)> callback);
int fulfill_read(blockstore_op_t *read_op,
@@ -254,7 +241,7 @@ class blockstore_impl_t: public blockstore_i
uint64_t offset, uint64_t submit_len, uint64_t & blk_begin, uint64_t & blk_end, uint8_t* & blk_buf);
bool read_range_fulfilled(std::vector<copy_buffer_t> & rv, uint64_t & fulfilled, uint8_t *read_buf,
uint8_t *clean_entry_bitmap, uint32_t item_start, uint32_t item_end);
bool read_checksum_block(blockstore_op_t *op, int rv_pos, uint64_t &fulfilled, uint64_t clean_loc, bool padded);
bool read_checksum_block(blockstore_op_t *op, int rv_pos, uint64_t &fulfilled, uint64_t clean_loc);
uint8_t* read_clean_meta_block(blockstore_op_t *read_op, uint64_t clean_loc, int rv_pos);
bool verify_padded_checksums(uint8_t *clean_entry_bitmap, uint8_t *csum_buf, uint32_t offset,
iovec *iov, int n_iov, std::function<void(uint32_t, uint32_t, uint32_t)> bad_block_cb);
@@ -267,16 +254,13 @@ class blockstore_impl_t: public blockstore_i
void handle_read_event(ring_data_t *data, blockstore_op_t *op);
// Write
bool forget_unstable_before_delete(blockstore_op_t *op, blockstore_dirty_db_t::iterator dirty_it);
bool enqueue_write(blockstore_op_t *op);
void unshift_stable_flush(obj_ver_id ov);
void cancel_all_writes(blockstore_op_t *op, blockstore_dirty_db_t::iterator dirty_it, int retval);
int dequeue_write(blockstore_op_t *op);
int dequeue_del(blockstore_op_t *op);
int continue_write(blockstore_op_t *op);
void release_journal_sectors(blockstore_op_t *op);
void handle_write_event(ring_data_t *data, blockstore_op_t *op);
void enqueue_own_op(blockstore_op_t *op);
// Sync
int continue_sync(blockstore_op_t *op);
@@ -296,7 +280,6 @@ class blockstore_impl_t: public blockstore_i
void mark_rolled_back(const obj_ver_id & ov);
void erase_dirty(blockstore_dirty_db_t::iterator dirty_start, blockstore_dirty_db_t::iterator dirty_end, uint64_t clean_loc);
void free_dirty_dyn_data(dirty_entry & e);
void free_dirty_dyn_data(copy_buffer_t & e);
// List
void process_list(blockstore_op_t *op);

View File

@@ -510,8 +510,6 @@ int blockstore_init_journal::loop()
goto resume_6;
else if (wait_state == 7)
goto resume_7;
else if (wait_state == 8)
goto resume_8;
printf("Reading blockstore journal\n");
if (!bs->journal.inmemory)
submitted_buf = memalign_or_die(MEM_ALIGNMENT, 2*bs->journal.block_size);
@@ -677,7 +675,6 @@ resume_1:
// zero out corrupted entry, if required
if (init_write_buf && !bs->readonly)
{
assert(init_write_sector > 0);
GET_SQE();
data->iov = { init_write_buf, (size_t)bs->journal.block_size };
data->callback = simple_callback;
@@ -755,43 +752,6 @@ resume_1:
}
bs->flusher->mark_trim_possible();
bs->journal.dirty_start = bs->journal.next_free;
if (!bs->readonly)
{
// Everything we just read may still be sitting in the volatile write cache of the device:
// the instance which wrote it is gone, so nothing is going to flush it any more, and a
// sync would find nothing of its own to sync and report success while a power outage
// could still take the recovered state away. Make it durable right here instead
{
bool fsync_data = !bs->disable_data_fsync;
bool fsync_meta = !bs->disable_meta_fsync;
bool fsync_journal = !bs->disable_journal_fsync;
// The same device may serve several roles - fsync each distinct one exactly once
if (bs->dsk.meta_fd == bs->dsk.data_fd)
fsync_data = fsync_data || fsync_meta, fsync_meta = false;
if (bs->dsk.journal_fd == bs->dsk.data_fd)
fsync_data = fsync_data || fsync_journal, fsync_journal = false;
else if (bs->dsk.journal_fd == bs->dsk.meta_fd)
fsync_meta = fsync_meta || fsync_journal, fsync_journal = false;
for (int i = 0; i < 3; i++)
{
if (i == 0 ? !fsync_data : (i == 1 ? !fsync_meta : !fsync_journal))
continue;
GET_SQE();
data->iov = { 0 };
data->callback = simple_callback;
io_uring_prep_fsync(sqe, i == 0 ? bs->dsk.data_fd : (i == 1 ? bs->dsk.meta_fd : bs->dsk.journal_fd), IORING_FSYNC_DATASYNC);
wait_count++;
}
if (wait_count > 0)
bs->ringloop->submit();
}
resume_8:
if (wait_count > 0)
{
wait_state = 8;
return 1;
}
}
printf(
"Journal entries loaded: %ju, free journal space: %ju bytes (%08jx..%08jx is used), free blocks: %ju / %ju\n",
entries_loaded,
@@ -829,8 +789,7 @@ int blockstore_init_journal::handle_journal_part(void *buf, uint64_t done_pos, u
{
auto buf_pos = proc_pos - done_pos + pos;
journal_entry *je = (journal_entry*)((uint8_t*)buf + buf_pos);
if ((bs->journal.block_size-pos) < 12 || je->size < 12 /*crc,magic,type,size*/ ||
je->magic != JOURNAL_MAGIC || buf_pos+je->size > len || je_crc32(je) != je->crc32 ||
if (je->magic != JOURNAL_MAGIC || buf_pos+je->size > len || je_crc32(je) != je->crc32 ||
je->type < JE_MIN || je->type > JE_MAX || started && je->crc32_prev != crc32_last)
{
if (pos == 0)
@@ -988,10 +947,10 @@ int blockstore_init_journal::handle_journal_part(void *buf, uint64_t done_pos, u
if (block_crc32 != *block_csums)
{
printf(
"Journal entry data is corrupt for small_write%s oid=%jx:%jx ver=%ju offset=%u len=%u data_offset=%jx - block %u crc32 %x != %x\n",
"Journal entry data is corrupt for small_write%s oid=%jx:%jx ver=%ju offset=%u len=%u - block %u crc32 %x != %x\n",
je->type == JE_SMALL_WRITE_INSTANT ? "_instant" : "",
je->small_write.oid.inode, je->small_write.oid.stripe, je->small_write.version,
je->small_write.offset, je->small_write.len, (uint64_t)je->small_write.data_offset,
je->small_write.offset, je->small_write.len,
pos, block_crc32, *block_csums
);
data_csum_valid = false;
@@ -1006,12 +965,7 @@ int blockstore_init_journal::handle_journal_part(void *buf, uint64_t done_pos, u
// interesting thing is that we must clear the corrupt entry if we're not readonly,
// because we don't write next entries in the same journal block
memset((uint8_t*)buf + proc_pos - done_pos + pos, 0, bs->journal.block_size - pos);
// If the corrupt entry was the first one in its block then clearing it
// empties the whole block, and an empty block is exactly what marks the
// end of the journal. Resuming after such a block would turn it into a
// permanent barrier: every later replay would stop at it again and
// discard everything written after it. So reuse the block instead.
bs->journal.next_free = pos == 0 ? proc_pos : prev_free;
bs->journal.next_free = prev_free;
init_write_buf = (uint8_t*)buf + proc_pos - done_pos;
init_write_sector = proc_pos;
return 0;
@@ -1045,7 +999,7 @@ int blockstore_init_journal::handle_journal_part(void *buf, uint64_t done_pos, u
memcpy((uint8_t*)dyn+sizeof(int), dyn_from, dyn_size);
}
bs->dirty_db.emplace(ov, (dirty_entry){
.state = (uint32_t)(BS_ST_SMALL_WRITE | BS_ST_SYNCED | (je->type == JE_SMALL_WRITE_INSTANT ? BS_ST_INSTANT : 0)),
.state = (BS_ST_SMALL_WRITE | BS_ST_SYNCED),
.flags = 0,
.location = location,
.offset = je->small_write.offset,
@@ -1129,7 +1083,7 @@ int blockstore_init_journal::handle_journal_part(void *buf, uint64_t done_pos, u
memcpy((uint8_t*)dyn+sizeof(int), dyn_from, dyn_size);
}
auto dirty_it = bs->dirty_db.emplace(ov, (dirty_entry){
.state = (uint32_t)(BS_ST_BIG_WRITE | BS_ST_SYNCED | (je->type == JE_BIG_WRITE_INSTANT ? BS_ST_INSTANT : 0)),
.state = (BS_ST_BIG_WRITE | BS_ST_SYNCED),
.flags = 0,
.location = je->big_write.location,
.offset = je->big_write.offset,
@@ -1216,12 +1170,6 @@ int blockstore_init_journal::handle_journal_part(void *buf, uint64_t done_pos, u
clean_it->second.version < je->del.version);
if (!clean_exists && dirty_exists)
{
// Stabilize previous entries to fix inode_space_stats and used_blocks (FIXME - remove it)
obj_ver_id ov = {
.oid = je->del.oid,
.version = dirty_it->first.version,
};
bs->mark_stable(ov, true);
// Clean entry doesn't exist. This means that the delete is already flushed.
// So we must not flush this object anymore.
erase_dirty_object(dirty_it);

View File

@@ -15,7 +15,6 @@ blockstore_journal_check_t::blockstore_journal_check_t(blockstore_impl_t *bs)
first_sector = -1;
next_in_pos = bs->journal.in_sector_pos;
right_dir = next_pos >= bs->journal.used_start;
give_up = false;
}
// Check if we can write <required> entries of <size> bytes and <data_after> data bytes after them to the journal
@@ -25,9 +24,7 @@ int blockstore_journal_check_t::check_available(blockstore_op_t *op, int entries
int required = entries_required;
while (1)
{
// Must match journal_t::entry_fits()
int fits = next_pos == bs->journal.next_free && bs->journal.sector_info[next_sector].written &&
(bs->journal.no_same_sector_overwrites || bs->journal.sector_info[next_sector].flush_count > 0)
int fits = bs->journal.no_same_sector_overwrites && next_pos == bs->journal.next_free && bs->journal.sector_info[next_sector].written
? 0
: (bs->journal.block_size - next_in_pos) / size;
if (fits > 0)
@@ -122,20 +119,13 @@ int blockstore_journal_check_t::check_available(blockstore_op_t *op, int entries
if (bs->log_level > 5)
{
printf(
"Ran out of journal space (used_start=%08jx, next_free=%08jx, dirty_start=%08jx, queued %zu)\n",
bs->journal.used_start, bs->journal.next_free, bs->journal.dirty_start, bs->flusher->get_queue_size()
"Ran out of journal space (used_start=%08jx, next_free=%08jx, dirty_start=%08jx)\n",
bs->journal.used_start, bs->journal.next_free, bs->journal.dirty_start
);
}
if (!bs->flusher->may_advance())
{
give_up = true;
}
else
{
PRIV(op)->wait_for = WAIT_JOURNAL;
PRIV(op)->wait_detail = bs->wait_journal_counter;
bs->flusher->request_trim();
}
PRIV(op)->wait_for = WAIT_JOURNAL;
bs->flusher->request_trim();
PRIV(op)->wait_detail = bs->journal.used_start;
return 0;
}
return 1;
@@ -185,8 +175,6 @@ journal_entry* prefill_single_journal_entry(journal_t & journal, uint16_t type,
void blockstore_impl_t::prepare_journal_sector_write(int cur_sector, blockstore_op_t *op)
{
// Don't submit the same sector twice in the same batch
assert(journal.sector_info[cur_sector].dirty);
bool submitted_now = false;
if (!journal.sector_info[cur_sector].submit_id)
{
io_uring_sqe *sqe = get_sqe();
@@ -205,24 +193,16 @@ void blockstore_impl_t::prepare_journal_sector_write(int cur_sector, blockstore_
(size_t)journal.block_size
};
data->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
assert(journal.sector_info[cur_sector].offset > 0);
assert(journal.sector_info[cur_sector].offset+journal.block_size <= dsk.journal_len);
io_uring_prep_writev(
sqe, dsk.journal_fd, &data->iov, 1, journal.offset + journal.sector_info[cur_sector].offset
);
submitted_now = true;
}
journal.sector_info[cur_sector].dirty = false;
// But always remember that this operation has to wait until this exact journal write is finished.
// An operation which appends to a sector another one has already queued in this same batch also
// has to wait for everything queued in between - the journal data write of that other operation
// in particular. That data lies behind our entry on the disk, and recovery stops at the first
// hole in the journal, so completing before it is durable would let an acknowledged operation
// disappear together with the unfinished write in front of it
journal.flushing_ops.emplace(journal.submit_id, (pending_journaling_t){
// But always remember that this operation has to wait until this exact journal write is finished
journal.flushing_ops.emplace(journal.sector_info[cur_sector].submit_id, (pending_journaling_t){
.pending = 1,
// Only the operation which queued the sector write itself releases its flush_count
.sector = submitted_now ? cur_sector : -1,
.sector = cur_sector,
.op = op,
});
auto priv = PRIV(op);
@@ -246,33 +226,38 @@ void blockstore_impl_t::handle_journal_write(ring_data_t *data, uint64_t flush_i
{
journal.sector_info[fl_it->second.sector].flush_count--;
}
// Mark every operation waiting for this exact write as no longer pending
for (auto it = fl_it; it != journal.flushing_ops.end() && it->first == flush_id; it++)
auto is_first = fl_it == journal.flushing_ops.begin();
while (fl_it != journal.flushing_ops.end())
{
it->second.pending = 0;
}
// Do not complete an operation while an earlier journal write is still in flight:
// a power outage would then leave a hole in the journal in front of it, and recovery
// stops at the first hole, so an already acknowledged operation would be lost.
// Only release operations when the write that just finished is the oldest unfinished
// one, and then cascade over the following writes that are already finished, stopping
// at the first one which isn't.
if (fl_it != journal.flushing_ops.begin())
{
return;
}
while (fl_it != journal.flushing_ops.end() && !fl_it->second.pending)
{
auto priv = PRIV(fl_it->second.op);
priv->pending_ops--;
assert(priv->pending_ops >= 0);
if (priv->pending_ops == 0)
bool del = false;
if (fl_it->first == flush_id)
{
release_journal_sectors(fl_it->second.op);
priv->op_state++;
ringloop->wakeup();
fl_it->second.pending = 0;
del = is_first;
}
else
{
del = !fl_it->second.pending;
}
if (del)
{
// Do not complete this operation if previous writes are unfinished
// Otherwise also complete following operations waiting for this one
auto priv = PRIV(fl_it->second.op);
priv->pending_ops--;
assert(priv->pending_ops >= 0);
if (priv->pending_ops == 0)
{
release_journal_sectors(fl_it->second.op);
priv->op_state++;
ringloop->wakeup();
}
journal.flushing_ops.erase(fl_it++);
}
else
{
fl_it++;
}
journal.flushing_ops.erase(fl_it++);
}
}

View File

@@ -23,12 +23,12 @@ struct pending_journaling_t
struct journal_t
{
int fd = -1;
int fd;
bool inmemory = false;
bool flush_journal = false;
void *buffer = NULL;
uint64_t block_size = 0;
uint64_t block_size;
uint64_t offset, len;
// Next free block offset
uint64_t next_free = 0;
@@ -41,7 +41,7 @@ struct journal_t
// Current sector(s) used for writing
void *sector_buf = NULL;
journal_sector_info_t *sector_info = NULL;
uint64_t sector_count = 0;
uint64_t sector_count;
bool no_same_sector_overwrites = false;
int cur_sector = 0;
int in_sector_pos = 0;
@@ -59,10 +59,8 @@ struct journal_t
void dump_diagnostics();
inline bool entry_fits(int size)
{
// Never append to a sector which is still being written: re-submitting it is probably UB
return !(block_size - in_sector_pos < size ||
sector_info[cur_sector].written &&
(no_same_sector_overwrites || sector_info[cur_sector].flush_count > 0));
no_same_sector_overwrites && sector_info[cur_sector].written);
}
};
@@ -72,7 +70,6 @@ struct blockstore_journal_check_t
uint64_t next_pos, next_sector, next_in_pos;
int sectors_to_write, first_sector;
bool right_dir; // writing to the end or the beginning of the ring buffer
bool give_up;
blockstore_journal_check_t(blockstore_impl_t *bs);
int check_available(blockstore_op_t *op, int required, int size, int data_after);

View File

@@ -110,7 +110,7 @@ int blockstore_impl_t::fulfill_read(blockstore_op_t *read_op,
(*dyn_data)++;
}
// Submit the journal checksum block read
if (!read_checksum_block(read_op, 1, fulfilled, item_location - item_start, false))
if (!read_checksum_block(read_op, 1, fulfilled, item_location - item_start))
{
r = 0;
}
@@ -173,8 +173,7 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
if (read_end == read_offset)
return 0;
int required = 0;
if (read_buf)
read_buf -= read_offset;
read_buf -= read_offset;
uint32_t last_block = (read_end-1)/dsk.csum_block_size;
uint32_t start_block = read_offset/dsk.csum_block_size;
uint32_t item_start_block = item_start/dsk.csum_block_size;
@@ -190,20 +189,20 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
};
rv.insert(rv.begin() + pos, el);
if (read_buf)
memset(read_buf + el.offset, 0, el.len);
memset(read_buf + el.offset - read_offset, 0, el.len);
fulfilled += el.len;
return 1;
};
if (read_offset < item_start)
{
// Zero-fill the beginning
find_holes(rv, read_offset, item_start < read_end ? item_start : read_end, zero_range);
find_holes(rv, read_offset, item_start, zero_range);
read_offset = item_start;
}
if (read_end > item_end)
{
// Zero-fill the end
find_holes(rv, read_offset < item_end ? item_end : read_offset, read_end, zero_range);
find_holes(rv, item_end, read_end, zero_range);
read_end = item_end;
}
while (start_block <= last_block)
@@ -228,12 +227,10 @@ int blockstore_impl_t::fill_partial_checksum_blocks(std::vector<copy_buffer_t> &
}
end_block++;
// OK, mark this range as required
uint32_t rv_start = start_block*dsk.csum_block_size < item_start ? item_start : start_block*dsk.csum_block_size;
uint32_t rv_end = end_block*dsk.csum_block_size > item_end ? item_end : end_block*dsk.csum_block_size;
rv.push_back((copy_buffer_t){
.copy_flags = COPY_BUF_CSUM_FILL | (from_journal ? COPY_BUF_JOURNALED_BIG : 0),
.offset = rv_start,
.len = rv_end - rv_start,
.offset = start_block*dsk.csum_block_size,
.len = (end_block-start_block)*dsk.csum_block_size,
// save checksum reference if we're reading clean data from the journal
.csum_buf = from_journal
? clean_entry_bitmap + dsk.clean_entry_bitmap_size + (start_block-item_start_block)*(dsk.data_csum_type & 0xFF)
@@ -281,7 +278,7 @@ bool blockstore_impl_t::read_range_fulfilled(std::vector<copy_buffer_t> & rv, ui
.offset = bmp_start*dsk.bitmap_granularity,
.len = (bmp_pos-bmp_start)*dsk.bitmap_granularity,
};
rv.insert(rv.begin() + pos + diff, el);
rv.insert(rv.begin() + pos, el);
if (read_buf)
memset(read_buf + el.offset, 0, el.len);
fulfilled += el.len;
@@ -302,7 +299,7 @@ bool blockstore_impl_t::read_range_fulfilled(std::vector<copy_buffer_t> & rv, ui
return all_done;
}
bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uint64_t &fulfilled, uint64_t clean_loc, bool padded)
bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uint64_t &fulfilled, uint64_t clean_loc)
{
auto & rv = PRIV(op)->read_vec;
auto *vi = &rv[rv.size()-rv_pos];
@@ -337,22 +334,10 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
}
return 0;
});
if (padded && (item_start % dsk.csum_block_size))
{
n_iov++;
}
if (padded && (item_end % dsk.csum_block_size))
{
n_iov++;
}
void *buf = memalign_or_die(MEM_ALIGNMENT, fill_size + n_iov*sizeof(struct iovec));
iovec *iov = (struct iovec*)((uint8_t*)buf+fill_size);
n_iov = 0;
fill_size = 0;
if (padded && (item_start % dsk.csum_block_size))
{
iov[n_iov++] = (struct iovec){ zero_object, item_start % dsk.csum_block_size };
}
find_holes(rv, item_start, item_end, [&](int pos, bool alloc, uint32_t cur_start, uint32_t cur_end)
{
int res = 0;
@@ -390,31 +375,18 @@ bool blockstore_impl_t::read_checksum_block(blockstore_op_t *op, int rv_pos, uin
}
return res;
});
if (padded && (item_end % dsk.csum_block_size))
{
iov[n_iov++] = (struct iovec){ zero_object, dsk.csum_block_size - (item_end % dsk.csum_block_size) };
}
vi = &rv[rv.size()-rv_pos];
// Save buf into read_vec too but in a creepy way
// FIXME: Shit, something else should be invented %)
*vi = (copy_buffer_t){
.copy_flags = vi->copy_flags,
.offset = (padded ? vi->offset - vi->offset % dsk.csum_block_size : vi->offset),
.offset = vi->offset,
.len = ((uint64_t)n_iov << 32) | fill_size,
.disk_offset = clean_loc + (padded ? vi->offset - vi->offset % dsk.csum_block_size : vi->offset),
.disk_offset = clean_loc + item_start,
.buf = (uint8_t*)buf,
.csum_buf = vi->csum_buf,
.dyn_data = vi->dyn_data,
};
if (padded && (item_start % dsk.csum_block_size))
{
iov++;
n_iov--;
}
if (padded && (item_end % dsk.csum_block_size))
{
n_iov--;
}
int submit_fd = (vi->copy_flags & COPY_BUF_JOURNAL ? dsk.journal_fd : dsk.data_fd);
uint64_t submit_offset = (vi->copy_flags & COPY_BUF_JOURNAL ? journal.offset : dsk.data_offset);
uint32_t d_pos = 0;
@@ -511,7 +483,7 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
// Copy from memory or read from journal, possibly checking checksums
if (!fulfill_read(read_op, fulfilled, dirty.offset, dirty.offset + dirty.len,
dirty.state, dirty_it->first.version, dirty.location, dirty.journal_sector+1,
journal.inmemory || !dsk.csum_block_size ? NULL : bmp_ptr+dsk.clean_entry_bitmap_size, dyn_data))
journal.inmemory ? NULL : bmp_ptr+dsk.clean_entry_bitmap_size, dyn_data))
{
goto undo_read;
}
@@ -549,7 +521,6 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
// May happen if there are entries in dirty_db but all of them are !version_ok
read_op->version = 0;
read_op->retval = -ENOENT;
release_read_vec(read_op);
FINISH_OP(read_op);
return 2;
}
@@ -563,9 +534,6 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
// region is not allocated - return zeroes
memset(read_op->buf, 0, read_op->len);
}
// Nothing was submitted, so no completion handler will run - release the
// references the read vector holds on entry bitmaps and checksums
release_read_vec(read_op);
read_op->retval = read_op->len;
FINISH_OP(read_op);
return 2;
@@ -580,29 +548,25 @@ int blockstore_impl_t::dequeue_read(blockstore_op_t *read_op)
}
}
read_op->retval = 0;
reads_in_flight.insert(read_op);
return 2;
undo_read:
// need to wait. undo added requests, don't dequeue op
release_clean(read_op);
release_read_vec(read_op);
rv.clear();
return 0;
}
// Free the buffers and drop the references that a read vector holds on the bitmaps and
// checksums of dirty entries. Safe to call more than once.
void blockstore_impl_t::release_read_vec(blockstore_op_t *read_op)
{
for (auto & vec: PRIV(read_op)->read_vec)
for (auto & vec: rv)
{
if ((vec.copy_flags & COPY_BUF_CSUM_FILL) && vec.buf)
{
free(vec.buf);
vec.buf = NULL;
}
free_dirty_dyn_data(vec);
if (vec.dyn_data && --(*vec.dyn_data) == 0) // refcount
{
free(vec.dyn_data);
vec.dyn_data = NULL;
}
}
rv.clear();
return 0;
}
void blockstore_impl_t::release_clean(blockstore_op_t *op)
@@ -710,7 +674,7 @@ bool blockstore_impl_t::fulfill_clean_read(blockstore_op_t *read_op, uint64_t &
}
for (int i = req; i > 0; i--)
{
if (!read_checksum_block(read_op, i, fulfilled, clean_loc, true))
if (!read_checksum_block(read_op, i, fulfilled, clean_loc))
{
return false;
}
@@ -947,35 +911,27 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
{
for (int i = 0; i < rv.size(); i++)
{
// Every entry holds a reference to the bitmap/checksums of its dirty
// entry, so it has to be released even when the entry itself is skipped
bool skip = false;
if (rv[i].copy_flags & COPY_BUF_META_BLOCK)
{
// Metadata read. Skip
assert(!meta_block);
meta_block = rv[i].buf;
rv[i].buf = NULL;
skip = true;
continue;
}
else if (rv[i].copy_flags & COPY_BUF_ZERO)
if (rv[i].copy_flags & COPY_BUF_ZERO)
{
// Zero read
skip = true;
continue;
}
else if (rv[i].copy_flags & COPY_BUF_COALESCED)
if (rv[i].copy_flags & COPY_BUF_COALESCED)
{
// Sub-block shared with another read. Skip
skip = true;
continue;
}
else if ((rv[i].copy_flags & COPY_BUF_JOURNAL) && journal.inmemory)
if ((rv[i].copy_flags & COPY_BUF_JOURNAL) && journal.inmemory)
{
// Do not check journal checksums in-memory
skip = true;
}
if (skip)
{
free_dirty_dyn_data(rv[i]);
continue;
}
iovec single_iov = {};
@@ -1043,7 +999,11 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
}
free(rv[i].buf);
rv[i].buf = NULL;
free_dirty_dyn_data(rv[i]);
if (rv[i].dyn_data && --(*rv[i].dyn_data) == 0) // refcount
{
free(rv[i].dyn_data);
rv[i].dyn_data = NULL;
}
}
}
else
@@ -1052,11 +1012,10 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
{
if (vec.copy_flags & COPY_BUF_META_BLOCK)
{
// Metadata read. Skip, but still release the entry reference
// Metadata read. Skip
assert(!meta_block);
meta_block = vec.buf;
vec.buf = NULL;
free_dirty_dyn_data(vec);
continue;
}
if (vec.csum_buf)
@@ -1078,7 +1037,11 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
}
}
}
free_dirty_dyn_data(vec);
if (vec.dyn_data && --(*vec.dyn_data) == 0) // refcount
{
free(vec.dyn_data);
vec.dyn_data = NULL;
}
}
}
if (meta_block)
@@ -1107,7 +1070,6 @@ void blockstore_impl_t::handle_read_event(ring_data_t *data, blockstore_op_t *op
}
if (op->retval == 0)
op->retval = op->len;
reads_in_flight.erase(op);
FINISH_OP(op);
}
}
@@ -1127,14 +1089,6 @@ int blockstore_impl_t::read_bitmap(object_id oid, uint64_t target_version, void
// Condition has to be the same as in dequeue_read()
if (!IS_IN_FLIGHT(dirty_it->second.state) && target_version >= dirty_it->first.version)
{
if (IS_DELETE(dirty_it->second.state))
{
if (result_version)
*result_version = 0;
if (bitmap)
memset(bitmap, 0, dsk.clean_entry_bitmap_size);
return -ENOENT;
}
if (result_version)
*result_version = dirty_it->first.version;
if (bitmap)

View File

@@ -28,7 +28,7 @@ int blockstore_impl_t::dequeue_rollback(blockstore_op_t *op)
else
{
dirty_it--;
if (dirty_it->first.oid != ov.oid || dirty_it->first.version <= ov.version)
if (dirty_it->first.oid != ov.oid || dirty_it->first.version < ov.version)
{
// Already rolled back, skip this object version
return STAB_SPLIT_DONE;
@@ -40,12 +40,8 @@ int blockstore_impl_t::dequeue_rollback(blockstore_op_t *op)
// Object write is still in progress. Wait until the write request completes
return STAB_SPLIT_WAIT;
}
else if (IS_STABLE(dirty_it->second.state))
{
// Can't jump back over a stable version
return -EBUSY;
}
else if (!IS_SYNCED(dirty_it->second.state))
else if (!IS_SYNCED(dirty_it->second.state) ||
IS_STABLE(dirty_it->second.state))
{
// Sync the object
return STAB_SPLIT_SYNC;
@@ -67,12 +63,6 @@ int blockstore_impl_t::dequeue_rollback(blockstore_op_t *op)
blockstore_journal_check_t space_check(this);
if (!space_check.check_available(op, op->len, sizeof(journal_entry_rollback), 0))
{
if (space_check.give_up)
{
op->retval = -EAGAIN;
FINISH_OP(op);
return 2;
}
return 0;
}
// There is sufficient space. Check SQEs
@@ -172,10 +162,7 @@ void blockstore_impl_t::mark_rolled_back(const obj_ver_id & ov)
if (unstab_it != unstable_writes.end())
{
if (max_unstable == 0)
{
unstable_writes.erase(unstab_it);
wakeup_wait_journal();
}
else
unstab_it->second = max_unstable;
}
@@ -205,8 +192,6 @@ void blockstore_impl_t::erase_dirty(blockstore_dirty_db_t::iterator dirty_start,
if ((dirty_it->second.state & BS_ST_WORKFLOW_MASK) == BS_ST_WAIT_DEL)
{
dirty_it->second.state = (dirty_it->second.state & ~BS_ST_WORKFLOW_MASK) | next_state;
// Entering WAIT_DEL -> request_trim, exiting -> release_trim
flusher->release_trim();
if (IS_BIG_WRITE(dirty_it->second.state))
{
next_state = BS_ST_WAIT_BIG;
@@ -233,28 +218,22 @@ void blockstore_impl_t::erase_dirty(blockstore_dirty_db_t::iterator dirty_start,
#endif
data_alloc->set(dirty_it->second.location / dsk.data_block_size, false);
}
// A big write is only journaled when it gets synced, so an entry erased before that
// holds no journal sector reference at all
if (dirty_it->second.journal_sector != 0)
{
auto used_it = journal.used_sectors.find(dirty_it->second.journal_sector);
uint64_t used = used_it == journal.used_sectors.end() ? UINT64_MAX : --used_it->second;
auto used = --journal.used_sectors.at(dirty_it->second.journal_sector);
#ifdef BLOCKSTORE_DEBUG
printf(
"remove usage of journal offset %08jx by %jx:%jx v%ju (%ju refs)\n", dirty_it->second.journal_sector,
dirty_it->first.oid.inode, dirty_it->first.oid.stripe, dirty_it->first.version, used
);
printf(
"remove usage of journal offset %08jx by %jx:%jx v%ju (%ju refs)\n", dirty_it->second.journal_sector,
dirty_it->first.oid.inode, dirty_it->first.oid.stripe, dirty_it->first.version, used
);
#endif
if (used == 0)
if (used == 0)
{
journal.used_sectors.erase(dirty_it->second.journal_sector);
if (dirty_it->second.journal_sector == journal.sector_info[journal.cur_sector].offset)
{
journal.used_sectors.erase(dirty_it->second.journal_sector);
if (dirty_it->second.journal_sector == journal.sector_info[journal.cur_sector].offset)
{
// Mark current sector as "full" to select the new one
journal.in_sector_pos = dsk.journal_block_size;
}
flusher->mark_trim_possible();
// Mark current sector as "full" to select the new one
journal.in_sector_pos = dsk.journal_block_size;
}
flusher->mark_trim_possible();
}
free_dirty_dyn_data(dirty_it->second);
if (dirty_it == dirty_start)
@@ -281,17 +260,4 @@ void blockstore_impl_t::free_dirty_dyn_data(dirty_entry & e)
}
}
void blockstore_impl_t::free_dirty_dyn_data(copy_buffer_t & e)
{
if (e.dyn_data)
{
assert(alloc_dyn_data);
if (--*e.dyn_data == 0) // refcount
{
free(e.dyn_data);
}
e.dyn_data = NULL;
}
}
} // namespace v1

View File

@@ -73,11 +73,7 @@ static void append_version(ver_vector_t & vec, obj_ver_id ov)
vec.items[vec.size++] = ov;
}
// Move the unsynced versions of <ov.oid> which the operation has to sync first from <check>
// into <to>. Stabilization needs the versions up to <ov.version> synced, and a rollback needs
// the versions above it - those are the ones it discards, and a rollback can only be journaled
// after they reach the journal themselves
static bool check_unsynced(std::vector<obj_ver_id> & check, obj_ver_id ov, std::vector<obj_ver_id> & to, int *count, bool above)
static bool check_unsynced(std::vector<obj_ver_id> & check, obj_ver_id ov, std::vector<obj_ver_id> & to, int *count)
{
bool found = false;
int j = 0, k = 0;
@@ -85,7 +81,7 @@ static bool check_unsynced(std::vector<obj_ver_id> & check, obj_ver_id ov, std::
{
if (check[j] == ov)
found = true;
if (check[j].oid == ov.oid && (above ? check[j].version > ov.version : check[j].version <= ov.version))
if (check[j].oid == ov.oid && check[j].version <= ov.version)
{
to.push_back(check[j++]);
if (count)
@@ -191,9 +187,8 @@ int blockstore_impl_t::split_stab_op(blockstore_op_t *op, std::function<int(obj_
PRIV(op)->sync_small_writes.clear();
add_sync = true;
}
bool above = op->opcode == BS_OP_ROLLBACK;
check_unsynced(unsynced_small_writes, *v, PRIV(op)->sync_small_writes, NULL, above);
check_unsynced(unsynced_big_writes, *v, PRIV(op)->sync_big_writes, &unsynced_big_write_count, above);
check_unsynced(unsynced_small_writes, *v, PRIV(op)->sync_small_writes, NULL);
check_unsynced(unsynced_big_writes, *v, PRIV(op)->sync_big_writes, &unsynced_big_write_count);
}
else /* if (action == STAB_SPLIT_TODO) */
{
@@ -215,8 +210,6 @@ int blockstore_impl_t::split_stab_op(blockstore_op_t *op, std::function<int(obj_
if (!todo && !bad_vers.size)
{
// Already stable
free(good_vers.items);
free(bad_vers.items);
op->retval = 0;
FINISH_OP(op);
return 2;
@@ -224,10 +217,7 @@ int blockstore_impl_t::split_stab_op(blockstore_op_t *op, std::function<int(obj_
op->retval = 0;
if (!todo && !add_sync)
{
// Only wait for inflight writes or current in-progress syncs.
// The operation is retried from scratch, so the version vectors go with it
free(good_vers.items);
free(bad_vers.items);
// Only wait for inflight writes or current in-progress syncs
return 0;
}
blockstore_op_t *sync_op = NULL, *split_stab_op = NULL;
@@ -372,12 +362,6 @@ int blockstore_impl_t::dequeue_stable(blockstore_op_t *op)
blockstore_journal_check_t space_check(this);
if (!space_check.check_available(op, op->len, sizeof(journal_entry_stable), 0))
{
if (space_check.give_up)
{
op->retval = -EAGAIN;
FINISH_OP(op);
return 2;
}
return 0;
}
// There is sufficient space. Check SQEs
@@ -461,9 +445,9 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
{
break;
}
if (!IS_SYNCED(back_it->second.state))
if (!IS_STABLE(back_it->second.state))
{
// There are preceding unsynced versions, can't flush <v>
// There are preceding unstable versions, can't flush <v>
return;
}
}
@@ -479,7 +463,6 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
v.version = dirty_it->first.version;
}
}
auto erase_end = dirty_db.end();
while (1)
{
bool was_stable = IS_STABLE(dirty_it->second.state);
@@ -487,7 +470,6 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
{
dirty_it->second.state = (dirty_it->second.state & ~BS_ST_WORKFLOW_MASK) | BS_ST_STABLE;
// Allocations and deletions are counted when they're stabilized
// FIXME: Count-on-stabilize logic is actually really strange, reconsider it
if (IS_BIG_WRITE(dirty_it->second.state))
{
int exists = -1;
@@ -541,45 +523,43 @@ void blockstore_impl_t::mark_stable(obj_ver_id v, bool forget_dirty)
exit(1);
}
if (forget_dirty && (IS_BIG_WRITE(dirty_it->second.state) ||
IS_DELETE(dirty_it->second.state)) && erase_end == dirty_db.end())
IS_DELETE(dirty_it->second.state)))
{
// Big write overrides all previous dirty entries
erase_end = dirty_it;
auto erase_end = dirty_it;
while (dirty_it != dirty_db.begin())
{
dirty_it--;
if (dirty_it->first.oid != v.oid)
{
dirty_it++;
break;
}
}
auto & clean_db = clean_db_shard(v.oid);
auto clean_it = clean_db.find(v.oid);
uint64_t clean_loc = clean_it != clean_db.end()
? clean_it->second.location : UINT64_MAX;
erase_dirty(dirty_it, erase_end, clean_loc);
break;
}
// Stopping at an already stable entry is only correct when we aren't erasing anything:
// everything below a big write or a deletion is dead and has to be erased even if it
// was stabilized earlier. Entries are stabilized one by one during the journal replay,
// so a small write in between used to hide an older big write from this walk forever,
// leaving its data block claimed by an entry which isn't current any more.
// <forget_dirty> is only set by the journal replay, so the walk stays short at runtime
if (!forget_dirty && was_stable || dirty_it == dirty_db.begin())
if (was_stable || dirty_it == dirty_db.begin())
{
break;
}
dirty_it--;
if (dirty_it->first.oid != v.oid)
{
dirty_it++;
break;
}
}
flusher->enqueue_flush(v);
if (erase_end != dirty_db.end())
{
// Erase only after marking previous entries stable to not skip used_blocks++
auto & clean_db = clean_db_shard(v.oid);
auto clean_it = clean_db.find(v.oid);
uint64_t clean_loc = clean_it != clean_db.end()
? clean_it->second.location : UINT64_MAX;
erase_dirty(dirty_it, erase_end, clean_loc);
}
}
auto unstab_it = unstable_writes.find(v.oid);
if (unstab_it != unstable_writes.end() &&
unstab_it->second <= v.version)
{
unstable_writes.erase(unstab_it);
wakeup_wait_journal();
}
}

View File

@@ -79,7 +79,6 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
// 2nd step: Data device is synced, prepare & write journal entries
// Check space in the journal and journal memory buffers
blockstore_journal_check_t space_check(this);
bool space_ok = true;
if (dsk.csum_block_size)
{
// More complex check because all journal entries have different lengths
@@ -91,34 +90,13 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
uint64_t dyn_size = dsk.dirty_dyn_size(dirty_entry.offset, dirty_entry.len);
if (!space_check.check_available(op, 1, sizeof(journal_entry_big_write) + dyn_size, 0))
{
space_ok = false;
break;
return 0;
}
}
}
else if (!space_check.check_available(op, PRIV(op)->sync_big_writes.size(),
sizeof(journal_entry_big_write) + dsk.clean_entry_bitmap_size, 0))
{
space_ok = false;
}
if (!space_ok)
{
if (space_check.give_up)
{
// Return everything back
unsynced_big_write_count += PRIV(op)->sync_big_writes.size();
PRIV(op)->sync_big_writes.insert(PRIV(op)->sync_big_writes.end(),
unsynced_big_writes.begin(), unsynced_big_writes.end());
PRIV(op)->sync_big_writes.swap(unsynced_big_writes);
PRIV(op)->sync_big_writes.clear();
PRIV(op)->sync_small_writes.insert(PRIV(op)->sync_small_writes.end(),
unsynced_small_writes.begin(), unsynced_small_writes.end());
PRIV(op)->sync_small_writes.swap(unsynced_small_writes);
PRIV(op)->sync_small_writes.clear();
op->retval = -EAGAIN;
FINISH_OP(op);
return 2;
}
return 0;
}
// Check SQEs. Don't bother about merging, submit each journal sector as a separate request
@@ -198,54 +176,60 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
void blockstore_impl_t::ack_sync(blockstore_op_t *op)
{
// Handle states
auto mark_sync = [&](obj_ver_id & ov)
for (auto it = PRIV(op)->sync_big_writes.begin(); it != PRIV(op)->sync_big_writes.end(); it++)
{
auto dirty_it = dirty_db.find(ov);
bool is_big = ((dirty_it->second.state & BS_ST_TYPE_MASK) == BS_ST_BIG_WRITE);
#ifdef BLOCKSTORE_DEBUG
printf("Ack sync big %jx:%jx v%ju\n", it->oid.inode, it->oid.stripe, it->version);
#endif
auto & unstab = unstable_writes[it->oid];
unstab = unstab < it->version ? it->version : unstab;
auto dirty_it = dirty_db.find(*it);
dirty_it->second.state = ((dirty_it->second.state & ~BS_ST_WORKFLOW_MASK) | BS_ST_SYNCED);
uint64_t stable_ver = 0;
if (IS_INSTANT(dirty_it->second.state))
if (dirty_it->second.state & BS_ST_INSTANT)
{
stable_ver = ov.version;
mark_stable(dirty_it->first);
}
else
{
unstable_unsynced--;
assert(unstable_unsynced >= 0);
wakeup_wait_journal();
}
dirty_it++;
while (dirty_it != dirty_db.end() && dirty_it->first.oid == ov.oid)
while (dirty_it != dirty_db.end() && dirty_it->first.oid == it->oid)
{
if (is_big && (dirty_it->second.state & BS_ST_WORKFLOW_MASK) == BS_ST_WAIT_BIG)
if ((dirty_it->second.state & BS_ST_WORKFLOW_MASK) == BS_ST_WAIT_BIG)
{
dirty_it->second.state = (dirty_it->second.state & ~BS_ST_WORKFLOW_MASK) | BS_ST_IN_FLIGHT;
}
else if ((dirty_it->second.state & BS_ST_WORKFLOW_MASK) == BS_ST_SYNCED &&
IS_INSTANT(dirty_it->second.state))
{
stable_ver = dirty_it->first.version;
}
dirty_it++;
}
if (stable_ver != 0)
{
mark_stable((obj_ver_id){ .oid = ov.oid, .version = stable_ver });
}
};
for (auto & ov: PRIV(op)->sync_big_writes)
{
#ifdef BLOCKSTORE_DEBUG
printf("Ack sync big %jx:%jx v%ju\n", ov.oid.inode, ov.oid.stripe, ov.version);
#endif
mark_sync(ov);
}
for (auto & ov: PRIV(op)->sync_small_writes)
for (auto it = PRIV(op)->sync_small_writes.begin(); it != PRIV(op)->sync_small_writes.end(); it++)
{
#ifdef BLOCKSTORE_DEBUG
printf("Ack sync small %jx:%jx v%ju\n", ov.oid.inode, ov.oid.stripe, ov.version);
printf("Ack sync small %jx:%jx v%ju\n", it->oid.inode, it->oid.stripe, it->version);
#endif
mark_sync(ov);
auto & unstab = unstable_writes[it->oid];
unstab = unstab < it->version ? it->version : unstab;
if (dirty_db[*it].state == (BS_ST_DELETE | BS_ST_WRITTEN))
{
dirty_db[*it].state = (BS_ST_DELETE | BS_ST_SYNCED);
// Deletions are treated as immediately stable
mark_stable(*it);
}
else /* (BS_ST_INSTANT?) | BS_ST_SMALL_WRITE | BS_ST_WRITTEN */
{
dirty_db[*it].state = (dirty_db[*it].state & ~BS_ST_WORKFLOW_MASK) | BS_ST_SYNCED;
if (dirty_db[*it].state & BS_ST_INSTANT)
{
mark_stable(*it);
}
else
{
unstable_unsynced--;
assert(unstable_unsynced >= 0);
}
}
}
op->retval = 0;
FINISH_OP(op);

View File

@@ -6,121 +6,25 @@
namespace v1 {
// A deletion supersedes everything written before it and is stable as soon as it's written,
// so versions preceding it that aren't in the journal yet are free to forget. And they have to
// be forgotten: a big write is only journaled when it gets synced, so leaving one behind would
// put its journal entry after the deletion and recovery would replay them in the wrong
// order, resurrecting the object. Returns false if a preceding write is still running -
// its completion refers to the entry, so we have to let it finish first.
//
// Entries which are already synced must NOT be forgotten. Their journal entries are on the disk
// and can't be taken back, while the deletion itself only reaches the journal on the next sync.
// Forgetting them drops their journal references, and the journal may then be trimmed past the
// object's big write while its small writes still sit in later sectors kept alive by other
// objects - recovery would then replay those small writes with no base under them.
// An entry which isn't in the unsynced lists any more has been taken over by a sync which is
// still in flight: continue_sync() swaps those lists over to itself, but the entries only get
// the SYNCED state later, in ack_sync()
static bool is_still_unsynced(const std::vector<obj_ver_id> & unsynced, obj_ver_id ov)
{
for (auto & u: unsynced)
if (u == ov)
return true;
return false;
}
bool blockstore_impl_t::forget_unstable_before_delete(blockstore_op_t *op, blockstore_dirty_db_t::iterator dirty_it)
{
if (immediate_commit == IMMEDIATE_ALL)
{
// Everything is journaled as soon as it's written here, so nothing can end up
// behind the deletion in the journal and there is nothing to forget
return true;
}
auto first_it = dirty_it;
while (first_it != dirty_db.begin())
{
auto prev_it = first_it;
prev_it--;
if (prev_it->first.oid != op->oid || IS_SYNCED(prev_it->second.state))
break;
if (IS_IN_FLIGHT(prev_it->second.state))
return false;
if (!is_still_unsynced(IS_BIG_WRITE(prev_it->second.state) ? unsynced_big_writes : unsynced_small_writes, prev_it->first))
{
// A sync in flight already owns it, so we can't erase it here: that would leave
// the sync with a dangling dirty_db entry in ack_sync() and would decrement
// unstable_unsynced a second time.
// A small write is journaled as soon as it's dequeued, so it is already in front
// of our deletion. A big write is not: its journal entry is only written when the
// sync reaches its journaling phase, which may well be after our deletion is
// journaled. Journal replay would then take it for a new write over the deletion
// and resurrect the object, so wait for that sync to finish instead
if (IS_BIG_WRITE(prev_it->second.state))
return false;
break;
}
first_it = prev_it;
}
if (first_it == dirty_it)
return true;
// Their durability becomes ours: they're acknowledged, but from now on the only thing
// on the disk which accounts for them is our deletion. A sync started before we're
// journaled and acked would otherwise find nothing to do and report success
unsynced_forgotten++;
PRIV(op)->forgot_unsynced = true;
for (auto it = first_it; it != dirty_it; it++)
{
// Drop it from the unsynced lists so that a later sync doesn't journal it
bool is_big = IS_BIG_WRITE(it->second.state);
auto & unsynced = is_big ? unsynced_big_writes : unsynced_small_writes;
for (size_t i = 0; i < unsynced.size(); i++)
{
if (unsynced[i].oid == it->first.oid && unsynced[i].version == it->first.version)
{
unsynced.erase(unsynced.begin()+i, unsynced.begin()+i+1);
if (is_big && immediate_commit != IMMEDIATE_ALL)
unsynced_big_write_count--;
break;
}
}
if (!(it->second.state & BS_ST_INSTANT) && !IS_SYNCED(it->second.state))
{
unstable_unsynced--;
assert(unstable_unsynced >= 0);
}
}
auto & clean_db = clean_db_shard(op->oid);
auto clean_it = clean_db.find(op->oid);
uint64_t clean_loc = clean_it != clean_db.end() ? clean_it->second.location : UINT64_MAX;
erase_dirty(first_it, dirty_it, clean_loc);
wakeup_wait_journal();
return true;
}
bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
{
// Check or assign version number
bool found = false, deleted = false, unsynced = false, is_del = (op->opcode == BS_OP_DELETE);
bool wait_big = false, wait_del = false, in_open_sector = false;
bool wait_big = false, wait_del = false;
void *dyn = NULL;
uint8_t *dyn_ptr = NULL;
if (is_del)
{
op->len = 0;
}
else
size_t dyn_size = dsk.dirty_dyn_size(op->offset, op->len);
if (!is_del && alloc_dyn_data)
{
if (alloc_dyn_data)
{
// FIXME: Working with `dyn_data` has to be refactored somehow but I first have to decide how :)
// +sizeof(int) = refcount
size_t dyn_size = dsk.dirty_dyn_size(op->offset, op->len);
dyn = calloc_or_die(1, dyn_size+sizeof(int));
*((int*)dyn) = 1;
}
dyn_ptr = (alloc_dyn_data ? (uint8_t*)dyn+sizeof(int) : (uint8_t*)&dyn);
// FIXME: Working with `dyn_data` has to be refactored somehow but I first have to decide how :)
// +sizeof(int) = refcount
dyn = calloc_or_die(1, dyn_size+sizeof(int));
*((int*)dyn) = 1;
}
uint8_t *dyn_ptr = (alloc_dyn_data ? (uint8_t*)dyn+sizeof(int) : (uint8_t*)&dyn);
uint64_t version = 1;
if (dirty_db.size() > 0)
{
@@ -131,15 +35,6 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
dirty_it--; // segfaults when dirty_db is empty
if (dirty_it != dirty_db.end() && dirty_it->first.oid == op->oid)
{
if (op->opcode == BS_OP_WRITE_STABLE && !IS_INSTANT(dirty_it->second.state))
{
op->retval = -EINVAL;
if (!is_del && alloc_dyn_data)
{
free(dyn);
}
return false;
}
found = true;
version = dirty_it->first.version + 1;
deleted = IS_DELETE(dirty_it->second.state);
@@ -148,7 +43,6 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
wait_big = (dirty_it->second.state & BS_ST_TYPE_MASK) == BS_ST_BIG_WRITE
? !IS_SYNCED(dirty_it->second.state)
: ((dirty_it->second.state & BS_ST_WORKFLOW_MASK) == BS_ST_WAIT_BIG);
in_open_sector = dirty_it->second.journal_sector == journal.sector_info[journal.cur_sector].offset;
if (!is_del && !deleted)
{
void *dyn_from = alloc_dyn_data
@@ -199,26 +93,6 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
wait_del = true;
PRIV(op)->real_version = op->version;
op->version = version;
if (!unsynced && in_open_sector &&
journal.sector_info[journal.cur_sector].flush_count == 0)
{
// In exotic configurations without no_same_sector_overwrites, the deletion may reside
// in the current sector. In that case nothing advances dirty_start and the deletion
// becomes unflushable... Example is test_blockstore_chaos --seed 10901878
if (!journal.sector_info[journal.cur_sector].dirty)
{
// If it's unmodified we can just move to the next sector
// Probably unreachable currently due to "Forcibly move to the next sector" logic below
journal.in_sector_pos = journal.block_size;
mark_not_dirty(journal.cur_sector);
}
else
{
// If it's modified we may add a SYNC so it writes the sector out
assert(immediate_commit != IMMEDIATE_ALL);
unsynced = true;
}
}
if (unsynced)
{
// Issue an additional sync so the delete reaches the journal
@@ -228,20 +102,20 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
sync_op->version = op->version;
sync_op->callback = [this](blockstore_op_t *sync_op)
{
unshift_stable_flush((obj_ver_id){
flusher->unshift_flush((obj_ver_id){
.oid = sync_op->oid,
.version = sync_op->version-1,
});
}, true);
delete sync_op;
};
enqueue_own_op(sync_op);
enqueue_op(sync_op);
}
else
{
unshift_stable_flush((obj_ver_id){
flusher->unshift_flush((obj_ver_id){
.oid = op->oid,
.version = version-1,
});
}, true);
}
}
else
@@ -269,7 +143,7 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
{
delete sync_op;
};
enqueue_own_op(sync_op);
enqueue_op(sync_op);
}
else if (!imm)
unsynced_queued_ops++;
@@ -300,11 +174,6 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
if (op->bitmap)
memcpy(dyn_ptr, op->bitmap, dsk.clean_entry_bitmap_size);
}
if (wait_del)
{
// Entering WAIT_DEL -> request_trim, exiting -> release_trim
flusher->request_trim();
}
// Calculate checksums
// FIXME: Allow to receive checksums from outside?
if (!is_del && dsk.data_csum_type && op->len > 0)
@@ -345,27 +214,8 @@ bool blockstore_impl_t::enqueue_write(blockstore_op_t *op)
return true;
}
// Ask the flusher to flush <ov> right now, but only if it's already stable.
// A sync we submit ourselves may complete without syncing anything, because a sync submitted
// before it has already taken those writes over and is still in flight. In that case the entry
// becomes stable - and is queued for flushing by mark_stable() - when that sync completes,
// and forcing a flush of a still unstable entry would abort the flusher
void blockstore_impl_t::unshift_stable_flush(obj_ver_id ov)
{
auto dirty_it = dirty_db.find(ov);
if (dirty_it != dirty_db.end() && IS_STABLE(dirty_it->second.state))
{
flusher->unshift_flush(ov, true);
}
}
void blockstore_impl_t::cancel_all_writes(blockstore_op_t *op, blockstore_dirty_db_t::iterator dirty_it, int retval)
{
if (PRIV(op)->forgot_unsynced)
{
PRIV(op)->forgot_unsynced = false;
unsynced_forgotten--;
}
while (dirty_it != dirty_db.end() && dirty_it->first.oid == op->oid)
{
free_dirty_dyn_data(dirty_it->second);
@@ -458,11 +308,6 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
sizeof(journal_entry_big_write) + dsk.clean_dyn_size,
(unstable_writes.size()+unstable_unsynced+((dirty_it->second.state & BS_ST_INSTANT) ? 0 : 1))*journal.block_size))
{
if (space_check.give_up)
{
cancel_all_writes(op, dirty_it, -EAGAIN);
return 2;
}
return 0;
}
// Big (redirect) write
@@ -555,11 +400,6 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
sizeof(journal_entry_small_write) + dyn_size,
op->len + (unstable_writes.size()+unstable_unsynced+((dirty_it->second.state & BS_ST_INSTANT) ? 0 : 1))*journal.block_size))
{
if (space_check.give_up)
{
cancel_all_writes(op, dirty_it, -EAGAIN);
return 2;
}
return 0;
}
// There is sufficient space. Check SQE(s)
@@ -572,8 +412,7 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
write_iodepth++;
// Got SQEs. Prepare previous journal sector write if required
if (immediate_commit == IMMEDIATE_NONE &&
!journal.entry_fits(sizeof(journal_entry_small_write) + dyn_size) &&
journal.sector_info[journal.cur_sector].dirty)
!journal.entry_fits(sizeof(journal_entry_small_write) + dyn_size))
{
prepare_journal_sector_write(journal.cur_sector, op);
}
@@ -633,13 +472,7 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
? (uint8_t*)dirty_it->second.dyn_data+sizeof(int) : (uint8_t*)&dirty_it->second.dyn_data), dyn_size);
je->crc32 = je_crc32((journal_entry*)je);
journal.crc32_last = je->crc32;
// With immediate_commit=none we normally leave the sector to be written out by a later
// sync. But if it is already queued for writing in this same batch, our entry goes to
// the disk with that write anyway - so join it instead of leaving the sector dirty,
// otherwise the next operation to write the sector out would queue a second write of
// it while the first one is still in flight. It costs no extra I/O: the write is
// already prepared, so prepare_journal_sector_write() only subscribes us to it
if (immediate_commit != IMMEDIATE_NONE || journal.sector_info[journal.cur_sector].submit_id)
if (immediate_commit != IMMEDIATE_NONE)
{
prepare_journal_sector_write(journal.cur_sector, op);
}
@@ -662,7 +495,6 @@ int blockstore_impl_t::dequeue_write(blockstore_op_t *op)
.op = op,
});
data2->callback = [this, flush_id = journal.submit_id](ring_data_t *data) { handle_journal_write(data, flush_id); };
assert(journal.next_free > 0);
assert(journal.next_free+op->len <= dsk.journal_len);
io_uring_prep_writev(sqe2, dsk.journal_fd, &data2->iov, 1, journal.offset + journal.next_free);
PRIV(op)->pending_ops++;
@@ -723,25 +555,6 @@ resume_2:
if (!space_check.check_available(op, 1, sizeof(journal_entry_big_write) + dyn_size,
(unstable_writes.size()+unstable_unsynced+((dirty_it->second.state & BS_ST_INSTANT) ? 0 : 1))*journal.block_size))
{
if (space_check.give_up)
{
auto uo_it = used_clean_objects.find(dirty_it->second.location);
if (uo_it != used_clean_objects.end())
uo_it->second.was_freed = true;
else
data_alloc->set(dirty_it->second.location / dsk.data_block_size, false);
if (!(dirty_it->second.state & BS_ST_INSTANT))
{
unstable_unsynced--;
assert(unstable_unsynced >= 0);
wakeup_wait_journal();
}
if (immediate_commit != IMMEDIATE_ALL)
unsynced_big_write_count--;
write_iodepth--;
cancel_all_writes(op, dirty_it, -EAGAIN);
return 2;
}
return 0;
}
BS_SUBMIT_CHECK_SQES(1);
@@ -805,7 +618,6 @@ resume_4:
{
unstable_unsynced--;
assert(unstable_unsynced >= 0);
wakeup_wait_journal();
}
}
dirty_it->second.state = (dirty_it->second.state & ~BS_ST_WORKFLOW_MASK)
@@ -834,12 +646,6 @@ resume_4:
});
}
}
if (PRIV(op)->forgot_unsynced)
{
// We're in the unsynced lists ourselves now, so a sync may proceed again
PRIV(op)->forgot_unsynced = false;
unsynced_forgotten--;
}
if (imm && (dirty_it->second.state & BS_ST_TYPE_MASK) == BS_ST_BIG_WRITE)
{
// Unblock small writes
@@ -914,20 +720,6 @@ void blockstore_impl_t::handle_write_event(ring_data_t *data, blockstore_op_t *o
}
}
void blockstore_impl_t::mark_not_dirty(int journal_sector)
{
uint64_t new_ds = journal.sector_info[journal_sector].offset + journal.block_size;
if (new_ds >= journal.len)
{
new_ds = journal.block_size;
}
if ((journal.dirty_start + (journal.dirty_start >= journal.used_start ? 0 : journal.len)) <
(new_ds + (new_ds >= journal.used_start ? 0 : journal.len)))
{
journal.dirty_start = new_ds;
}
}
void blockstore_impl_t::release_journal_sectors(blockstore_op_t *op)
{
// Release flushed journal sectors
@@ -942,11 +734,19 @@ void blockstore_impl_t::release_journal_sectors(blockstore_op_t *op)
if (s == (1+journal.cur_sector))
{
// Forcibly move to the next sector and move dirty position
// FIXME: This is the target behaviour, ok, but it seems to duplicate no_same_sector_overwrites...
journal.in_sector_pos = journal.block_size;
}
// We know for sure that we won't write into this sector anymore
mark_not_dirty(s-1);
uint64_t new_ds = journal.sector_info[s-1].offset + journal.block_size;
if (new_ds >= journal.len)
{
new_ds = journal.block_size;
}
if ((journal.dirty_start + (journal.dirty_start >= journal.used_start ? 0 : journal.len)) <
(new_ds + (new_ds >= journal.used_start ? 0 : journal.len)))
{
journal.dirty_start = new_ds;
}
}
if (s == PRIV(op)->max_flushed_journal_sector)
break;
@@ -967,25 +767,16 @@ int blockstore_impl_t::dequeue_del(blockstore_op_t *op)
.version = op->version,
});
assert(dirty_it != dirty_db.end());
if (!forget_unstable_before_delete(op, dirty_it))
{
// A preceding write is still running, retry when it finishes
return 0;
}
blockstore_journal_check_t space_check(this);
if (!space_check.check_available(op, 1, sizeof(journal_entry_del), (unstable_writes.size()+unstable_unsynced)*journal.block_size))
{
if (space_check.give_up)
{
cancel_all_writes(op, dirty_it, -EAGAIN);
return 2;
}
return 0;
}
// Write current journal sector only if it's dirty and full, or in the immediate_commit mode.
// Write current journal sector only if it's dirty and full, or in the immediate_commit mode
BS_SUBMIT_CHECK_SQES(
(immediate_commit != IMMEDIATE_NONE ||
!journal.entry_fits(sizeof(journal_entry_del)) ? 1 : 0)
(dsk.journal_block_size - journal.in_sector_pos) < sizeof(journal_entry_del) &&
journal.sector_info[journal.cur_sector].dirty) ? 1 : 0
);
if (write_iodepth >= max_write_iodepth)
{
@@ -994,7 +785,7 @@ int blockstore_impl_t::dequeue_del(blockstore_op_t *op)
write_iodepth++;
// Prepare journal sector write
if (immediate_commit == IMMEDIATE_NONE &&
!journal.entry_fits(sizeof(journal_entry_del)) &&
(dsk.journal_block_size - journal.in_sector_pos) < sizeof(journal_entry_del) &&
journal.sector_info[journal.cur_sector].dirty)
{
prepare_journal_sector_write(journal.cur_sector, op);
@@ -1017,8 +808,7 @@ int blockstore_impl_t::dequeue_del(blockstore_op_t *op)
je->crc32 = je_crc32((journal_entry*)je);
journal.crc32_last = je->crc32;
dirty_it->second.state = BS_ST_DELETE | BS_ST_SUBMITTED;
// Join a sector write which is already queued in this same batch, see dequeue_write()
if (immediate_commit != IMMEDIATE_NONE || journal.sector_info[journal.cur_sector].submit_id)
if (immediate_commit != IMMEDIATE_NONE)
{
prepare_journal_sector_write(journal.cur_sector, op);
}

View File

@@ -7,13 +7,21 @@ add_library(vitastor_common STATIC
etcd_state_client.cpp
msgr_stop.cpp
msgr_op.cpp
../../json11/json11.cpp
osd_ops.cpp
pg_states.cpp
msgr_encrypt.cpp
msgr_handshake.cpp
../util/allocator.cpp
../util/addr_util.cpp
../util/timerfd_manager.cpp
../util/str_util.cpp
../util/json_util.cpp
../util/xxh_x86dispatch.c
../util/openssl_util.cpp
)
target_compile_options(vitastor_common PUBLIC -fPIC)
target_link_libraries(vitastor_common ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES} util)
target_link_libraries(vitastor_common ${OPENSSL_LIBRARIES} ${ISAL_CRYPTO_LIBRARIES})
# libvitastor_net.a
set(MSGR_RDMA "")
@@ -25,12 +33,14 @@ if (RDMACM_LIBRARIES)
set(MSGR_RDMACM "msgr_rdmacm.cpp")
endif (RDMACM_LIBRARIES)
add_library(vitastor_net STATIC
../util/epoll_manager.cpp
etcd_state_client_http.cpp
messenger.cpp
msgr_iothread.cpp
msgr_send.cpp
msgr_receive.cpp
msgr_encrypt.cpp
../util/ringloop.cpp
http_client.cpp
${MSGR_RDMA}
${MSGR_RDMACM}
@@ -80,9 +90,8 @@ if (${WITH_FIO})
# libfio_vitastor_sec.so
add_library(fio_vitastor_sec SHARED
fio_sec_osd.cpp
)
target_link_libraries(fio_vitastor_sec
util
../util/rw_blocking.cpp
../util/addr_util.cpp
)
endif (${WITH_FIO})

View File

@@ -2,7 +2,6 @@
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#include <stdexcept>
#undef NDEBUG
#include <assert.h>
#include "pg_states.h"
#include "cluster_client_impl.h"
@@ -39,7 +38,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
{
// peer_osd just dropped connection
// determine WHICH dirty_buffers are now obsolete and repeat them
if (wb->repeat_ops_for(this, peer_osd) > 0)
if (wb->repeat_ops_for(this, peer_osd, 0, 0) > 0)
{
continue_ops();
}
@@ -48,7 +47,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
msgr.exec_op = [this](osd_op_t *op)
{
// Garbage in
fprintf(stderr, "Can't handle incoming operation from client %ju\n", op->client_id);
fprintf(stderr, "Can't handle incoming operation from client %lu\n", op->client_id);
msgr.stop_client(op->client_id);
delete op;
};
@@ -56,7 +55,7 @@ cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd
st_cli = std::move(st_cli_ptr);
st_cli->on_load_config_hook = [this](json11::Json::object & cfg) { on_load_config_hook(cfg); };
st_cli->on_change_osd_state_hook = [this](osd_num_t peer_osd) { on_change_osd_state_hook(peer_osd); };
st_cli->on_change_osd_state_hook = [this](uint64_t peer_osd) { on_change_osd_state_hook(peer_osd); };
st_cli->on_change_pool_config_hook = [this]() { on_change_pool_config_hook(); };
st_cli->on_change_pg_config_hook = [this]() { on_change_pool_config_hook(); };
st_cli->on_change_pg_state_hook = [this](pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary) { on_change_pg_state_hook(pool_id, pg_num, prev_primary); };
@@ -591,7 +590,7 @@ void cluster_client_t::on_change_pool_config_hook()
{
if (log_level > 2 && pg_counts[pool_item.first])
{
fprintf(stderr, "Pool %u (%s) PG count changed from %ju to %ju\n", pool_item.first, pool_item.second.name.c_str(),
fprintf(stderr, "Pool %u (%s) PG count changed from %lu to %lu\n", pool_item.first, pool_item.second.name.c_str(),
pg_counts[pool_item.first], pool_item.second.real_pg_count);
}
// At this point, all pool operations should have been suspended
@@ -610,11 +609,18 @@ void cluster_client_t::on_change_pool_config_hook()
}
inode_cache.clear();
inode_cache_children.clear();
vault_keys.clear();
continue_ops();
}
void cluster_client_t::on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary)
{
auto & pg_cfg = st_cli->pool_config[pool_id].pg_config[pg_num];
if (pg_cfg.cur_primary != prev_primary)
{
// Repeat this PG operations because an OSD which stopped being primary may not fsync operations
wb->repeat_ops_for(this, 0, pool_id, pg_num);
}
// Always continue to resume operations hung because of lack of the primary OSD
continue_ops();
continue_lists();
@@ -633,7 +639,7 @@ bool cluster_client_t::get_immediate_commit(uint64_t inode)
return pool_it->second.immediate_commit == IMMEDIATE_ALL;
}
void cluster_client_t::on_change_osd_state_hook(osd_num_t peer_osd)
void cluster_client_t::on_change_osd_state_hook(uint64_t peer_osd)
{
osd_tree_metrics.erase(peer_osd);
if (msgr.wanted_peers.find(peer_osd) != msgr.wanted_peers.end())
@@ -745,7 +751,7 @@ void cluster_client_t::execute(cluster_op_t *op)
offline_ops.push_back(op);
return;
}
op->flags = op->flags & (OSD_OP_IGNORE_READONLY | OSD_OP_WAIT_UP_TIMEOUT | OSD_OP_IGNORE_WRITEBACK); // allowed client flags
op->flags = op->flags & (OSD_OP_IGNORE_READONLY | OSD_OP_WAIT_UP_TIMEOUT); // allowed client flags
execute_internal(op);
}
@@ -853,8 +859,10 @@ void cluster_client_t::execute_internal(cluster_op_t *op)
}
}
void cluster_client_t::execute_cas(cluster_op_t *op, bool nosync)
void cluster_client_t::execute_cas(cluster_op_t *op)
{
slice_rw(op);
op->needs_reslice = false;
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && op->version && op->parts.size() > 1)
{
// Atomic writes to multiple stripes are unsupported
@@ -863,28 +871,6 @@ void cluster_client_t::execute_cas(cluster_op_t *op, bool nosync)
cb(op);
return;
}
if (!(op->flags & OP_IMMEDIATE_COMMIT) && !nosync &&
wb->has_dirty(op->inode, op->offset, op->len))
{
cluster_op_t *sync_op = new cluster_op_t;
sync_op->opcode = OSD_OP_SYNC;
sync_op->flags = 0;
sync_op->callback = [this, op](cluster_op_t* sync_op)
{
execute_cas(op, true);
delete sync_op;
};
execute_internal(sync_op);
return;
}
if (!slice_rw(op))
{
op->retval = -EINVAL;
auto cb = std::move(op->callback);
cb(op);
return;
}
op->needs_reslice = false;
int res = try_send(op, 0, [this, op](osd_op_t *part)
{
int expected = part->req.hdr.opcode == OSD_OP_DELETE ? 0 : part->req.rw.len;
@@ -893,7 +879,7 @@ void cluster_client_t::execute_cas(cluster_op_t *op, bool nosync)
op->retval = -EIO;
op->retval = op->retval == -EPIPE ? -EINTR : op->retval;
auto peer_it = msgr.osd_peers.find(op->parts[0].osd_num);
if (op->retval != expected || (op->flags & OP_IMMEDIATE_COMMIT))
if (op->retval != 0 || (op->flags & OP_IMMEDIATE_COMMIT))
{
auto cb = std::move(op->callback);
cb(op);
@@ -1032,7 +1018,7 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
{
for (auto & parent: icache->chain)
{
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_dirty(parent, op->offset, op->len))
if (INODE_POOL(parent) == INODE_POOL(op->inode) && wb->has_inode(parent))
{
// Deoptimise reads - we have dirty data for one of the parent layer(s).
op->deoptimise_snapshot = true;
@@ -1086,12 +1072,7 @@ int cluster_client_t::continue_rw(cluster_op_t *op)
goto resume_2;
resume_0:
// Slice the operation into parts
if (!slice_rw(op))
{
op->retval = -EINVAL;
erase_op(op);
return 1;
}
slice_rw(op);
op->needs_reslice = false;
resume_1:
// Send unsent parts, if they're not subject to change
@@ -1225,16 +1206,8 @@ resume_2:
op->retval = op->len;
if (op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
{
auto pool_it = st_cli->pool_config.find(INODE_POOL(op->inode));
if (pool_it == st_cli->pool_config.end())
{
op->retval = -EINVAL;
}
else
{
auto & pool_cfg = pool_it->second;
op->retval = op->len / pool_cfg.bitmap_granularity;
}
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->inode));
op->retval = op->len / pool_cfg.bitmap_granularity;
}
if (op->flush_id)
{
@@ -1244,11 +1217,9 @@ resume_2:
return 1;
}
else if (op->retval != 0 && op->opcode != OSD_OP_SYNC && !(op->flags & OP_FLUSH_BUFFER) &&
op->retval != -EPIPE && op->retval != -EAGAIN &&
(op->retval != -EIO || !client_eio_retry_interval) &&
(op->retval != -ENOSPC || !client_retry_enospc))
op->retval != -EPIPE && (op->retval != -EIO || !client_eio_retry_interval) && (op->retval != -ENOSPC || !client_retry_enospc))
{
// Fatal error (neither -EPIPE, -EAGAIN, -EIO nor -ENOSPC)
// Fatal error (neither -EPIPE, -EIO nor -ENOSPC)
erase_op(op);
return 1;
}
@@ -1311,17 +1282,11 @@ static void add_iov(int size, int skip, cluster_op_t *op, int &iov_idx, size_t &
}
}
bool cluster_client_t::slice_rw(cluster_op_t *op)
void cluster_client_t::slice_rw(cluster_op_t *op)
{
// Slice the request into individual object stripe requests
// Primary OSDs still operate individual stripes, but their size is multiplied by PG minsize in case of EC
auto pool_it = st_cli->pool_config.find(INODE_POOL(op->cur_inode));
if (pool_it == st_cli->pool_config.end() ||
!pool_it->second.real_pg_count)
{
return false;
}
auto & pool_cfg = pool_it->second;
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
uint64_t first_stripe = (op->offset / pg_block_size) * pg_block_size;
@@ -1416,7 +1381,48 @@ bool cluster_client_t::slice_rw(cluster_op_t *op)
op->parts[i].osd_num = 0;
i++;
}
return true;
}
bool cluster_client_t::affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num)
{
if (INODE_POOL(inode) != pool_id)
{
return false;
}
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(inode));
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
uint64_t first_stripe = (offset / pg_block_size) * pg_block_size;
uint64_t last_stripe = len > 0 ? ((offset + len - 1) / pg_block_size) * pg_block_size : first_stripe;
if ((last_stripe/pool_cfg.pg_stripe_size) - (first_stripe/pool_cfg.pg_stripe_size) + 1 >= pool_cfg.real_pg_count)
{
// All PGs are affected
return true;
}
pg_num_t first_pg_num = (first_stripe/pool_cfg.pg_stripe_size) % pool_cfg.real_pg_count + 1; // like map_to_pg()
pg_num_t last_pg_num = (last_stripe/pool_cfg.pg_stripe_size) % pool_cfg.real_pg_count + 1; // like map_to_pg()
return (first_pg_num <= last_pg_num
? (pg_num >= first_pg_num && pg_num <= last_pg_num)
: (pg_num >= first_pg_num || pg_num <= last_pg_num));
}
bool cluster_client_t::affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd)
{
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(inode));
uint32_t pg_data_size = (pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks);
uint64_t pg_block_size = pool_cfg.data_block_size * pg_data_size;
uint64_t first_stripe = (offset / pg_block_size) * pg_block_size;
uint64_t last_stripe = len > 0 ? ((offset + len - 1) / pg_block_size) * pg_block_size : first_stripe;
for (uint64_t stripe = first_stripe; stripe <= last_stripe; stripe += pg_block_size)
{
pg_num_t pg_num = (stripe/pool_cfg.pg_stripe_size) % pool_cfg.real_pg_count + 1; // like map_to_pg()
auto pg_it = pool_cfg.pg_config.find(pg_num);
if (pg_it != pool_cfg.pg_config.end() && pg_it->second.cur_primary == osd)
{
return true;
}
}
return false;
}
int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_op_t *op_part)> cb)
@@ -1426,12 +1432,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::function<void(osd_o
init_msgr();
}
auto part = &op->parts[i];
auto pool_it = st_cli->pool_config.find(INODE_POOL(op->cur_inode));
if (pool_it == st_cli->pool_config.end())
{
return TRY_SEND_OFFLINE;
}
auto & pool_cfg = pool_it->second;
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
auto pg_it = pool_cfg.pg_config.find(part->pg_num);
if (pg_it != pool_cfg.pg_config.end() &&
!pg_it->second.pause && pg_it->second.cur_primary &&
@@ -1554,10 +1555,8 @@ resume_1:
}
if (op->retval != 0)
{
for (int i = 0; i < op->parts.size(); i++)
dirty_osds.insert(op->parts[i].osd_num);
wb->fsync_error();
if (op->retval == -EPIPE || op->retval == -EAGAIN || op->retval == -EIO || op->retval == -ENOSPC)
if (op->retval == -EPIPE || op->retval == -EIO || op->retval == -ENOSPC)
{
// Retry later
op->parts.clear();
@@ -1608,6 +1607,9 @@ static inline void mem_or(void *res, const void *r2, unsigned int len)
}
}
// Error priority: others > EPERM > EIO > ENOSPC > ETIMEDOUT > EPIPE
#define ERR_PRIO(e) (((e) == -EPERM ? 5 : ((e) == -EIO ? 4 : ((e) == -ENOSPC ? 3 : ((e) == -ETIMEDOUT ? 2 : ((e) == -EPIPE ? 1 : (!(e) ? 0 : 10)))))))
void cluster_client_t::handle_op_part(cluster_op_part_t *part)
{
cluster_op_t *op = part->parent;
@@ -1619,7 +1621,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
if (ERR_PRIO(part->op.reply.hdr.retval) > ERR_PRIO(op->retval))
op->retval = part->op.reply.hdr.retval;
uint64_t stop_client_id = 0;
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM && op->retval != -EAGAIN)
if (op->retval != -EINTR && op->retval != -EIO && op->retval != -ENOSPC && op->retval != -EPERM)
{
stop_client_id = part->op.client_id;
if (op->retval != -EPIPE || log_level > 0)
@@ -1641,11 +1643,11 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
// So do all these things after modifying operation state, otherwise we may hit reenterability bugs
// FIXME postpone such things to set_immediate here to avoid bugs
// Set op->retry_after to retry operation after a short pause (not immediately)
if (!op->retry_after && (op->retval == -EPIPE || op->retval == -EAGAIN ||
if (!op->retry_after && (op->retval == -EPIPE ||
op->retval == -EIO && client_eio_retry_interval ||
op->retval == -ENOSPC && client_retry_enospc))
{
op->retry_after = op->retval != -EPIPE && op->retval != -EAGAIN ? client_eio_retry_interval : client_retry_interval;
op->retry_after = op->retval != -EPIPE ? client_eio_retry_interval : client_retry_interval;
}
reset_retry_timer(op->retry_after);
if (stop_client_id)
@@ -1666,10 +1668,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
// OK
op->inflight_count--;
if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OP_IMMEDIATE_COMMIT))
{
wb->mark_flush_written_to(op->inode, part->op.req.rw.offset, part->op.req.rw.len, op->flush_id, part->osd_num);
dirty_osds.insert(part->osd_num);
}
part->flags |= PART_DONE;
op->done_count++;
if ((op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)
@@ -1695,12 +1694,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
void cluster_client_t::copy_part_bitmap(cluster_op_t *op, cluster_op_part_t *part)
{
// Copy (OR) bitmap
auto pool_it = st_cli->pool_config.find(INODE_POOL(op->cur_inode));
if (pool_it == st_cli->pool_config.end())
{
return;
}
auto & pool_cfg = pool_it->second;
auto & pool_cfg = st_cli->pool_config.at(INODE_POOL(op->cur_inode));
uint32_t pg_block_size = pool_cfg.data_block_size * (
pool_cfg.scheme == POOL_SCHEME_REPLICATED ? 1 : pool_cfg.pg_size-pool_cfg.parity_chunks
);

View File

@@ -17,7 +17,6 @@
#define OSD_OP_IGNORE_READONLY 0x08
#define OSD_OP_WAIT_UP_TIMEOUT 0x10
#define OSD_OP_IGNORE_WRITEBACK 0x20
struct cluster_op_t;
@@ -29,7 +28,7 @@ struct cluster_op_part_t
pg_num_t pg_num;
osd_num_t osd_num;
osd_op_buf_list_t iov;
uint32_t flags;
unsigned flags;
osd_op_t op;
};
@@ -40,11 +39,10 @@ struct __attribute__((visibility("default"))) cluster_op_t
uint64_t offset;
uint64_t len;
// for reads and writes within a single object (stripe),
// non-cached reads return current version and writes can use "CAS" semantics
// reads can return current version and writes can use "CAS" semantics
uint64_t version = 0;
// flags: OSD_OP_IGNORE_READONLY - ignore inode readonly flag
// OSD_OP_WAIT_UP_TIMEOUT - do not retry the operation infinitely if PG is inactive, only for for <wait_up_timeout>
// OSD_OP_IGNORE_WRITEBACK - do not read from write-back/write-repeat cache (it doesn't return version number)
uint64_t flags = 0;
// negative retval is an error number
// write and read return len on success
@@ -93,7 +91,6 @@ struct inode_cache_t
bool has_parent_loop = false;
inode_t other_pool_parent_id = 0;
int err_code = 0;
timespec err_timeout = {};
~inode_cache_t();
};
@@ -101,7 +98,6 @@ struct inode_cache_t
struct vault_load_key_t
{
int key_state = 0;
timespec load_time = {};
std::string key;
};
@@ -212,20 +208,23 @@ protected:
void vault_destroy();
void vault_parse_secret(const std::string & key_id, const std::string & err, json11::Json data);
bool affects_osd(uint64_t inode, uint64_t offset, uint64_t len, osd_num_t osd);
bool affects_pg(uint64_t inode, uint64_t offset, uint64_t len, pool_id_t pool_id, pg_num_t pg_num);
void on_load_config_hook(json11::Json::object & config);
void on_load_pgs_hook(bool success);
void on_change_pool_config_hook();
void on_change_pg_state_hook(pool_id_t pool_id, pg_num_t pg_num, osd_num_t prev_primary);
void on_change_osd_state_hook(osd_num_t peer_osd);
void on_change_osd_state_hook(uint64_t peer_osd);
void on_change_node_placement_hook();
void on_change_inode_hook(uint64_t inode, bool removed);
void execute_internal(cluster_op_t *op);
void execute_cas(cluster_op_t *op, bool nosync = false);
void execute_cas(cluster_op_t *op);
void unshift_op(cluster_op_t *op);
int continue_rw(cluster_op_t *op);
bool check_rw(cluster_op_t *op);
bool slice_rw(cluster_op_t *op);
void slice_rw(cluster_op_t *op);
void reset_retry_timer(int new_duration);
int try_send(cluster_op_t *op, int i, std::function<void(osd_op_t *op_part)> cb = nullptr);
int continue_sync(cluster_op_t *op);

View File

@@ -1,9 +1,8 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#undef NDEBUG
#include <assert.h>
#include <stdexcept>
#include <assert.h>
#include "cluster_client_impl.h"
#include "str_util.h"
@@ -74,18 +73,7 @@ std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
auto icache_it = inode_cache.find(ino);
if (icache_it != inode_cache.end())
{
if (icache_it->second &&
icache_it->second->err_code &&
icache_it->second->err_timeout.tv_sec)
{
timespec tv = {};
clock_gettime(CLOCK_REALTIME, &tv);
if (icache_it->second->err_timeout.tv_sec > tv.tv_sec)
return icache_it->second;
printf("%lu\n", icache_it->second ? icache_it->second->err_timeout.tv_sec : 0);
}
else
return icache_it->second;
return icache_it->second;
}
// Fill inode cache
auto ino_it = st_cli->inode_config.find(ino);
@@ -150,25 +138,16 @@ std::shared_ptr<inode_cache_t> cluster_client_t::inode_cache_get(inode_t ino)
inode_config_t *cfg = !i ? &inode_cfg : chain_cfg[i-1];
if (cfg && cfg->enc_key.substr(0, strlen(VAULT_KEY_PREFIX)) == VAULT_KEY_PREFIX)
{
auto & ik = vault_keys[cfg->enc_key];
if (ik.key_state == VAULT_KEY_ERROR)
{
timespec tv = {};
clock_gettime(CLOCK_REALTIME, &tv);
if (ik.load_time.tv_sec + vault_error_timeout_sec <= tv.tv_sec)
ik.key_state = VAULT_KEY_NOT_LOADED;
}
if (vault_url.empty() || ik.key_state == VAULT_KEY_ERROR)
auto & ik = vault_keys[inode_cfg.enc_key];
if (ik.key_state == VAULT_KEY_ERROR || vault_url.empty())
{
icache->err_code = EPERM;
icache->err_timeout = ik.load_time;
icache->err_timeout.tv_sec += vault_error_timeout_sec;
enc_key_count = 0;
}
else if (ik.key_state == VAULT_KEY_NOT_LOADED)
{
ik.key_state = VAULT_KEY_LOADING;
vault_key_load_queue.push_back(cfg->enc_key);
vault_key_load_queue.push_back(inode_cfg.enc_key);
vault_load_keys();
icache->err_code = EAGAIN;
enc_key_count = 0;
@@ -233,7 +212,6 @@ void cluster_client_t::vault_parse_secret(const std::string & key_id, const std:
{
vault_loading = false;
auto & k = vault_keys[key_id];
clock_gettime(CLOCK_REALTIME, &k.load_time);
if (err != "")
{
k.key_state = VAULT_KEY_ERROR;

View File

@@ -4,7 +4,6 @@
#pragma once
#include "cluster_client.h"
#include "small_vector.h"
#define PART_SENT 1
#define PART_DONE 2
@@ -30,11 +29,6 @@ struct cluster_buffer_t
int state;
uint64_t flush_id;
uint64_t *refcnt;
// Funny thing is that a buffer may be CACHE_REPEATING or even CACHE_DIRTY
// which means it's NOT written yet and have written_to_osd at the same time,
// because it may be left from the PREVIOUS write attempt which we MUST remember
// to correctly handle OSD failure retries.
small_vector_t<osd_num_t, 4> written_to_osd;
};
typedef std::map<object_id, cluster_buffer_t>::iterator dirty_buf_it_t;
@@ -53,17 +47,16 @@ public:
std::multimap<uint64_t, uint64_t*> flushed_buffers; // flush_id => refcnt
~writeback_cache_t();
bool has_dirty(uint64_t inode, uint64_t offset, uint64_t len);
bool has_inode(uint64_t inode);
dirty_buf_it_t find_dirty(uint64_t inode, uint64_t offset);
bool is_left_merged(dirty_buf_it_t dirty_it);
bool is_right_merged(dirty_buf_it_t dirty_it);
void copy_write(cluster_op_t *op, int state, uint64_t new_flush_id = 0);
int repeat_ops_for(cluster_client_t *cli, osd_num_t peer_osd);
int repeat_ops_for(cluster_client_t *cli, osd_num_t peer_osd, pool_id_t pool_id, pg_num_t pg_num);
void start_writebacks(cluster_client_t *cli, int count);
bool read_from_cache(cluster_op_t *op, uint32_t bitmap_granularity);
void flush_buffers(cluster_client_t *cli, dirty_buf_it_t from_it, dirty_buf_it_t to_it);
void mark_flush_written(uint64_t inode, uint64_t offset, uint64_t len, uint64_t flush_id);
void mark_flush_written_to(uint64_t inode, uint64_t offset, uint64_t len, uint64_t flush_id, osd_num_t osd_num);
void delete_flush(uint64_t inode, uint64_t offset, uint64_t len, uint64_t flush_id);
void fsync_start();
void fsync_error();

View File

@@ -63,15 +63,14 @@ void cluster_client_t::list_inode(inode_t inode, uint64_t min_offset, uint64_t m
{
init_msgr();
pool_id_t pool_id = INODE_POOL(inode);
auto pool_it = st_cli->pool_config.find(pool_id);
if (!pool_id || pool_it == st_cli->pool_config.end())
if (!pool_id || st_cli->pool_config.find(pool_id) == st_cli->pool_config.end())
{
if (log_level > 0)
fprintf(stderr, "Pool %u does not exist\n", pool_id);
pg_callback(-EINVAL, 0, 0, std::set<object_id>());
return;
}
auto pg_stripe_size = pool_it->second.pg_stripe_size;
auto pg_stripe_size = st_cli->pool_config.at(pool_id).pg_stripe_size;
if (min_offset)
min_offset = (min_offset/pg_stripe_size) * pg_stripe_size;
inode_list_t *lst = new inode_list_t();
@@ -249,12 +248,7 @@ void cluster_client_t::set_list_retry_timeout(int ms, timespec new_time)
int cluster_client_t::start_pg_listing(inode_list_pg_t *pg)
{
auto pool_it = st_cli->pool_config.find(pg->lst->pool_id);
if (pool_it == st_cli->pool_config.end())
{
return LIST_PG_WAIT_ACTIVE;
}
auto & pool_cfg = pool_it->second;
auto & pool_cfg = st_cli->pool_config.at(pg->lst->pool_id);
auto pg_it = pool_cfg.pg_config.find(pg->pg_num);
assert(pg->lst->real_pg_count == pool_cfg.real_pg_count);
if (pg_it == pool_cfg.pg_config.end() ||
@@ -370,7 +364,8 @@ void cluster_client_t::send_list(inode_list_osd_t *cur_list)
fprintf(stderr, "Failed to get PG %u/%u object list from OSD %ju (retval=%jd), skipping\n",
cur_list->pg->lst->pool_id, cur_list->pg->pg_num, cur_list->osd_num, op->reply.hdr.retval);
if (!cur_list->pg->errcode ||
ERR_PRIO(op->reply.hdr.retval) > ERR_PRIO(cur_list->pg->errcode))
cur_list->pg->errcode == -EPIPE ||
op->reply.hdr.retval != -EPIPE)
{
cur_list->pg->errcode = op->reply.hdr.retval;
}
@@ -433,7 +428,7 @@ void cluster_client_t::finish_list_pg(inode_list_pg_t *pg, bool retry_epipe)
auto lst = pg->lst;
if (pg->inflight_ops == 0)
{
if ((pg->errcode == -EPIPE || pg->errcode == -EAGAIN) && retry_epipe)
if (pg->errcode == -EPIPE && retry_epipe)
{
// Retry listing after <client_retry_interval> ms on EPIPE
pg->state = LIST_PG_WAIT_RETRY;

View File

@@ -1,8 +1,7 @@
// Copyright (c) Vitaliy Filippov, 2019+
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
#undef NDEBUG
#include <assert.h>
#include <cassert>
#include "cluster_client_impl.h"
@@ -18,10 +17,13 @@ writeback_cache_t::~writeback_cache_t()
dirty_buffers.clear();
}
bool writeback_cache_t::has_dirty(uint64_t inode, uint64_t offset, uint64_t len)
bool writeback_cache_t::has_inode(uint64_t inode)
{
auto dirty_it = find_dirty(inode, offset);
return dirty_it != dirty_buffers.end() && dirty_it->first.stripe < offset+len;
auto dirty_it = dirty_buffers.lower_bound((object_id){
.inode = inode,
.stripe = 0,
});
return dirty_it != dirty_buffers.end() && dirty_it->first.inode == inode;
}
dirty_buf_it_t writeback_cache_t::find_dirty(uint64_t inode, uint64_t offset)
@@ -93,7 +95,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
// approaches and fix everything accordingly when I decide to implement TRIM.
return;
}
small_vector_t<osd_num_t, 4> was_written_to;
auto dirty_it = find_dirty(op->inode, op->offset);
auto new_end = op->offset + op->len;
while (dirty_it != dirty_buffers.end() &&
@@ -101,11 +102,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
dirty_it->first.stripe < op->offset+op->len)
{
assert(dirty_it->first.stripe + dirty_it->second.len > op->offset);
for (auto osd_num: dirty_it->second.written_to_osd)
{
if (std::find(was_written_to.begin(), was_written_to.end(), osd_num) == was_written_to.end())
was_written_to.push_back(osd_num);
}
// Remove overlapping part(s) of buffers
auto old_end = dirty_it->first.stripe + dirty_it->second.len;
if (dirty_it->first.stripe < op->offset)
@@ -123,7 +119,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
.state = dirty_it->second.state,
.flush_id = dirty_it->second.flush_id,
.refcnt = dirty_it->second.refcnt,
.written_to_osd = dirty_it->second.written_to_osd,
});
if (dirty_it->second.buf)
{
@@ -182,7 +177,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
.state = dirty_it->second.state,
.flush_id = dirty_it->second.flush_id,
.refcnt = dirty_it->second.refcnt,
.written_to_osd = dirty_it->second.written_to_osd,
});
dirty_buffers.erase(dirty_it);
dirty_it = new_dirty_it;
@@ -233,7 +227,6 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
.state = state,
.flush_id = new_flush_id,
.refcnt = refcnt,
.written_to_osd = std::move(was_written_to),
});
if (state == CACHE_DIRTY)
{
@@ -272,7 +265,7 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu
}
}
int writeback_cache_t::repeat_ops_for(cluster_client_t *cli, osd_num_t peer_osd)
int writeback_cache_t::repeat_ops_for(cluster_client_t *cli, osd_num_t peer_osd, pool_id_t pool_id, pg_num_t pg_num)
{
int repeated = 0;
if (dirty_buffers.size())
@@ -282,10 +275,11 @@ int writeback_cache_t::repeat_ops_for(cluster_client_t *cli, osd_num_t peer_osd)
for (auto wr_it = dirty_buffers.begin(), flush_it = wr_it, last_it = wr_it; ; )
{
bool end = wr_it == dirty_buffers.end();
auto & wr_to = wr_it->second.written_to_osd;
bool flush_this = std::find(wr_to.begin(), wr_to.end(), peer_osd) != wr_to.end();
if (flush_this)
wr_to.clear();
bool flush_this = !end && wr_it->second.state != CACHE_REPEATING && wr_it->second.state != CACHE_DIRTY;
if (peer_osd)
flush_this = flush_this && cli->affects_osd(wr_it->first.inode, wr_it->first.stripe, wr_it->second.len, peer_osd);
if (pool_id && pg_num)
flush_this = flush_this && cli->affects_pg(wr_it->first.inode, wr_it->first.stripe, wr_it->second.len, pool_id, pg_num);
if (flush_it != wr_it && (end || !flush_this ||
wr_it->first.inode != flush_it->first.inode ||
wr_it->first.stripe != last_it->first.stripe+last_it->second.len))
@@ -317,28 +311,28 @@ void writeback_cache_t::flush_buffers(cluster_client_t *cli, dirty_buf_it_t from
op->offset = from_it->first.stripe;
op->len = prev_it->first.stripe + prev_it->second.len - from_it->first.stripe;
uint32_t calc_len = 0;
op->flush_id = ++last_flush_id;
uint64_t flush_id = ++last_flush_id;
for (auto it = from_it; it != to_it; it++)
{
it->second.state = CACHE_REPEATING;
it->second.flush_id = op->flush_id;
it->second.flush_id = flush_id;
if (it->second.buf)
{
(*it->second.refcnt)++;
flushed_buffers.emplace(op->flush_id, it->second.refcnt);
flushed_buffers.emplace(flush_id, it->second.refcnt);
op->iov.push_back(it->second.buf, it->second.len);
}
calc_len += it->second.len;
}
assert(calc_len == op->len);
writebacks_active++;
op->callback = [this](cluster_op_t* op)
op->callback = [this, flush_id](cluster_op_t* op)
{
// Buffer flushes are always retried, regardless of the error,
// so they should never result in an error here
assert(op->retval == op->len);
for (auto fl_it = flushed_buffers.lower_bound(op->flush_id);
fl_it != flushed_buffers.end() && fl_it->first == op->flush_id; )
for (auto fl_it = flushed_buffers.find(flush_id);
fl_it != flushed_buffers.end() && fl_it->first == flush_id; )
{
if (!--(*fl_it->second)) // refcnt
{
@@ -348,7 +342,11 @@ void writeback_cache_t::flush_buffers(cluster_client_t *cli, dirty_buf_it_t from
}
if (op->flags & OP_IMMEDIATE_COMMIT)
{
delete_flush(op->inode, op->offset, op->len, op->flush_id);
delete_flush(op->inode, op->offset, op->len, flush_id);
}
else
{
mark_flush_written(op->inode, op->offset, op->len, flush_id);
}
delete op;
writebacks_active--;
@@ -400,21 +398,6 @@ void writeback_cache_t::mark_flush_written(uint64_t inode, uint64_t offset, uint
}
}
void writeback_cache_t::mark_flush_written_to(uint64_t inode, uint64_t offset, uint64_t len, uint64_t flush_id, osd_num_t osd_num)
{
for (auto dirty_it = find_dirty(inode, offset);
dirty_it != dirty_buffers.end() && dirty_it->first.inode == inode &&
dirty_it->first.stripe < offset+len; dirty_it++)
{
if (dirty_it->second.flush_id == flush_id && dirty_it->second.state == CACHE_REPEATING)
{
auto & wr_to = dirty_it->second.written_to_osd;
if (std::find(wr_to.begin(), wr_to.end(), osd_num) == wr_to.end())
wr_to.push_back(osd_num);
}
}
}
void writeback_cache_t::start_writebacks(cluster_client_t *cli, int count)
{
if (!writeback_queue.size())
@@ -540,8 +523,7 @@ bool writeback_cache_t::read_from_cache(cluster_op_t *op, uint32_t bitmap_granul
{
bool dirty_copied = false;
if (dirty_buffers.size() && (op->opcode == OSD_OP_READ ||
op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP) &&
!(op->flags & OSD_OP_IGNORE_WRITEBACK))
op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP))
{
// We also have to return reads from CACHE_REPEATING buffers - they are not
// guaranteed to be present on target OSDs at the moment of repeating

View File

@@ -66,6 +66,10 @@ std::shared_ptr<user_info_t> etcd_state_client_t::get_user(const std::string & u
bool etcd_state_client_t::check_image_perm(const std::shared_ptr<user_info_t> & user_info, inode_t inode_num, bool write)
{
if (user_info->type == user_type_t::ADMIN)
{
return true;
}
auto cache_it = user_info->perm_cache.find(inode_num);
if (cache_it != user_info->perm_cache.end() &&
cache_it->second.mod_revision == user_perm_cache_revision)
@@ -410,18 +414,15 @@ void etcd_state_client_t::clean_nonexistent_pgs()
}
}
}
std::vector<osd_num_t> stale_peers;
for (auto & peer_item: peer_states)
{
if (seen_peers.find(peer_item.first) == seen_peers.end())
stale_peers.push_back(peer_item.first);
}
for (auto & stale_peer: stale_peers)
{
fprintf(stderr, "OSD %ju state disappeared after reload, forgetting it\n", stale_peer);
parse_state((etcd_kv_t){
.key = etcd_prefix+"/osd/state/"+std::to_string(stale_peer),
});
{
fprintf(stderr, "OSD %ju state disappeared after reload, forgetting it\n", peer_item.first);
parse_state((etcd_kv_t){
.key = etcd_prefix+"/osd/state/"+std::to_string(peer_item.first),
});
}
}
seen_peers.clear();
}
@@ -468,9 +469,10 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
// PG Size
pc.pg_size = pool_item.second["pg_size"].uint64_value();
if (pc.pg_size < 1 ||
pc.pg_size < 3 && (pc.scheme == POOL_SCHEME_XOR || pc.scheme == POOL_SCHEME_EC) ||
pool_item.second["pg_size"].uint64_value() < 3 &&
(pc.scheme == POOL_SCHEME_XOR || pc.scheme == POOL_SCHEME_EC) ||
// limit is 64 because osd_peering_pg.cpp uses a 64-bit mask for has_roles
pc.pg_size > 64)
pool_item.second["pg_size"].uint64_value() > 64)
{
fprintf(stderr, "Pool %u has invalid pg_size, skipping pool\n", pool_id);
continue;
@@ -518,18 +520,16 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
continue;
}
// Data Block Size
uint64_t data_block_size = pool_item.second["block_size"].uint64_value();
if (!data_block_size)
data_block_size = global_block_size;
if ((data_block_size & (data_block_size-1)) ||
data_block_size < MIN_DATA_BLOCK_SIZE || data_block_size > MAX_DATA_BLOCK_SIZE ||
pc.scheme != POOL_SCHEME_REPLICATED && data_block_size*pc.pg_size > UINT32_MAX)
pc.data_block_size = pool_item.second["block_size"].uint64_value();
if (!pc.data_block_size)
pc.data_block_size = global_block_size;
if ((pc.data_block_size & (pc.data_block_size-1)) ||
pc.data_block_size < MIN_DATA_BLOCK_SIZE || pc.data_block_size > MAX_DATA_BLOCK_SIZE)
{
fprintf(stderr, "Pool %u has invalid block_size (must be a power of two between %u and %u; must not exceed 2^32-1 when multiplied by pg_size), skipping pool\n",
fprintf(stderr, "Pool %u has invalid block_size (must be a power of two between %u and %u), skipping pool\n",
pool_id, MIN_DATA_BLOCK_SIZE, MAX_DATA_BLOCK_SIZE);
continue;
}
pc.data_block_size = data_block_size;
// Bitmap Granularity
pc.bitmap_granularity = pool_item.second["bitmap_granularity"].uint64_value();
if (!pc.bitmap_granularity)
@@ -902,6 +902,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
{
if (inf)
{
inf->type = user_type_t::CLIENT;
inf->groups.clear();
inf->perm_cache.clear();
}
@@ -914,6 +915,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv)
inf = std::make_shared<user_info_t>();
inf->name = name;
}
inf->type = value["type"] == "admin" ? user_type_t::ADMIN : user_type_t::CLIENT;
inf->groups.clear();
for (auto & group: value["groups"].array_items())
{
@@ -959,8 +961,6 @@ void etcd_state_client_t::insert_inode_config(const inode_config_t & cfg)
if (w->name == cfg.name)
{
w->cfg = cfg;
if (w->callback)
w->callback(w);
}
}
}

View File

@@ -101,7 +101,6 @@ struct inode_watch_t
{
std::string name;
inode_config_t cfg = {};
std::function<void(inode_watch_t*)> callback;
};
struct http_url_t
@@ -112,6 +111,12 @@ struct http_url_t
std::string path;
};
enum class user_type_t
{
CLIENT = 0,
ADMIN = 1,
};
struct user_perm_t
{
enum class perm_type_t: uint8_t;
@@ -125,6 +130,7 @@ struct user_perm_t
struct user_info_t
{
std::string name;
user_type_t type;
robin_hood::unordered_flat_set<std::string> groups;
robin_hood::unordered_flat_map<inode_t, user_perm_t> perm_cache;
};
@@ -159,7 +165,7 @@ public:
uint32_t global_bitmap_granularity = DEFAULT_BITMAP_GRANULARITY;
uint32_t global_immediate_commit = IMMEDIATE_NONE;
osd_num_t osd_num = 0;
uint64_t osd_num = 0;
std::string etcd_prefix;
std::string etcd_client_cert;
std::string etcd_client_key;

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