Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 0d61b70

Browse files
committed
implement qemu disk resize support
1 parent 4b31a61 commit 0d61b70

1 file changed

Lines changed: 39 additions & 1 deletion

File tree

  • packages/jumpstarter-driver-qemu/jumpstarter_driver_qemu

packages/jumpstarter-driver-qemu/jumpstarter_driver_qemu/driver.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import os
66
import platform
7+
import shutil
78
from collections.abc import AsyncGenerator
89
from dataclasses import dataclass, field
910
from functools import cached_property
@@ -20,7 +21,7 @@
2021
from jumpstarter_driver_opendal.driver import FlasherInterface
2122
from jumpstarter_driver_power.driver import PowerInterface, PowerReading
2223
from jumpstarter_driver_pyserial.driver import PySerial
23-
from pydantic import BaseModel, Field, validate_call
24+
from pydantic import BaseModel, ByteSize, Field, TypeAdapter, ValidationError, validate_call
2425
from qemu.qmp import QMPClient
2526
from qemu.qmp.protocol import ConnectError, Runstate
2627

@@ -169,6 +170,7 @@ async def on(self) -> None: # noqa: C901
169170
proc.check_returncode()
170171
info = json.loads(proc.stdout)
171172
image_format = info.get("format", "raw")
173+
current_virtual_size = info.get("virtual-size") or root.stat().st_size
172174
match image_format:
173175
case "raw" | "qcow2" | "qcow" | "vmdk":
174176
image_driver = image_format
@@ -177,6 +179,34 @@ async def on(self) -> None: # noqa: C901
177179
except CalledProcessError:
178180
self.logger.warning("unable to detect image format, assuming raw")
179181
image_driver = "raw"
182+
current_virtual_size = root.stat().st_size
183+
184+
# Resize disk if configured
185+
if self.parent.disk_size:
186+
requested = self.parent._parse_size(self.parent.disk_size)
187+
188+
if requested < current_virtual_size:
189+
raise RuntimeError(
190+
f"Shrinking disk is not supported: current {ByteSize(current_virtual_size).human_readable()}, "
191+
f"requested {self.parent.disk_size}"
192+
)
193+
194+
available = shutil.disk_usage(root.parent).free
195+
if requested > available:
196+
raise RuntimeError(
197+
f"Not enough disk space: need {ByteSize(requested).human_readable()}, "
198+
f"only {ByteSize(available).human_readable()} available"
199+
)
200+
201+
if requested > current_virtual_size:
202+
self.logger.info(f"Resizing disk to {ByteSize(requested).human_readable()}")
203+
proc = await run_process(
204+
["qemu-img", "resize", str(root), str(requested)],
205+
stdout=PIPE,
206+
stderr=PIPE,
207+
)
208+
if proc.returncode != 0:
209+
raise RuntimeError(f"Failed to resize disk: {proc.stderr.decode()}")
180210

181211
cmdline += [
182212
"-blockdev",
@@ -254,6 +284,7 @@ class Qemu(Driver):
254284

255285
smp: int = 2
256286
mem: str = "512M"
287+
disk_size: str | None = None # e.g., "20G" (resize disk before boot)
257288

258289
hostname: str = "demo"
259290
username: str = "jumpstarter"
@@ -372,3 +403,10 @@ def get_username(self) -> str:
372403
@validate_call(validate_return=True)
373404
def get_password(self) -> str:
374405
return self.password
406+
407+
def _parse_size(self, size: str) -> int:
408+
"""Parse size string (e.g., '20G') to bytes."""
409+
try:
410+
return int(TypeAdapter(ByteSize).validate_python(size + "iB" if size[-1] in "kmgtKMGT" else size))
411+
except (ValidationError, IndexError):
412+
raise ValueError(f"Invalid size: '{size}'. Use e.g. '20G', '512M', '2T'") from None

0 commit comments

Comments
 (0)