Files
diskless-workstation/serving/spdk/spdk-target.sh
T
2026-06-27 19:29:19 +09:30

88 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# spdk-target.sh — LONG-LIVED nvmf_tgt entrypoint.
#
# Starts the NVMe-oF target ONCE and keeps it up for the container's entire life.
# It is RELEASE-AGNOSTIC: rootfs versions are added/removed ONLINE via
# spdk-release.sh (JSON-RPC) without ever restarting this process, so a new build
# never drops running hosts' NVMe/TCP root connections (docs/TODO.md #6). This is
# the key difference from the legacy serving/spdk-setup.sh, which configured a
# single version at start and thus required a connection-dropping restart to
# change versions.
#
# On (re)start it reconciles: every release staged under $RELEASES_DIR is added,
# so a serving-host reboot restores all currently-served versions automatically.
set -euo pipefail
RELEASES_DIR="${RELEASES_DIR:-/releases}"
CPUMASK="${SPDK_CPUMASK:-0x1}"
TADDR="${TARGET_ADDR:-0.0.0.0}"
TPORT="${TARGET_PORT:-4420}"
HUGEPAGES="${HUGEPAGES:-1024}"
DISCOVERY_NQN="nqn.2014-08.org.nvmexpress.discovery"
TGT=$(command -v nvmf_tgt || echo /usr/libexec/spdk/spdk/build/bin/nvmf_tgt)
RPC=$(command -v rpc.py || echo /usr/libexec/spdk/scripts/rpc.py)
RELEASE_CTL=$(command -v spdk-release || echo /usr/local/bin/spdk-release)
# Ensure hugepages exist — DPDK needs them. vm.nr_hugepages isn't namespaced, so
# a privileged container writes the host value; re-applied after a host reboot
# (the container restarts). Best-effort: allocation can fail on a fragmented
# host, in which case set vm.nr_hugepages on the host via /etc/sysctl.d.
if [ -w /proc/sys/vm/nr_hugepages ]; then
cur=$(cat /proc/sys/vm/nr_hugepages 2>/dev/null || echo 0)
if [ "${cur:-0}" -lt "$HUGEPAGES" ]; then
echo "==> allocating $HUGEPAGES hugepages (was ${cur:-0})"
echo "$HUGEPAGES" > /proc/sys/vm/nr_hugepages 2>/dev/null || true
fi
fi
free=$(awk '/HugePages_Free/{print $2}' /proc/meminfo 2>/dev/null || echo 0)
[ "${free:-0}" -gt 0 ] || echo "WARNING: no free hugepages — SPDK will fail; allocate vm.nr_hugepages on the host." >&2
echo "==> starting LONG-LIVED nvmf_tgt (cpumask ${CPUMASK})"
"$TGT" -m "$CPUMASK" &
TGT_PID=$!
# A TERM/INT here stops the target and DROPS EVERY connection — only on container
# stop, never for a version change. Make the intent loud.
trap 'echo "==> stopping nvmf_tgt — this drops ALL host root connections" >&2; kill "$TGT_PID" 2>/dev/null || true' TERM INT
trap 'kill "$TGT_PID" 2>/dev/null || true' EXIT
# Wait for the RPC socket.
for _ in $(seq 1 30); do
"$RPC" framework_get_config >/dev/null 2>&1 && break
sleep 1
done
echo "==> creating TCP transport"
"$RPC" nvmf_create_transport -t TCP -u 16384 -m 8 -c 8192 \
|| echo "WARNING: nvmf_create_transport failed (already present?)" >&2
echo "==> registering discovery listener ${TADDR}:${TPORT}"
"$RPC" nvmf_subsystem_add_listener "$DISCOVERY_NQN" -t tcp -a "$TADDR" -s "$TPORT" 2>/dev/null || true
# Bootstrap convenience: if a single OCI artifact image is mounted at /artifacts
# (the simple one-version `docker compose up` flow) and that version isn't yet
# staged under $RELEASES_DIR, stage it so the long-lived target serves it too.
# Symlinks resolve inside the container (both are container paths); fall back to
# a copy if linking fails.
if [ -s /artifacts/verity.json ] && [ -n "${IMAGE_VERSION:-}" ]; then
seed="$RELEASES_DIR/$IMAGE_VERSION"
if [ ! -e "$seed/verity.json" ]; then
echo "==> seeding bootstrap release $IMAGE_VERSION from /artifacts"
mkdir -p "$seed"
for f in rootfs.erofs rootfs.hash verity.json; do
ln -sf "/artifacts/$f" "$seed/$f" 2>/dev/null \
|| cp "/artifacts/$f" "$seed/$f" 2>/dev/null \
|| echo "WARNING: could not seed /artifacts/$f" >&2
done
fi
fi
echo "==> reconciling releases under $RELEASES_DIR"
RELEASES_DIR="$RELEASES_DIR" TARGET_ADDR="$TADDR" TARGET_PORT="$TPORT" \
bash "$RELEASE_CTL" reconcile || echo "WARNING: reconcile reported errors" >&2
echo "==> nvmf_tgt up; releases served online. Add/remove versions with:"
echo " docker compose exec ${NVMEOF_SVC:-nvmeof} spdk-release {add|remove|list} <version>"
echo " or host-side: serving/spdk/spdk-ctl.sh {load|unload|list} <version>"
wait "$TGT_PID"