-
Notifications
You must be signed in to change notification settings - Fork 16
qemu: Various enhancements #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,7 +118,39 @@ pub struct ResourceLimits { | |
| pub nice_level: Option<i8>, | ||
| } | ||
|
|
||
| /// VM boot configuration: direct kernel boot. | ||
| /// QEMU machine type selection. | ||
| #[derive(Debug, Clone, Default)] | ||
| pub enum MachineType { | ||
| /// Auto-detect based on host architecture (recommended). | ||
| #[default] | ||
| Auto, | ||
| /// Use a specific machine type string (e.g., "q35", "pc-q35-9.2"). | ||
| Explicit(String), | ||
| } | ||
|
|
||
| impl MachineType { | ||
| /// Resolve to a QEMU `-machine` argument for the current host. | ||
| /// | ||
| /// Returns `None` for unknown architectures, letting QEMU use its default. | ||
| pub fn resolve(&self) -> Option<&str> { | ||
| // xref: https://github.com/coreos/coreos-assembler/blob/main/mantle/platform/qemu.go | ||
| match self { | ||
| Self::Auto => match std::env::consts::ARCH { | ||
| "x86_64" => Some("q35"), | ||
| // gic-version=max selects the best available GIC for the host | ||
| "aarch64" => Some("virt,gic-version=max"), | ||
| "s390x" => Some("s390-ccw-virtio"), | ||
| // kvm-type=HV ensures bare metal KVM, not user mode | ||
| // ic-mode=xics for interrupt controller | ||
| "powerpc64" => Some("pseries,kvm-type=HV,ic-mode=xics"), | ||
| _ => None, | ||
| }, | ||
| Self::Explicit(name) => Some(name.as_str()), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// VM boot configuration. | ||
| #[derive(Debug)] | ||
| pub enum BootMode { | ||
| /// Direct kernel boot (fast, testing-focused). | ||
|
|
@@ -139,6 +171,15 @@ pub enum BootMode { | |
| /// VirtIO-FS socket for root filesystem. | ||
| virtiofs_socket: Utf8PathBuf, | ||
| }, | ||
| /// Boot from an ISO image (e.g. for Anaconda installer testing). | ||
| /// | ||
| /// The ISO is attached as a CDROM device. Unlike DirectBoot, there is no | ||
| /// root virtiofs socket — the installer boots from the ISO and installs | ||
| /// to a disk device added via [`QemuConfig::add_virtio_blk_device`]. | ||
| IsoBoot { | ||
| /// Path to the ISO image file. | ||
| iso_path: String, | ||
| }, | ||
| } | ||
|
|
||
| /// Complete QEMU VM configuration with builder pattern. | ||
|
|
@@ -148,6 +189,8 @@ pub struct QemuConfig { | |
| pub memory_mb: u32, | ||
| /// Number of vCPUs (1-256). | ||
| pub vcpus: u32, | ||
| /// Machine type (default: auto-detect based on host architecture). | ||
| pub machine_type: MachineType, | ||
| boot_mode: Option<BootMode>, | ||
| /// Main VirtioFS configuration for root filesystem (handled separately from additional mounts). | ||
| pub main_virtiofs_config: Option<VirtiofsConfig>, | ||
|
|
@@ -171,6 +214,11 @@ pub struct QemuConfig { | |
| pub enable_console: bool, | ||
| /// SMBIOS credentials for systemd. | ||
| smbios_credentials: Vec<String>, | ||
| /// Path to write serial console output (if set, `-serial file:<path>` | ||
| /// is used instead of `-serial none`). | ||
| pub serial_log: Option<String>, | ||
| /// Prevent automatic reboot (useful for debugging or post-install inspection). | ||
| pub no_reboot: bool, | ||
|
|
||
| /// Write systemd notifications to this file. | ||
| pub systemd_notify: Option<File>, | ||
|
|
@@ -200,6 +248,16 @@ impl QemuConfig { | |
| } | ||
| } | ||
|
|
||
| /// Create a new config for ISO boot (e.g. Anaconda installer). | ||
| pub fn new_iso_boot(memory_mb: u32, vcpus: u32, iso_path: String) -> Self { | ||
| Self { | ||
| memory_mb, | ||
| vcpus, | ||
| boot_mode: Some(BootMode::IsoBoot { iso_path }), | ||
| ..Default::default() | ||
| } | ||
| } | ||
|
|
||
| /// Enable vsock support. | ||
| pub fn enable_vsock(&mut self) -> Result<()> { | ||
| let fd = OpenOptions::new() | ||
|
|
@@ -249,6 +307,39 @@ impl QemuConfig { | |
| return Err(eyre!("vCPU count too high: {} (maximum 256)", self.vcpus)); | ||
| } | ||
|
|
||
| // Validate boot mode specifics | ||
| match &self.boot_mode { | ||
| Some(BootMode::IsoBoot { iso_path }) => { | ||
| if iso_path.is_empty() { | ||
| return Err(eyre!("ISO path cannot be empty")); | ||
| } | ||
| if !std::path::Path::new(iso_path).exists() { | ||
| return Err(eyre!("ISO image not found: {}", iso_path)); | ||
| } | ||
| // main_virtiofs_config is for the root filesystem in DirectBoot; | ||
| // it has no meaning for ISO boot. | ||
| if self.main_virtiofs_config.is_some() { | ||
| return Err(eyre!( | ||
| "main_virtiofs_config is not supported with ISO boot \ | ||
| (the root filesystem comes from the ISO)" | ||
| )); | ||
| } | ||
| } | ||
| Some(BootMode::DirectBoot { | ||
| kernel_path, | ||
| initramfs_path, | ||
| .. | ||
| }) => { | ||
| if kernel_path.is_empty() { | ||
| return Err(eyre!("Kernel path cannot be empty")); | ||
| } | ||
| if initramfs_path.is_empty() { | ||
| return Err(eyre!("Initramfs path cannot be empty")); | ||
| } | ||
| } | ||
| None => {} | ||
| } | ||
|
|
||
| // Validate virtiofs mounts | ||
| for mount in &self.additional_mounts { | ||
| if mount.tag.is_empty() { | ||
|
|
@@ -430,6 +521,12 @@ fn spawn( | |
| .map_err(Into::into) | ||
| }); | ||
| } | ||
|
|
||
| // Set machine type (auto-detected or explicit) | ||
| if let Some(machine) = config.machine_type.resolve() { | ||
| cmd.args(["-machine", machine]); | ||
| } | ||
|
|
||
| cmd.args([ | ||
| "-m", | ||
| &memory_arg, | ||
|
|
@@ -446,6 +543,10 @@ fn spawn( | |
| "node,memdev=mem", | ||
| ]); | ||
|
|
||
| if config.no_reboot { | ||
| cmd.arg("-no-reboot"); | ||
| } | ||
|
|
||
| for (idx, fd) in config.fdset.iter().enumerate() { | ||
| let fd_id = 100 + idx as u32; // Start at 100 to avoid conflicts | ||
| let set_id = idx + 1; // fdset starts at 1 | ||
|
|
@@ -498,6 +599,9 @@ fn spawn( | |
| let append_str = kernel_cmdline.join(" "); | ||
| cmd.args(["-append", &append_str]); | ||
| } | ||
| Some(BootMode::IsoBoot { iso_path }) => { | ||
| cmd.args(["-cdrom", iso_path]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, we boot from ISO and boot the ISO's bootloader. That requires firmware. Do we need add UEFI boot or just keep BIOS by default? Thanks.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes...the messy thing here is that supporting uefi adds more nontrivial code into our qemu.rs which we don't technically need right now. There's just this giant tension between this case versus libvirt (and systemd-vmspawn) among other cases. |
||
| } | ||
| None => {} | ||
| } | ||
|
|
||
|
|
@@ -562,8 +666,13 @@ fn spawn( | |
| } | ||
| } | ||
|
|
||
| // No GUI, and no emulated serial ports by default. | ||
| cmd.args(["-serial", "none", "-nographic", "-display", "none"]); | ||
| // No GUI; serial console either to a log file or disabled. | ||
| if let Some(ref serial_path) = config.serial_log { | ||
| cmd.args(["-serial", &format!("file:{}", serial_path)]); | ||
| } else { | ||
| cmd.args(["-serial", "none"]); | ||
| } | ||
| cmd.args(["-nographic", "-display", "none"]); | ||
|
|
||
| match &config.display_mode { | ||
| DisplayMode::None => { | ||
|
|
@@ -868,6 +977,17 @@ mod tests { | |
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_iso_boot_config() { | ||
| let config = QemuConfig::new_iso_boot(2048, 2, "/test/image.iso".to_string()); | ||
| assert_eq!(config.memory_mb, 2048); | ||
| assert_eq!(config.vcpus, 2); | ||
| assert!(matches!( | ||
| &config.boot_mode, | ||
| Some(BootMode::IsoBoot { iso_path }) if iso_path == "/test/image.iso" | ||
| )); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_disk_format() { | ||
| assert_eq!(DiskFormat::Raw.as_str(), "raw"); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.