From 9246a2e43eb82809dbde5a4ac63653d316c7ca2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B6gl-Brunner=20Michel?= Date: Tue, 10 Nov 2026 09:42:00 +0100 Subject: [PATCH] fix: flush the image before importing a VM disk Freshly extracted or converted images leave gigabytes of dirty pages. On a default ZFS install the image (rpool/var-lib-vz) and the target zvol (rpool/data) share a pool, so the zvol create inside qm disk import hits Proxmox's zfs command timeout ("got timeout") while ZFS writes them back. vm_import_disk now runs a best-effort sync -f on the image first, which covers every VM script at once instead of patching them one by one. Refs community-scripts/ProxmoxVE#18580, community-scripts/ProxmoxVE#18581 --- docs/vm.md | 2 +- pve/vm-core.func | 10 ++++++++++ tests/vm-helpers.sh | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/vm.md b/docs/vm.md index 2104586..a513502 100644 --- a/docs/vm.md +++ b/docs/vm.md @@ -70,7 +70,7 @@ layouts are understood and the algorithm follows from the hash length. | Function | Contract | | --- | --- | -| `vm_import_disk [format]` | `qm disk import` (or `qm importdisk` on older hosts) with `DISK_IMPORT_FORMAT`; sets `VM_IMPORTED_DISK` to the volume Proxmox reported or the `unusedN` entry the import added. Never guesses a name. | +| `vm_import_disk [format]` | Flushes the image to disk (`sync -f`, best effort), then `qm disk import` (or `qm importdisk` on older hosts) with `DISK_IMPORT_FORMAT`; sets `VM_IMPORTED_DISK` to the volume Proxmox reported or the `unusedN` entry the import added. Never guesses a name. | | `kvm_check` | Part of `vm_preflight`, as on Incus: exit 236 when `/dev/kvm` is missing, before any question or download. `qm start` refuses a KVM guest without it. | | `vm_fit_cores` | Cuts `CORE_COUNT` to the host's CPU threads, with a warning. `vm_start_script` calls it after `default_settings`; `vm_prompt_cpu_cores` refuses a larger answer. | | `vm_claim_vmid` | Call right before `qm create`: re-checks `VMID`, which was picked before the download. An auto-picked ID another run took moves to the next free one and the disk names follow; an ID chosen in advanced settings stops with exit 205. | diff --git a/pve/vm-core.func b/pve/vm-core.func index ad4dfec..3c47d1b 100644 --- a/pve/vm-core.func +++ b/pve/vm-core.func @@ -2630,6 +2630,12 @@ vm_resize_disk() { # entry the import added. Scripts used to guess the name or take the last # `pvesm list` line, which sorts disk-10 before disk-9. The format defaults to # DISK_IMPORT_FORMAT from vm_select_storage. +# +# The image is flushed to disk first. Images are often just extracted or +# converted (xz, gzip, qemu-img, virt-customize), leaving gigabytes of dirty +# pages. When they live on the same ZFS pool as the target (rpool/var-lib-vz +# and rpool/data on a default install), the zvol create inside the import runs +# into Proxmox's zfs command timeout ("got timeout") while ZFS writes them back. vm_import_disk() { local vmid="${1:?vmid}" file="${2:?image}" storage="${3:?storage}" format="${4:-${DISK_IMPORT_FORMAT:-}}" local out before volume @@ -2640,6 +2646,10 @@ vm_import_disk() { [[ -n "$format" ]] && opts=(--format "$format") before="$(qm config "$vmid" 2>/dev/null | sed -n 's/^unused[0-9]*: //p')" || before="" + msg_info "Flushing $(basename "$file") to disk" + # Best effort: a failed flush is no reason to skip the import. + sync -f "$file" 2>/dev/null || sync 2>/dev/null || true + msg_info "Importing $(basename "$file") to ${storage}" if ! out="$("${cmd[@]}" "$vmid" "$file" "$storage" "${opts[@]}" 2>&1)"; then msg_error "Importing $(basename "$file") into VM ${vmid} failed" diff --git a/tests/vm-helpers.sh b/tests/vm-helpers.sh index d2cba66..b0275f5 100644 --- a/tests/vm-helpers.sh +++ b/tests/vm-helpers.sh @@ -237,6 +237,7 @@ qm() { *) return 0 ;; esac } +sync() { printf 'sync %s\n' "$*" >>"$LOG"; return "${SYNC_RC:-0}"; } pct() { return 1; } lvs() { :; } pvesh() { printf '%s\n' "${NEXTID:-105}"; } @@ -256,6 +257,21 @@ assert_eq "$VM_IMPORTED_DISK" "local-lvm:vm-100-disk-1" "volume from qm's report assert_contains "$LOG" "qm disk import 100 $IMAGE local-lvm --format raw" ok "vm_import_disk uses qm disk import and the reported volume" +# Dirty pages from extraction stall the zvol create on the same ZFS pool (#18580). +sync_line="$(grep -n "^sync -f $IMAGE\$" "$LOG" | cut -d: -f1)" +import_line="$(grep -n "^qm disk import 100 " "$LOG" | cut -d: -f1)" +[[ -n "$sync_line" && -n "$import_line" && "$sync_line" -lt "$import_line" ]] || + fail "the image must be flushed before qm disk import" +ok "vm_import_disk flushes the image before importing it" + +import_case +SYNC_RC=1 +vm_import_disk 100 "$IMAGE" local-lvm || fail "a failed flush must not stop the import" +assert_eq "$VM_IMPORTED_DISK" "local-lvm:vm-100-disk-1" "imported despite the failed flush" +assert_contains "$LOG" "qm disk import 100 $IMAGE local-lvm" +SYNC_RC=0 +ok "vm_import_disk imports even when the flush fails" + import_case QM_MODERN=no vm_import_disk 100 "$IMAGE" local-lvm qcow2 || fail "legacy import"