forked from vitalif/vitastor
Compare commits
74 Commits
cli-help-t
...
rdma-close
| Author | SHA1 | Date | |
|---|---|---|---|
| ded8832e65 | |||
| 7e018f98a8 | |||
| edc0ebb2a0 | |||
| 452a722884 | |||
| 29c3841eaa | |||
| 45204da444 | |||
| c18ddbf255 | |||
| 76410df0e8 | |||
| 6a0f3a38d0 | |||
| 265e99ccd0 | |||
| aa71a1968f | |||
| 5382f1c7bb | |||
| a5dbf74123 | |||
| 784fd7d233 | |||
| 13b7c0e73d | |||
| d747ec8c41 | |||
| cabe5d1543 | |||
| 38da624930 | |||
| e0ff8a014f | |||
| 8721f21874 | |||
| 9eb858fb5f | |||
| 9f084f48f6 | |||
| c998325448 | |||
| a243ff6459 | |||
| 47b3294666 | |||
| 3afed2473e | |||
| fdeacdcf30 | |||
| d7ddd7752c | |||
| 2b2c7d5e39 | |||
| e26478c2e7 | |||
| c7b9d30fd3 | |||
| 5803655840 | |||
| 61ce26ba29 | |||
| 32e651ec96 | |||
| dbed0e94cb | |||
| 8b39a268b9 | |||
| d40ba7c5ce | |||
| 985257f2d7 | |||
| a4f3383f21 | |||
| b2d828ec84 | |||
| b2a74de715 | |||
| 430761ad2e | |||
| 38a1f6937d | |||
| 8b46914999 | |||
| 1b81e60187 | |||
| 526c597cd0 | |||
| 7a97783ead | |||
| b4425d8e65 | |||
| 1afda35bd3 | |||
| 0953f5ebdd | |||
| 1b03615e50 | |||
| 8f208c53df | |||
| c0d2dabe66 | |||
| 6da4aaf176 | |||
| 5b6a6fce9a | |||
| 79fb2def57 | |||
| 7f5c24144b | |||
| 3cf876fafa | |||
| 771aa83282 | |||
| 03404ac95d | |||
| decf314238 | |||
| ab1849ff07 | |||
| 33b18c7229 | |||
| 4a2efb29ec | |||
| 00a432193a | |||
| d83eb599a0 | |||
| c1c9b1975d | |||
| ba0d9ad9f2 | |||
| 4feabc4ab6 | |||
| 1a14301c50 | |||
| 5dc52a7c06 | |||
| 4d45b27696 | |||
| 5995c4a00f | |||
| 906388adaa |
@@ -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; \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Submodule cpp-btree updated: bea37ef717...431d2e1d35
@@ -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/ && \
|
||||
|
||||
@@ -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/ && \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.2.2
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
2
debian/changelog
vendored
@@ -1,4 +1,4 @@
|
||||
vitastor (3.2.2-1) unstable; urgency=medium
|
||||
vitastor (3.0.15-1) unstable; urgency=medium
|
||||
|
||||
* Bugfixes
|
||||
|
||||
|
||||
6
debian/vitastor-buildenv.Dockerfile
vendored
6
debian/vitastor-buildenv.Dockerfile
vendored
@@ -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
|
||||
|
||||
2
debian/vitastor-client.install
vendored
2
debian/vitastor-client.install
vendored
@@ -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*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VITASTOR_VERSION ?= v3.2.2
|
||||
VITASTOR_VERSION ?= v3.0.15
|
||||
|
||||
all: build push
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
- Тип: целое число
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
- Тип: строка
|
||||
|
||||
@@ -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: 'Можно менять на лету: да',
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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: \
|
||||
|
||||
@@ -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: \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: не нужно
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 (пока) не поддерживает аутентификацию
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -231,8 +230,8 @@ Options:
|
||||
* `--writers-stopped` allows optimised removal in case of a single 'slim' read-write
|
||||
child and 'fat' removed parent: the child is merged into parent and parent is renamed
|
||||
to child in that case. In other cases parent layers are always merged into children.
|
||||
* `--exact` - remove multiple images with given names.
|
||||
* `--matching` - remove multiple images with names matching given glob patterns.
|
||||
* `--exact` - remove multiple images with names matching given glob patterns.
|
||||
* `--matching` - remove multiple images with given names
|
||||
* `--down-ok` - continue deletion/merging even if some data will be left on unavailable OSDs.
|
||||
|
||||
## flatten
|
||||
@@ -287,7 +286,7 @@ Describe unclean object locations in the cluster. Options:
|
||||
|
||||
## fix
|
||||
|
||||
`vitastor-cli fix [--objects <objects>] --bad-osds <osds> [--part <part>] [--check no]`
|
||||
`vitastor-cli fix [--objects <objects>] [--bad-osds <osds>] [--part <part>] [--check no]`
|
||||
|
||||
Fix inconsistent objects in the cluster by deleting some copies.
|
||||
|
||||
@@ -298,7 +297,7 @@ Fix inconsistent objects in the cluster by deleting some copies.
|
||||
Plain text format: 0x<inode>:0x<stripe> <any delimiter> 0x<inode>:0x<stripe> ...
|
||||
JSON format: [{"inode":"0x...","stripe":"0x..."},...]
|
||||
--bad-osds <osds>
|
||||
Required. Remove inconsistent copies/parts of objects from these OSDs, effectively
|
||||
Remove inconsistent copies/parts of objects from these OSDs, effectively
|
||||
marking them bad and allowing Vitastor to recover objects from other copies.
|
||||
--part <number>
|
||||
Only remove EC part <number> (from 0 to pg_size-1), required for extreme
|
||||
@@ -316,17 +315,14 @@ Allocate a new OSD number and reserve it by creating empty `/osd/stats/<n>` key.
|
||||
|
||||
## rm-osd
|
||||
|
||||
`vitastor-cli rm-osd [-f|--force] [--allow-data-loss] [--allow-up] [--dry-run] <osd_id> [osd_id...]`
|
||||
`vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] <osd_id> [osd_id...]`
|
||||
|
||||
Remove metadata and configuration for specified OSD(s) from etcd.
|
||||
|
||||
Refuses to remove OSDs that are up or whose removal makes PGs degraded, offline or incomplete.
|
||||
Refuses to remove OSDs with data without `--force` and `--allow-data-loss`.
|
||||
|
||||
* `-f|--force` - remove OSDs even if they are up or PGs become degraded or offline.
|
||||
* `--allow-data-loss` - remove OSDs even if PGs or objects become incomplete (DATA LOSS). Implies `--force`.
|
||||
* `--allow-up` - remove OSDs even if they are up.
|
||||
* `--dry-run` - only check if deletion is possible without data loss and
|
||||
redundancy degradation.
|
||||
With `--dry-run` only checks if deletion is possible without data loss and
|
||||
redundancy degradation.
|
||||
|
||||
## osd-tree
|
||||
|
||||
@@ -511,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.
|
||||
@@ -535,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.
|
||||
|
||||
@@ -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 байт. |
|
||||
@@ -239,8 +238,8 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
|
||||
у удаляемой цепочки есть только один дочерний образ, содержащий небольшой объём данных.
|
||||
В этом случае дочерний образ вливается в родительский и удаляется, а родительский
|
||||
переименовывается в дочерний.
|
||||
* `--exact` - удалить все образы с точно заданными именами.
|
||||
* `--matching` - удалить все образы с именами, подходящими под переданные glob-шаблоны.
|
||||
* `--exact` - удалить все образы с именами, подходящими под переданные glob-шаблоны.
|
||||
* `--matching` - удалить все образы с точно заданными именами.
|
||||
* `--down-ok` - продолжать удаление/слияние, даже если часть данных останется неудалённой на недоступных OSD.
|
||||
|
||||
## flatten
|
||||
@@ -302,7 +301,7 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
|
||||
|
||||
## fix
|
||||
|
||||
`vitastor-cli fix [--objects <объекты>] --bad-osds <osds> [--part <номер>] [--check no]`
|
||||
`vitastor-cli fix [--objects <объекты>] [--bad-osds <osds>] [--part <номер>] [--check no]`
|
||||
|
||||
Исправить неконсистентные (неоднозначные) объекты путём удаления части копий.
|
||||
|
||||
@@ -313,9 +312,9 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
|
||||
Простой формат: 0x<инод>:0x<смещение> <любой разделитель> 0x<инод>:0x<смещение> ...
|
||||
Формат JSON: [{"inode":"0x<инод>","stripe":"0x<смещение>"},...]
|
||||
--bad-osds <osds>
|
||||
Обязательная опция. Удалить неконсистентные копии/части объектов с данных
|
||||
OSD, таким образом признавая потерю этих копий и позволяя Vitastor-у
|
||||
восстановить объекты из других копий.
|
||||
Удалить неконсистентные копии/части объектов с данных OSD, таким образом
|
||||
признавая потерю этих копий и позволяя Vitastor-у восстановить объекты из
|
||||
других копий.
|
||||
--part <номер>
|
||||
Удалить только части EC с заданным номером (от 0 до pg_size-1). Нужно только
|
||||
в редких граничных случаях, когда один и тот же OSD содержит несколько частей
|
||||
@@ -334,19 +333,14 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
|
||||
|
||||
## rm-osd
|
||||
|
||||
`vitastor-cli rm-osd [-f|--force] [--allow-data-loss] [--allow-up] [--dry-run] <osd_id> [osd_id...]`
|
||||
`vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] <osd_id> [osd_id...]`
|
||||
|
||||
Удалить метаданные и конфигурацию для заданных OSD из etcd.
|
||||
|
||||
Отказывается удалять работающие OSD, а также OSD, после удаления которых PG станут
|
||||
деградированными, недоступными (offline) или неполными (incomplete).
|
||||
Отказывается удалять OSD с данными без опций `--force` и `--allow-data-loss`.
|
||||
|
||||
* `-f|--force` - удалить OSD, даже если они работают или PG станут деградированными или недоступными.
|
||||
* `--allow-data-loss` - удалить OSD, даже если PG или объекты станут неполными (ПОТЕРЯ ДАННЫХ).
|
||||
Включает действие `--force`.
|
||||
* `--allow-up` - удалить OSD, даже если они работают.
|
||||
* `--dry-run` - только проверить, возможно ли удаление без потери данных и деградации
|
||||
избыточности.
|
||||
С опцией `--dry-run` только проверяет, возможно ли удаление без потери данных и деградации
|
||||
избыточности.
|
||||
|
||||
## osd-tree
|
||||
|
||||
@@ -532,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` пулов
|
||||
для авторизации операций. Пустая строка очищает список групп.
|
||||
@@ -556,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
| <!-- --> | <!-- --> |
|
||||
|
||||
@@ -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.
|
||||
|
||||
## Общие опции
|
||||
|
||||
| <!-- --> | <!-- --> |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`).
|
||||
|
||||
## Производительность шифрования
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
15
mon/mon.js
15
mon/mon.js
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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); });
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
@@ -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, §) < 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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user