mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 19:01:42 +00:00
227 lines
7.9 KiB
Bash
227 lines
7.9 KiB
Bash
#!/bin/sh
|
|
# Self-contained POSIX test for rds-datadir: discovery of the hot-plugged data
|
|
# volume by virtio-blk serial, formatting a blank volume, mounting an existing
|
|
# one as found, and the refusals — no volume, two volumes, a partition table.
|
|
# No block devices and no root: a fake sysfs tree stands in for /sys/block, and
|
|
# blkid, mkfs.ext4, mount and chown are stubbed on PATH.
|
|
#
|
|
# Run: sh scripts/images/rds-postgres/rds-datadir_test.sh
|
|
set -eu
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
SCRIPT="${SCRIPT_DIR}/rds-datadir"
|
|
WORK=$(mktemp -d)
|
|
trap 'rm -rf "${WORK}"' EXIT
|
|
|
|
STUBBIN="${WORK}/bin"
|
|
mkdir -p "${STUBBIN}"
|
|
|
|
# blkid stub: prints a real blkid's line for any device listed in FS_TABLE as
|
|
# "<dev> <fstype>", and exits non-zero for one that holds no filesystem.
|
|
cat > "${STUBBIN}/blkid" <<'EOF'
|
|
#!/bin/sh
|
|
# BLKID_RC simulates a probe that could not run at all — 127 is a blkid missing
|
|
# from the image, 4 an internal error. Both print nothing, which is precisely
|
|
# what a genuinely blank disk prints, so only the exit status separates them.
|
|
[ "${BLKID_RC:-0}" = "0" ] || exit "${BLKID_RC}"
|
|
dev="$1"
|
|
fstype=$(awk -v d="${dev}" '$1 == d { print $2 }' "${FS_TABLE}" 2>/dev/null)
|
|
[ -n "${fstype}" ] || exit 2
|
|
echo "${dev}: UUID=\"1234-5678\" TYPE=\"${fstype}\""
|
|
EOF
|
|
|
|
# mkfs.ext4 stub: records the call and marks the device formatted, so a mount
|
|
# in the same run sees the filesystem the format created.
|
|
cat > "${STUBBIN}/mkfs.ext4" <<'EOF'
|
|
#!/bin/sh
|
|
echo "mkfs.ext4 $*" >> "${MKFS_CALLS}"
|
|
[ "${MKFS_FAIL:-0}" = "1" ] && exit 1
|
|
dev=""
|
|
for a in "$@"; do dev="${a}"; done
|
|
echo "${dev} ext4" >> "${FS_TABLE}"
|
|
exit 0
|
|
EOF
|
|
|
|
# mount stub: records the call only. The script re-reads its mounts file rather
|
|
# than the mount table, so nothing else has to be simulated.
|
|
cat > "${STUBBIN}/mount" <<'EOF'
|
|
#!/bin/sh
|
|
echo "mount $*" >> "${MOUNT_CALLS}"
|
|
exit "${MOUNT_RC:-0}"
|
|
EOF
|
|
|
|
# chown stub: file ownership is a no-op outside the guest.
|
|
cat > "${STUBBIN}/chown" <<'EOF'
|
|
#!/bin/sh
|
|
echo "chown $*" >> "${CHOWN_CALLS}"
|
|
exit 0
|
|
EOF
|
|
|
|
chmod +x "${STUBBIN}"/*
|
|
PATH="${STUBBIN}:${PATH}"
|
|
export PATH
|
|
|
|
FAILS=0
|
|
fail() { echo "FAIL: $*"; FAILS=$((FAILS + 1)); }
|
|
pass() { echo "ok: $*"; }
|
|
|
|
DATA_MOUNT="${WORK}/data"
|
|
SYS_BLOCK="${WORK}/sys/block"
|
|
DEV_DIR="${WORK}/dev"
|
|
MOUNTS="${WORK}/mounts"
|
|
|
|
FS_TABLE="${WORK}/fs.table"
|
|
MKFS_CALLS="${WORK}/mkfs.calls"
|
|
MOUNT_CALLS="${WORK}/mount.calls"
|
|
CHOWN_CALLS="${WORK}/chown.calls"
|
|
export FS_TABLE MKFS_CALLS MOUNT_CALLS CHOWN_CALLS
|
|
|
|
# add_disk <name> [serial]: materialise a block device in the fake sysfs. A disk
|
|
# with no serial stands in for the boot volume, which Spinifex attaches without
|
|
# one.
|
|
add_disk() {
|
|
mkdir -p "${SYS_BLOCK}/$1" "${DEV_DIR}"
|
|
: > "${DEV_DIR}/$1"
|
|
[ -n "${2:-}" ] && printf '%s\n' "$2" > "${SYS_BLOCK}/$1/serial"
|
|
return 0
|
|
}
|
|
|
|
# add_partition <disk> <n>: the sysfs child directory the kernel creates for a
|
|
# partition, which is how the script detects a partition table.
|
|
add_partition() { mkdir -p "${SYS_BLOCK}/$1/$1$2"; }
|
|
|
|
# reset_state clears the fake sysfs, the filesystem table and the stub call logs.
|
|
# The mounts file starts with the datadir unmounted, as it is at boot.
|
|
reset_state() {
|
|
rm -rf "${WORK}/sys" "${DEV_DIR}" "${DATA_MOUNT}"
|
|
mkdir -p "${SYS_BLOCK}" "${DEV_DIR}" "${DATA_MOUNT}"
|
|
printf '/dev/vda1 / ext4 rw,relatime 0 0\n' > "${MOUNTS}"
|
|
: > "${FS_TABLE}"
|
|
: > "${MKFS_CALLS}"
|
|
: > "${MOUNT_CALLS}"
|
|
: > "${CHOWN_CALLS}"
|
|
unset MKFS_FAIL MOUNT_RC BLKID_RC || true
|
|
}
|
|
|
|
# run: invoke rds-datadir against the fake sysfs and a wait short enough that
|
|
# the "never attached" case does not stall the suite.
|
|
run() {
|
|
env RDS_DATA_MOUNT="${DATA_MOUNT}" \
|
|
RDS_SYS_BLOCK="${SYS_BLOCK}" \
|
|
RDS_DEV_DIR="${DEV_DIR}" \
|
|
RDS_MOUNTS_FILE="${MOUNTS}" \
|
|
RDS_DATA_VOLUME_WAIT="${RDS_DATA_VOLUME_WAIT:-2}" \
|
|
MKFS_FAIL="${MKFS_FAIL:-0}" \
|
|
MOUNT_RC="${MOUNT_RC:-0}" \
|
|
BLKID_RC="${BLKID_RC:-0}" \
|
|
sh "${SCRIPT}" </dev/null
|
|
}
|
|
|
|
run_ok() { run > "${WORK}/out" 2>&1 || { fail "$1: non-zero exit: $(cat "${WORK}/out")"; return 1; }; }
|
|
run_fails() { run > "${WORK}/out" 2>&1 && fail "$1: expected a non-zero exit" || pass "$1: refused"; }
|
|
|
|
# --- Case 1: a blank data volume is formatted and mounted ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
if run_ok "blank"; then
|
|
grep -q "mkfs.ext4 .*${DEV_DIR}/vdb" "${MKFS_CALLS}" \
|
|
&& pass "blank: formatted the data volume" || fail "blank: no mkfs"
|
|
grep -q -- '-m 0' "${MKFS_CALLS}" \
|
|
&& pass "blank: no reserved blocks" || fail "blank: reserved blocks left on"
|
|
grep -q "mount ${DEV_DIR}/vdb ${DATA_MOUNT}" "${MOUNT_CALLS}" \
|
|
&& pass "blank: mounted at the datadir mount point" || fail "blank: not mounted"
|
|
grep -q "postgres:postgres ${DATA_MOUNT}" "${CHOWN_CALLS}" \
|
|
&& pass "blank: mount point handed to postgres" || fail "blank: ownership not set"
|
|
fi
|
|
|
|
# --- Case 2: the boot volume is never a candidate ---
|
|
reset_state
|
|
add_disk vda
|
|
if run_fails "boot-only"; then :; fi
|
|
grep -q . "${MKFS_CALLS}" \
|
|
&& fail "boot-only: formatted a disk with no volume serial" || pass "boot-only: nothing formatted"
|
|
|
|
# --- Case 3: an existing volume is mounted as found, never re-made ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
echo "${DEV_DIR}/vdb ext4" > "${FS_TABLE}"
|
|
if run_ok "existing"; then
|
|
grep -q . "${MKFS_CALLS}" \
|
|
&& fail "existing: reformatted a volume that already held a filesystem" \
|
|
|| pass "existing: not reformatted"
|
|
grep -q "mount ${DEV_DIR}/vdb ${DATA_MOUNT}" "${MOUNT_CALLS}" \
|
|
&& pass "existing: mounted as found" || fail "existing: not mounted"
|
|
fi
|
|
|
|
# --- Case 4: a partition table is somebody else's disk ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
add_partition vdb 1
|
|
run_fails "partitioned"
|
|
grep -q . "${MKFS_CALLS}" \
|
|
&& fail "partitioned: formatted over a partition table" || pass "partitioned: nothing formatted"
|
|
|
|
# --- Case 5: two data volumes is an error, not a guess ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
add_disk vdc vol0fedcba987654321
|
|
run_fails "ambiguous"
|
|
grep -q . "${MKFS_CALLS}" \
|
|
&& fail "ambiguous: formatted one of two candidates" || pass "ambiguous: nothing formatted"
|
|
|
|
# --- Case 6: an already-mounted datadir is a no-op, so a restart is safe ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
printf '%s %s ext4 rw,relatime 0 0\n' "${DEV_DIR}/vdb" "${DATA_MOUNT}" >> "${MOUNTS}"
|
|
if run_ok "already-mounted"; then
|
|
grep -q . "${MOUNT_CALLS}" \
|
|
&& fail "already-mounted: mounted over an existing mount" || pass "already-mounted: no-op"
|
|
fi
|
|
|
|
# --- Case 7: a failed format does not go on to mount a raw device ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
MKFS_FAIL=1 run_fails "mkfs-fail"
|
|
grep -q . "${MOUNT_CALLS}" \
|
|
&& fail "mkfs-fail: mounted after a failed format" || pass "mkfs-fail: not mounted"
|
|
|
|
# --- Case 8: a probe that could not run is never read as a blank disk ---
|
|
# The volume below holds a customer's filesystem. Deciding from blkid's empty
|
|
# output rather than its exit status reformats it.
|
|
for rc in 127 4; do
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
echo "${DEV_DIR}/vdb ext4" > "${FS_TABLE}"
|
|
BLKID_RC="${rc}" run_fails "blkid-exit-${rc}"
|
|
grep -q . "${MKFS_CALLS}" \
|
|
&& fail "blkid-exit-${rc}: formatted a volume it could not probe" \
|
|
|| pass "blkid-exit-${rc}: nothing formatted"
|
|
grep -q . "${MOUNT_CALLS}" \
|
|
&& fail "blkid-exit-${rc}: mounted a volume it could not probe" \
|
|
|| pass "blkid-exit-${rc}: not mounted"
|
|
done
|
|
|
|
# --- Case 9: sysfs listing a disk before its device node exists is not blank ---
|
|
reset_state
|
|
add_disk vda
|
|
add_disk vdb vol0123456789abcdef
|
|
rm -f "${DEV_DIR}/vdb"
|
|
run_fails "no-device-node"
|
|
grep -q . "${MKFS_CALLS}" \
|
|
&& fail "no-device-node: formatted a device that does not exist yet" \
|
|
|| pass "no-device-node: nothing formatted"
|
|
|
|
if [ "${FAILS}" -eq 0 ]; then
|
|
echo "PASS: all rds-datadir cases"
|
|
exit 0
|
|
fi
|
|
echo "FAILED: ${FAILS} case(s)"
|
|
exit 1
|