vmspawn: apply --grow-image= to the ephemeral copy
Combining --ephemeral with --grow-image= truncated the file passed to --image= to the requested size before the VM was started, so a mode whose entire purpose is to leave the original image untouched modified it on disk. Skip growing the original image when running ephemeral, and instead create the qcow2 overlay with the requested virtual size, so the guest sees the larger disk while the base image stays read-only. As only the overlay is sized in that case, the base image format no longer matters, so allow --grow-image= together with qcow2 images when running ephemeral. The round up to a multiple of 4096 moves to option parsing so both paths apply the same value. The vmspawn drive test grows an ephemeral VM and checks that the image passed to --image= keeps its size, and that the overlay QEMU runs off is created at the requested one.
This commit is contained in:
committed by
Yu Watanabe
parent
0b7c68eb3a
commit
2dd07068a3
@@ -169,6 +169,10 @@
|
|||||||
already as large or larger than requested. The specified size accepts the usual K, M, G suffixes
|
already as large or larger than requested. The specified size accepts the usual K, M, G suffixes
|
||||||
(to the base of 1024). Specified values are rounded up to multiples of 4096.</para>
|
(to the base of 1024). Specified values are rounded up to multiples of 4096.</para>
|
||||||
|
|
||||||
|
<para>If <option>--ephemeral</option> is specified, the original image file is left untouched and
|
||||||
|
the requested size is applied to the ephemeral copy instead. In that case qcow2 images are
|
||||||
|
supported too.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
#include "ether-addr-util.h"
|
#include "ether-addr-util.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
|
#include "format-util.h"
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
#include "json-util.h"
|
#include "json-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -493,6 +494,13 @@ static int qmp_setup_ephemeral_drive(VmspawnQmpBridge *bridge, QmpClient *qmp, D
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
/* Growing the overlay leaves the base image untouched, which is the whole point of ephemeral mode. */
|
||||||
|
if (drive->grow_to > virtual_size) {
|
||||||
|
log_debug("Growing ephemeral overlay of '%s' from %s to %s.",
|
||||||
|
drive->path, FORMAT_BYTES(virtual_size), FORMAT_BYTES(drive->grow_to));
|
||||||
|
virtual_size = drive->grow_to;
|
||||||
|
}
|
||||||
|
|
||||||
/* Step 1-2: Pass both fds to QEMU */
|
/* Step 1-2: Pass both fds to QEMU */
|
||||||
_cleanup_free_ char *base_path = NULL;
|
_cleanup_free_ char *base_path = NULL;
|
||||||
uint64_t base_fdset_id;
|
uint64_t base_fdset_id;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ typedef struct DriveInfo {
|
|||||||
char *pcie_port; /* pcie-root-port id for device_add bus (NULL on non-PCIe) */
|
char *pcie_port; /* pcie-root-port id for device_add bus (NULL on non-PCIe) */
|
||||||
int fd; /* pre-opened image fd (-EBADF if unused) */
|
int fd; /* pre-opened image fd (-EBADF if unused) */
|
||||||
int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (-EBADF if unused) */
|
int overlay_fd; /* pre-opened anonymous overlay fd for ephemeral (-EBADF if unused) */
|
||||||
|
uint64_t grow_to; /* ephemeral: minimum virtual size of the overlay (0 if unset) */
|
||||||
QmpDriveFlags flags;
|
QmpDriveFlags flags;
|
||||||
|
|
||||||
/* Per-add-op state (populated by the add flow; zeroed at CLI-parse time) */
|
/* Per-add-op state (populated by the add flow; zeroed at CLI-parse time) */
|
||||||
|
|||||||
+18
-12
@@ -423,6 +423,11 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
r = parse_size(opts.arg, 1024, &arg_grow_image);
|
r = parse_size(opts.arg, 1024, &arg_grow_image);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", opts.arg);
|
return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", opts.arg);
|
||||||
|
|
||||||
|
if (arg_grow_image > UINT64_MAX - 4095)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
|
||||||
|
"Specified --grow-image= size too large: %s", opts.arg);
|
||||||
|
arg_grow_image = ROUND_UP(arg_grow_image, 4096);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
OPTION_GROUP("Host Configuration"): {}
|
OPTION_GROUP("Host Configuration"): {}
|
||||||
@@ -2088,12 +2093,6 @@ static int grow_image(const char *path, uint64_t size) {
|
|||||||
if (size == 0)
|
if (size == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Round up to multiple of 4K */
|
|
||||||
size = DIV_ROUND_UP(size, 4096);
|
|
||||||
if (size > UINT64_MAX / 4096)
|
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified file size too large, refusing.");
|
|
||||||
size *= 4096;
|
|
||||||
|
|
||||||
_cleanup_close_ int fd = xopenat_full(AT_FDCWD, path, O_RDWR|O_CLOEXEC, XO_REGULAR, /* mode= */ 0);
|
_cleanup_close_ int fd = xopenat_full(AT_FDCWD, path, O_RDWR|O_CLOEXEC, XO_REGULAR, /* mode= */ 0);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return log_error_errno(fd, "Failed to open image file '%s': %m", path);
|
return log_error_errno(fd, "Failed to open image file '%s': %m", path);
|
||||||
@@ -2418,6 +2417,7 @@ static int prepare_primary_drive(const char *runtime_dir, DriveInfos *drives) {
|
|||||||
}
|
}
|
||||||
d->overlay_fd = TAKE_FD(overlay_fd);
|
d->overlay_fd = TAKE_FD(overlay_fd);
|
||||||
d->flags |= QMP_DRIVE_NO_FLUSH;
|
d->flags |= QMP_DRIVE_NO_FLUSH;
|
||||||
|
d->grow_to = arg_grow_image;
|
||||||
}
|
}
|
||||||
|
|
||||||
drives->drives[drives->n_drives++] = TAKE_PTR(d);
|
drives->drives[drives->n_drives++] = TAKE_PTR(d);
|
||||||
@@ -3249,14 +3249,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
|||||||
arg_image);
|
arg_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg_image_disk_type != DISK_TYPE_VIRTIO_SCSI_CDROM) {
|
if (arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) {
|
||||||
|
/* CD-ROMs are read-only, so override any "rw" on the kernel command line. */
|
||||||
|
if (strv_contains(arg_kernel_cmdline_extra, "rw") &&
|
||||||
|
strv_extend(&arg_kernel_cmdline_extra, "ro") < 0)
|
||||||
|
return log_oom();
|
||||||
|
} else if (!arg_ephemeral) {
|
||||||
|
/* In ephemeral mode the original image is never written to, the requested size is
|
||||||
|
* applied to the qcow2 overlay instead, see prepare_primary_drive(). */
|
||||||
r = grow_image(arg_image, arg_grow_image);
|
r = grow_image(arg_image, arg_grow_image);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
/* CD-ROMs are read-only, so override any "rw" on the kernel command line. */
|
}
|
||||||
} else if (strv_contains(arg_kernel_cmdline_extra, "rw") &&
|
|
||||||
strv_extend(&arg_kernel_cmdline_extra, "ro") < 0)
|
|
||||||
return log_oom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||||
@@ -4225,7 +4229,9 @@ static int verify_arguments(void) {
|
|||||||
log_warning("--grow-image has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only).");
|
log_warning("--grow-image has no effect with --image-disk-type=scsi-cd (CD-ROMs are read-only).");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg_grow_image && arg_image_format == IMAGE_FORMAT_QCOW2)
|
/* In ephemeral mode the size is picked when creating the qcow2 overlay, so the base image format
|
||||||
|
* doesn't matter. */
|
||||||
|
if (arg_grow_image && arg_image_format == IMAGE_FORMAT_QCOW2 && !arg_ephemeral)
|
||||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||||
"--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'.");
|
"--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'.");
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ WORKDIR="$(mktemp -d)"
|
|||||||
|
|
||||||
at_exit() {
|
at_exit() {
|
||||||
set +e
|
set +e
|
||||||
for m in "${MACHINE_MULTI:-}" "${MACHINE_EPHEMERAL:-}"; do
|
for m in "${MACHINE_MULTI:-}" "${MACHINE_EPHEMERAL:-}" "${MACHINE_GROW:-}"; do
|
||||||
[[ -n "$m" ]] || continue
|
[[ -n "$m" ]] || continue
|
||||||
if machinectl status "$m" &>/dev/null; then
|
if machinectl status "$m" &>/dev/null; then
|
||||||
machinectl terminate "$m" 2>/dev/null
|
machinectl terminate "$m" 2>/dev/null
|
||||||
@@ -62,6 +62,7 @@ at_exit() {
|
|||||||
done
|
done
|
||||||
[[ -n "${VMSPAWN_MULTI_PID:-}" ]] && kill "$VMSPAWN_MULTI_PID" 2>/dev/null && wait "$VMSPAWN_MULTI_PID" 2>/dev/null
|
[[ -n "${VMSPAWN_MULTI_PID:-}" ]] && kill "$VMSPAWN_MULTI_PID" 2>/dev/null && wait "$VMSPAWN_MULTI_PID" 2>/dev/null
|
||||||
[[ -n "${VMSPAWN_EPHEMERAL_PID:-}" ]] && kill "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null && wait "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null
|
[[ -n "${VMSPAWN_EPHEMERAL_PID:-}" ]] && kill "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null && wait "$VMSPAWN_EPHEMERAL_PID" 2>/dev/null
|
||||||
|
[[ -n "${VMSPAWN_GROW_PID:-}" ]] && kill "$VMSPAWN_GROW_PID" 2>/dev/null && wait "$VMSPAWN_GROW_PID" 2>/dev/null
|
||||||
rm -rf "$WORKDIR"
|
rm -rf "$WORKDIR"
|
||||||
}
|
}
|
||||||
trap at_exit EXIT
|
trap at_exit EXIT
|
||||||
@@ -185,4 +186,59 @@ timeout 10 bash -c "while machinectl status '$MACHINE_EPHEMERAL' &>/dev/null; do
|
|||||||
timeout 10 bash -c "while kill -0 '$VMSPAWN_EPHEMERAL_PID' 2>/dev/null; do sleep .5; done"
|
timeout 10 bash -c "while kill -0 '$VMSPAWN_EPHEMERAL_PID' 2>/dev/null; do sleep .5; done"
|
||||||
echo "Ephemeral VM terminated cleanly"
|
echo "Ephemeral VM terminated cleanly"
|
||||||
|
|
||||||
|
# --- Test 3: Ephemeral overlay grown with --grow-image= ---
|
||||||
|
# In ephemeral mode the requested size has to land on the qcow2 overlay. The
|
||||||
|
# image passed to --image= is opened read-only and has to come out of the run
|
||||||
|
# at its original size.
|
||||||
|
|
||||||
|
MACHINE_GROW="test-vmspawn-grow-$$"
|
||||||
|
GROW_SIZE=$((512 * 1024 * 1024))
|
||||||
|
IMAGE_SIZE="$(stat -c %s "$WORKDIR/root.raw")"
|
||||||
|
|
||||||
|
systemd-vmspawn \
|
||||||
|
--machine="$MACHINE_GROW" \
|
||||||
|
--ram=256M \
|
||||||
|
--image="$WORKDIR/root.raw" \
|
||||||
|
--ephemeral \
|
||||||
|
--grow-image="$GROW_SIZE" \
|
||||||
|
--linux="$KERNEL" \
|
||||||
|
--tpm=no \
|
||||||
|
--console=headless \
|
||||||
|
root=/dev/vda rw \
|
||||||
|
&>"$WORKDIR/vmspawn-grow.log" &
|
||||||
|
VMSPAWN_GROW_PID=$!
|
||||||
|
|
||||||
|
wait_for_machine "$MACHINE_GROW" "$VMSPAWN_GROW_PID" "$WORKDIR/vmspawn-grow.log"
|
||||||
|
echo "Grown ephemeral machine '$MACHINE_GROW' registered with machined"
|
||||||
|
|
||||||
|
assert_eq "$(stat -c %s "$WORKDIR/root.raw")" "$IMAGE_SIZE"
|
||||||
|
echo "Image passed to --image= was left at its original size"
|
||||||
|
|
||||||
|
# The overlay is unlinked, so QEMU's fd is the only way to reach it. vmspawn
|
||||||
|
# registers QEMU itself as the machine leader, so machined knows its PID.
|
||||||
|
QEMU_PID="$(varlinkctl call /run/systemd/machine/io.systemd.Machine \
|
||||||
|
io.systemd.Machine.List "{\"name\":\"$MACHINE_GROW\"}" | jq -r '.leader.pid')"
|
||||||
|
|
||||||
|
OVERLAY=""
|
||||||
|
for fd in /proc/"$QEMU_PID"/fd/*; do
|
||||||
|
target="$(readlink "$fd" 2>/dev/null)" || continue
|
||||||
|
# O_TMPFILE in the runtime directory, or the memfd fallback.
|
||||||
|
[[ "$target" == */vmspawn/"$MACHINE_GROW"/#* || "$target" == /memfd:vmspawn-overlay* ]] || continue
|
||||||
|
OVERLAY="$fd"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
assert_neq "$OVERLAY" ""
|
||||||
|
echo "Ephemeral overlay is QEMU fd ${OVERLAY##*/}"
|
||||||
|
|
||||||
|
# qcow2 header: the magic, followed by the virtual size as a big endian u64 at
|
||||||
|
# offset 24. Read it out of the header directly, qemu-img may not be installed.
|
||||||
|
assert_eq "$(od -An -tx1 -N4 "$OVERLAY" | tr -d ' ')" "514649fb"
|
||||||
|
assert_eq "$(od -An -tu8 -j24 -N8 --endian=big "$OVERLAY" | tr -d ' ')" "$GROW_SIZE"
|
||||||
|
echo "Ephemeral overlay was created at the requested size"
|
||||||
|
|
||||||
|
machinectl terminate "$MACHINE_GROW"
|
||||||
|
timeout 10 bash -c "while machinectl status '$MACHINE_GROW' &>/dev/null; do sleep .5; done"
|
||||||
|
timeout 10 bash -c "while kill -0 '$VMSPAWN_GROW_PID' 2>/dev/null; do sleep .5; done"
|
||||||
|
echo "Grown ephemeral VM terminated cleanly"
|
||||||
|
|
||||||
echo "All vmspawn drive setup tests passed"
|
echo "All vmspawn drive setup tests passed"
|
||||||
|
|||||||
Reference in New Issue
Block a user