diff --git a/pve/vm-core.func b/pve/vm-core.func index ad4dfec..741cca0 100644 --- a/pve/vm-core.func +++ b/pve/vm-core.func @@ -2641,7 +2641,16 @@ vm_import_disk() { before="$(qm config "$vmid" 2>/dev/null | sed -n 's/^unused[0-9]*: //p')" || before="" msg_info "Importing $(basename "$file") to ${storage}" - if ! out="$("${cmd[@]}" "$vmid" "$file" "$storage" "${opts[@]}" 2>&1)"; then + # A freshly extracted image still has writes pending; on ZFS the volume create + # then waited behind them and failed with "got timeout". + sync -f "$file" 2>/dev/null || sync + local attempt rc + for attempt in 1 2 3; do + out="$("${cmd[@]}" "$vmid" "$file" "$storage" "${opts[@]}" 2>&1)" && rc=0 || rc=$? + [[ $rc -ne 0 && "$out" == *"got timeout"* && $attempt -lt 3 ]] || break + sleep $((attempt * 10)) + done + if [[ $rc -ne 0 ]]; then msg_error "Importing $(basename "$file") into VM ${vmid} failed" # Into the log too: the failure trace is read from there. printf '%s\n' "$out" | tail -n 5 | tee -a "$(get_active_logfile)" >&2 || true diff --git a/tests/vm-helpers.sh b/tests/vm-helpers.sh index d2cba66..b0edf47 100644 --- a/tests/vm-helpers.sh +++ b/tests/vm-helpers.sh @@ -208,11 +208,17 @@ QM_CONFIG_BEFORE="" QM_CONFIG_AFTER="" # A file, not a variable: the import runs inside a command substitution. QM_IMPORTED_FLAG="$TEST_DIR/qm-imported" +QM_TIMEOUTS_FILE="$TEST_DIR/qm-timeouts" qm() { printf 'qm %s\n' "$*" >>"$LOG" case "$1 ${2:-}" in "disk import") if [[ "${3:-}" == --help ]]; then [[ "$QM_MODERN" == yes ]]; return; fi + if [[ -s "$QM_TIMEOUTS_FILE" && "$(cat "$QM_TIMEOUTS_FILE")" -gt 0 ]]; then + echo $(($(cat "$QM_TIMEOUTS_FILE") - 1)) >"$QM_TIMEOUTS_FILE" + echo "command 'zfs create -s -V 33554432k rpool/data/vm-100-disk-0' failed: got timeout" + return 255 + fi : >"$QM_IMPORTED_FLAG" printf '%s\n' "$QM_IMPORT_OUT" return "$QM_IMPORT_RC" @@ -256,6 +262,19 @@ 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" +import_case +echo 1 >"$QM_TIMEOUTS_FILE" +vm_import_disk 100 "$IMAGE" local-lvm || fail "import after a zfs timeout" +assert_eq "$(grep -c 'qm disk import 100' "$LOG")" 2 "one timeout, one retry" +ok "vm_import_disk retries after a ZFS got timeout" + +import_case +echo 9 >"$QM_TIMEOUTS_FILE" +vm_import_disk 100 "$IMAGE" local-lvm 2>/dev/null && fail "three timeouts still fail" +assert_eq "$(grep -c 'qm disk import 100' "$LOG")" 3 "gives up after three attempts" +: >"$QM_TIMEOUTS_FILE" +ok "vm_import_disk gives up after three timeouts" + import_case QM_MODERN=no vm_import_disk 100 "$IMAGE" local-lvm qcow2 || fail "legacy import"