fix: real-time progress during qemu-img convert, smoother percentages
qemu-img convert is the longest step but previously had no progress updates — bar stuck at 10% until conversion completed. Now _convert_with_progress() runs qemu-img in background and polls output file size every 2s, updating progress with GiB done/total. Progress flow for single disk: 5% queued → 10% starting → 10-55% converting (real-time) → 60% shrinking → 70% creating VM → 75-85% importing → 95% configuring → 100% done
This commit is contained in:
parent
affcde7afb
commit
9a6d2df78b
1 changed files with 44 additions and 8 deletions
|
|
@ -145,6 +145,43 @@ def _run(cmd: list[str], check: bool = True, timeout: int = 3600) -> subprocess.
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_with_progress(
|
||||||
|
job_id: str, source_path: Path, output_path: Path, format: str,
|
||||||
|
start_pct: int, end_pct: int,
|
||||||
|
):
|
||||||
|
"""Run qemu-img convert in background and update job progress
|
||||||
|
by polling the output file size against the source virtual size."""
|
||||||
|
# Get source virtual size for percentage estimation
|
||||||
|
info = _run(["qemu-img", "info", "--output=json", str(source_path)])
|
||||||
|
source_size = json.loads(info.stdout).get("virtual-size", 0)
|
||||||
|
if source_size == 0:
|
||||||
|
source_size = source_path.stat().st_size
|
||||||
|
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
["qemu-img", "convert", "-f", format, "-O", "qcow2",
|
||||||
|
str(source_path), str(output_path)],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Poll output file size and update progress
|
||||||
|
while proc.poll() is None:
|
||||||
|
time.sleep(2)
|
||||||
|
if output_path.exists():
|
||||||
|
current = output_path.stat().st_size
|
||||||
|
if source_size > 0:
|
||||||
|
ratio = min(current / source_size, 1.0)
|
||||||
|
pct = start_pct + int(ratio * (end_pct - start_pct))
|
||||||
|
gb_done = current / (1024**3)
|
||||||
|
gb_total = source_size / (1024**3)
|
||||||
|
_update_job(job_id, progress=pct,
|
||||||
|
message=f"Converting disk... {gb_done:.1f} / {gb_total:.1f} GiB")
|
||||||
|
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise RuntimeError(f"qemu-img convert failed (rc={proc.returncode})")
|
||||||
|
|
||||||
|
_update_job(job_id, progress=end_pct, message="Disk converted.")
|
||||||
|
|
||||||
|
|
||||||
def _process_job(job_id: str, req: JobSubmissionRequest):
|
def _process_job(job_id: str, req: JobSubmissionRequest):
|
||||||
try:
|
try:
|
||||||
vmid = req.vmid
|
vmid = req.vmid
|
||||||
|
|
@ -171,13 +208,11 @@ def _process_job(job_id: str, req: JobSubmissionRequest):
|
||||||
logger.info("Disk %d: source=%s format=%s size=%.1f GiB",
|
logger.info("Disk %d: source=%s format=%s size=%.1f GiB",
|
||||||
idx, source_path, disk.format, disk.size_gb)
|
idx, source_path, disk.format, disk.size_gb)
|
||||||
|
|
||||||
_run(["qemu-img", "convert",
|
# Convert with real-time progress
|
||||||
"-f", disk.format,
|
_convert_with_progress(
|
||||||
"-O", "qcow2",
|
job_id, source_path, output_path, disk.format,
|
||||||
str(source_path), str(output_path)],
|
start_pct=10 + idx * 10, end_pct=55 + idx * 10,
|
||||||
timeout=7200)
|
)
|
||||||
_update_job(job_id, progress=20 + (idx + 1) * 15,
|
|
||||||
message=f"Converted disk {idx + 1}/{len(disk_specs)}")
|
|
||||||
else:
|
else:
|
||||||
size_gb = spec.size_gb or 32
|
size_gb = spec.size_gb or 32
|
||||||
logger.info("Creating empty disk %d: %d GiB", idx, size_gb)
|
logger.info("Creating empty disk %d: %d GiB", idx, size_gb)
|
||||||
|
|
@ -204,7 +239,7 @@ def _process_job(job_id: str, req: JobSubmissionRequest):
|
||||||
target_gb = int(round(current_gb)) + 1
|
target_gb = int(round(current_gb)) + 1
|
||||||
|
|
||||||
if target_gb < current_gb:
|
if target_gb < current_gb:
|
||||||
_update_job(job_id, progress=65,
|
_update_job(job_id, progress=60,
|
||||||
message=f"Shrinking disk to {target_gb} GiB...")
|
message=f"Shrinking disk to {target_gb} GiB...")
|
||||||
logger.info("Shrinking disk: %.1f GiB → %d GiB via virt-resize", current_gb, target_gb)
|
logger.info("Shrinking disk: %.1f GiB → %d GiB via virt-resize", current_gb, target_gb)
|
||||||
try:
|
try:
|
||||||
|
|
@ -276,6 +311,7 @@ def _process_job(job_id: str, req: JobSubmissionRequest):
|
||||||
f"{req.target_storage}:0,format=raw,size=4M"])
|
f"{req.target_storage}:0,format=raw,size=4M"])
|
||||||
|
|
||||||
# ── Finalise ───────────────────────────────────────────────
|
# ── Finalise ───────────────────────────────────────────────
|
||||||
|
_update_job(job_id, progress=95, message="Configuring VM...")
|
||||||
_run(["qm", "set", str(vmid), "--boot", "order=scsi0"])
|
_run(["qm", "set", str(vmid), "--boot", "order=scsi0"])
|
||||||
_run(["qm", "set", str(vmid), "--serial0", "socket"])
|
_run(["qm", "set", str(vmid), "--serial0", "socket"])
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue