docs,test: --bind-volume / bind-volume / unbind-volume
- Document the new --bind-volume= option in systemd-vmspawn(1) and
the new bind-volume / unbind-volume verbs in machinectl(1).
- Add an integration test
(TEST-87-AUX-UTILS-VM.bind-volume.sh) covering boot-time attach
via --bind-volume, runtime attach via 'machinectl bind-volume',
runtime detach via 'machinectl unbind-volume', the StorageImmutable
rejection of attempts to detach boot-time volumes, and the
NoSuchStorage rejection of detach on unknown names.
- Strike "hook-up in systemd-vmspawn" from TODO.md; the nspawn and
service-manager hookups remain.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
@@ -138,7 +138,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
|||||||
|
|
||||||
- StorageProvider interface + storagectl
|
- StorageProvider interface + storagectl
|
||||||
- hook-up in systemd-nspawn
|
- hook-up in systemd-nspawn
|
||||||
- hook-up in systemd-vmspawn
|
|
||||||
- hook-up in service manager (BindVolume=)
|
- hook-up in service manager (BindVolume=)
|
||||||
- introduce a locking concept: right now all access to volumes is fully
|
- introduce a locking concept: right now all access to volumes is fully
|
||||||
shared. Let's add a basic locking concept: supporting backends can take an
|
shared. Let's add a basic locking concept: supporting backends can take an
|
||||||
|
|||||||
@@ -357,6 +357,38 @@
|
|||||||
<xi:include href="version-info.xml" xpointer="v219"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v219"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><command>bind-volume</command> <replaceable>NAME</replaceable> <replaceable>SPEC</replaceable></term>
|
||||||
|
|
||||||
|
<listitem><para>Acquire a storage volume from a
|
||||||
|
<citerefentry><refentrytitle>storagectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||||
|
provider and attach it to the running machine. <replaceable>SPEC</replaceable> is a string of the form
|
||||||
|
<literal><replaceable>PROVIDER</replaceable>:<replaceable>VOLUME</replaceable>[:<replaceable>CONFIG</replaceable>][:<replaceable>K=V</replaceable>,…]</literal>,
|
||||||
|
identical in grammar to the <option>--bind-volume=</option> argument of
|
||||||
|
<citerefentry><refentrytitle>systemd-vmspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||||
|
|
||||||
|
<para>The attached volume is identified by the name <literal><replaceable>PROVIDER</replaceable>:<replaceable>VOLUME</replaceable></literal>
|
||||||
|
and may be detached at runtime via <command>unbind-volume</command>. Currently only supported for
|
||||||
|
<command>systemd-vmspawn</command> machines that expose an
|
||||||
|
<constant>io.systemd.MachineInstance</constant> control socket.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><command>unbind-volume</command> <replaceable>NAME</replaceable> <replaceable>STORAGE-NAME</replaceable></term>
|
||||||
|
|
||||||
|
<listitem><para>Detach a storage volume from the running machine. <replaceable>STORAGE-NAME</replaceable>
|
||||||
|
is the <literal><replaceable>PROVIDER</replaceable>:<replaceable>VOLUME</replaceable></literal>
|
||||||
|
identifier that was specified at <command>bind-volume</command> time. Volumes that were attached at machine
|
||||||
|
startup (e.g. via <option>--bind-volume=</option> on
|
||||||
|
<citerefentry><refentrytitle>systemd-vmspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>)
|
||||||
|
cannot be detached and will fail with
|
||||||
|
<constant>io.systemd.MachineInstance.StorageImmutable</constant>.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><command>copy-to</command> <replaceable>NAME</replaceable> <replaceable>PATH</replaceable> [<replaceable>PATH</replaceable>] <option>--force</option></term>
|
<term><command>copy-to</command> <replaceable>NAME</replaceable> <replaceable>PATH</replaceable> [<replaceable>PATH</replaceable>] <option>--force</option></term>
|
||||||
|
|
||||||
|
|||||||
@@ -566,6 +566,39 @@
|
|||||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--bind-volume=<replaceable>PROVIDER</replaceable>:<replaceable>VOLUME</replaceable>[:<replaceable>CONFIG</replaceable>][:<replaceable>K=V</replaceable>,…]</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Acquire a storage volume from a
|
||||||
|
<citerefentry><refentrytitle>storagectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||||
|
provider and attach it to the virtual machine. <replaceable>PROVIDER</replaceable> is the
|
||||||
|
provider name (typically <literal>block</literal> or <literal>fs</literal>). <replaceable>VOLUME</replaceable>
|
||||||
|
is the volume name passed to the provider's <function>Acquire()</function> method.
|
||||||
|
<replaceable>CONFIG</replaceable> selects the guest device type and takes one of
|
||||||
|
<literal>virtio-blk</literal>, <literal>virtio-scsi</literal>, <literal>nvme</literal>, or
|
||||||
|
<literal>scsi-cd</literal>. If empty or omitted, defaults to <literal>virtio-blk</literal>.</para>
|
||||||
|
|
||||||
|
<para>The trailing comma-separated <replaceable>K=V</replaceable> list passes parameters to
|
||||||
|
<function>io.systemd.StorageProvider.Acquire()</function>: <varname>template=</varname>,
|
||||||
|
<varname>create=</varname> (one of <literal>any</literal>, <literal>new</literal>, <literal>open</literal>),
|
||||||
|
<varname>read-only=</varname> (or <varname>ro=</varname>; takes a boolean or <literal>auto</literal>),
|
||||||
|
<varname>size=</varname> / <varname>create-size=</varname> (size for created volumes),
|
||||||
|
<varname>request-as=</varname> (one of <literal>blk</literal>, <literal>reg</literal>,
|
||||||
|
<literal>dir</literal>; <literal>dir</literal> is rejected by vmspawn).</para>
|
||||||
|
|
||||||
|
<para>Each attached volume is identified by the name <literal><replaceable>PROVIDER</replaceable>:<replaceable>VOLUME</replaceable></literal>.
|
||||||
|
Volumes attached at startup via this option cannot be detached at runtime via
|
||||||
|
<command>machinectl unbind-volume</command>; only volumes added at runtime via
|
||||||
|
<command>machinectl bind-volume</command> are removable.</para>
|
||||||
|
|
||||||
|
<para>The provider is looked up under
|
||||||
|
<filename>/run/systemd/io.systemd.StorageProvider/</filename> for system mode (or
|
||||||
|
<filename>$XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/</filename> for user mode), matching
|
||||||
|
the runtime scope chosen via <option>--user</option> / <option>--system</option>.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--bind-user=</option></term>
|
<term><option>--bind-user=</option></term>
|
||||||
|
|
||||||
|
|||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
# Test --bind-volume / machinectl bind-volume / unbind-volume integration with the
|
||||||
|
# StorageProvider Varlink interface.
|
||||||
|
#
|
||||||
|
# Exercises:
|
||||||
|
# - --bind-volume parser + runtime_directory_generic + Acquire round-trip
|
||||||
|
# - boot-time attach via DriveInfo (non-removable)
|
||||||
|
# - runtime hotplug via io.systemd.MachineInstance.AddStorage (removable)
|
||||||
|
# - runtime hot-remove via io.systemd.MachineInstance.RemoveStorage
|
||||||
|
# - StorageImmutable rejection for boot-time attached volumes
|
||||||
|
set -eux
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
# shellcheck source=test/units/util.sh
|
||||||
|
. "$(dirname "$0")"/util.sh
|
||||||
|
|
||||||
|
if [[ -v ASAN_OPTIONS ]]; then
|
||||||
|
echo "vmspawn launches QEMU which doesn't work under ASan, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v systemd-vmspawn >/dev/null 2>&1; then
|
||||||
|
echo "systemd-vmspawn not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v storagectl >/dev/null 2>&1; then
|
||||||
|
echo "storagectl not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! find_qemu_binary; then
|
||||||
|
echo "QEMU not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v mke2fs >/dev/null 2>&1; then
|
||||||
|
echo "mke2fs not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Storage providers are socket-activated; skip if the fs provider socket isn't present.
|
||||||
|
if ! test -S /run/systemd/io.systemd.StorageProvider/fs; then
|
||||||
|
echo "StorageProvider fs socket not found, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find a kernel for direct boot
|
||||||
|
KERNEL=""
|
||||||
|
for k in /usr/lib/modules/"$(uname -r)"/vmlinuz /boot/vmlinuz-"$(uname -r)" /boot/vmlinuz; do
|
||||||
|
if [[ -f "$k" ]]; then
|
||||||
|
KERNEL="$k"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$KERNEL" ]]; then
|
||||||
|
echo "No kernel found for direct VM boot, skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORKDIR="$(mktemp -d /tmp/test-bind-volume.XXXXXXXXXX)"
|
||||||
|
|
||||||
|
at_exit() {
|
||||||
|
set +e
|
||||||
|
if [[ -n "${MACHINE:-}" ]]; then
|
||||||
|
if machinectl status "$MACHINE" &>/dev/null; then
|
||||||
|
machinectl terminate "$MACHINE" 2>/dev/null
|
||||||
|
timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[[ -n "${VMSPAWN_PID:-}" ]] && { kill "$VMSPAWN_PID" 2>/dev/null; wait "$VMSPAWN_PID" 2>/dev/null; }
|
||||||
|
rm -rf "$WORKDIR"
|
||||||
|
rm -f /var/lib/storage/test-bind-volume-*.volume
|
||||||
|
}
|
||||||
|
trap at_exit EXIT
|
||||||
|
|
||||||
|
# Build a minimal root for direct boot — guest just sleeps.
|
||||||
|
mkdir -p "$WORKDIR/rootfs/sbin"
|
||||||
|
cat >"$WORKDIR/rootfs/sbin/init" <<'INITEOF'
|
||||||
|
#!/bin/sh
|
||||||
|
exec sleep infinity
|
||||||
|
INITEOF
|
||||||
|
chmod +x "$WORKDIR/rootfs/sbin/init"
|
||||||
|
|
||||||
|
truncate -s 256M "$WORKDIR/root.raw"
|
||||||
|
mke2fs -t ext4 -q -d "$WORKDIR/rootfs" "$WORKDIR/root.raw"
|
||||||
|
|
||||||
|
BOOT_VOL="test-bind-volume-boot-$$"
|
||||||
|
RUNTIME_VOL="test-bind-volume-runtime-$$"
|
||||||
|
|
||||||
|
wait_for_machine() {
|
||||||
|
local machine="$1" pid="$2" log="$3"
|
||||||
|
timeout 30 bash -c "
|
||||||
|
while ! machinectl list --no-legend 2>/dev/null | grep >/dev/null '$machine'; do
|
||||||
|
if ! kill -0 $pid 2>/dev/null; then
|
||||||
|
echo 'vmspawn exited before machine registration'
|
||||||
|
cat '$log'
|
||||||
|
exit 77
|
||||||
|
fi
|
||||||
|
sleep .5
|
||||||
|
done
|
||||||
|
" || {
|
||||||
|
local rc=$?
|
||||||
|
if [[ $rc -eq 77 ]]; then exit 0; fi
|
||||||
|
exit "$rc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Boot the VM with one boot-time bind-volume ---
|
||||||
|
MACHINE="test-bind-volume-$$"
|
||||||
|
systemd-vmspawn \
|
||||||
|
--machine="$MACHINE" \
|
||||||
|
--ram=256M \
|
||||||
|
--image="$WORKDIR/root.raw" \
|
||||||
|
--bind-volume="fs:${BOOT_VOL}::create=new,size=64M,template=sparse-file" \
|
||||||
|
--linux="$KERNEL" \
|
||||||
|
--tpm=no \
|
||||||
|
--console=headless \
|
||||||
|
root=/dev/vda rw \
|
||||||
|
&>"$WORKDIR/vmspawn.log" &
|
||||||
|
VMSPAWN_PID=$!
|
||||||
|
|
||||||
|
wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log"
|
||||||
|
echo "Machine '$MACHINE' registered"
|
||||||
|
|
||||||
|
VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \
|
||||||
|
io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress')
|
||||||
|
assert_neq "$VARLINK_ADDR" "null"
|
||||||
|
|
||||||
|
varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' \
|
||||||
|
| jq -e '.running == true' >/dev/null
|
||||||
|
echo "VM running with boot-time bind-volume attached"
|
||||||
|
|
||||||
|
# --- Hot-add a second volume via machinectl bind-volume (must succeed) ---
|
||||||
|
machinectl bind-volume "$MACHINE" \
|
||||||
|
"fs:${RUNTIME_VOL}:virtio-scsi:create=new,size=32M,template=sparse-file"
|
||||||
|
echo "Hot-added runtime bind-volume succeeded"
|
||||||
|
|
||||||
|
# --- Hot-remove the runtime-added volume (must succeed) ---
|
||||||
|
machinectl unbind-volume "$MACHINE" "fs:${RUNTIME_VOL}"
|
||||||
|
echo "Hot-removed runtime bind-volume succeeded"
|
||||||
|
|
||||||
|
# --- Removing the boot-time volume must fail with StorageImmutable ---
|
||||||
|
if machinectl unbind-volume "$MACHINE" "fs:${BOOT_VOL}" 2>"$WORKDIR/unbind.err"; then
|
||||||
|
echo "ERROR: unbind-volume of boot-time volume should have failed"
|
||||||
|
cat "$WORKDIR/unbind.err"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep StorageImmutable "$WORKDIR/unbind.err" >/dev/null
|
||||||
|
echo "Boot-time bind-volume correctly rejected with StorageImmutable"
|
||||||
|
|
||||||
|
# --- Removing a non-existent name must fail with NoSuchStorage ---
|
||||||
|
if machinectl unbind-volume "$MACHINE" "fs:no-such-volume-$$" 2>"$WORKDIR/unbind-noexist.err"; then
|
||||||
|
echo "ERROR: unbind-volume of non-existent name should have failed"
|
||||||
|
cat "$WORKDIR/unbind-noexist.err"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep NoSuchStorage "$WORKDIR/unbind-noexist.err" >/dev/null
|
||||||
|
echo "Non-existent unbind-volume correctly rejected with NoSuchStorage"
|
||||||
|
|
||||||
|
machinectl terminate "$MACHINE"
|
||||||
|
timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done"
|
||||||
|
timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done"
|
||||||
|
echo "All bind-volume tests passed"
|
||||||
Reference in New Issue
Block a user