Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions petri/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,12 @@ pub struct PetriVmBuilder<T: PetriVmmBackend> {
/// The boot device type for the VM
boot_device_type: BootDeviceType,

// Minimal mode: skip default devices, serial, save/restore.
minimal_mode: bool,
// Raw pipette binary path (for CPIO embedding in initrd).
pipette_binary: Option<ResolvedArtifact>,
// Enable serial output even in minimal mode (for diagnostics).
enable_serial: bool,
// Pre-built initrd with pipette already injected (skips runtime injection).
prebuilt_initrd: Option<PathBuf>,
}
Expand All @@ -189,6 +193,8 @@ impl<T: PetriVmmBackend> Debug for PetriVmBuilder<T> {
.field("agent_image", &self.agent_image)
.field("openhcl_agent_image", &self.openhcl_agent_image)
.field("boot_device_type", &self.boot_device_type)
.field("minimal_mode", &self.minimal_mode)
.field("enable_serial", &self.enable_serial)
.field("prebuilt_initrd", &self.prebuilt_initrd)
.finish()
}
Expand Down Expand Up @@ -234,8 +240,12 @@ pub struct PetriVmProperties {
pub using_vpci: bool,
/// The OS flavor of the guest in the VM
pub os_flavor: OsFlavor,
/// Minimal mode: skip default devices, serial, save/restore
pub minimal_mode: bool,
/// Pipette embeds in initrd as PID 1 (non-OpenHCL Linux direct boot)
pub uses_pipette_as_init: bool,
/// Enable serial output even in minimal mode
pub enable_serial: bool,
/// Pre-built initrd path with pipette already injected
pub prebuilt_initrd: Option<PathBuf>,
/// Whether the VM has a CIDATA agent disk attached
Expand Down Expand Up @@ -399,13 +409,89 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
openhcl_agent_image: artifacts.openhcl_agent_image,
boot_device_type,

minimal_mode: false,
pipette_binary: artifacts.pipette_binary,
enable_serial: true,
prebuilt_initrd: None,
}
.add_petri_scsi_controllers()
.add_guest_crash_disk(params.post_test_hooks))
}

/// Create a minimal VM builder with only the bare minimum device set.
///
/// Unlike [`new()`](Self::new), this constructor:
/// - Does not add default VMBus devices (shutdown IC, KVP, etc.)
/// - Does not add serial ports
/// - Does not add SCSI controllers or crash dump disks
/// - Does not verify save/restore on boot
///
/// Use builder methods to opt in to specific devices. Intended for
/// performance tests where minimal overhead is critical.
pub fn minimal(
params: PetriTestParams<'_>,
artifacts: PetriVmArtifacts<T>,
driver: &DefaultDriver,
) -> anyhow::Result<Self> {
let (guest_quirks, vmm_quirks) = T::quirks(&artifacts.firmware);
let expected_boot_event = artifacts.firmware.expected_boot_event();
let boot_device_type = match artifacts.firmware {
Firmware::LinuxDirect { .. } => BootDeviceType::None,
Firmware::OpenhclLinuxDirect { .. } => BootDeviceType::None,
Firmware::Pcat { .. } => BootDeviceType::Ide,
Firmware::OpenhclPcat { .. } => BootDeviceType::IdeViaScsi,
Firmware::Uefi {
guest: UefiGuest::None,
..
}
| Firmware::OpenhclUefi {
guest: UefiGuest::None,
..
} => BootDeviceType::None,
Firmware::Uefi { .. } | Firmware::OpenhclUefi { .. } => BootDeviceType::Scsi,
};

Ok(Self {
backend: artifacts.backend,
config: PetriVmConfig {
name: make_vm_safe_name(params.test_name),
arch: artifacts.arch,
host_log_levels: None,
firmware: artifacts.firmware,
memory: Default::default(),
proc_topology: Default::default(),

vmgs: PetriVmgsResource::Ephemeral,
tpm: None,
vmbus_storage_controllers: HashMap::new(),
},
Comment thread
jstarks marked this conversation as resolved.
modify_vmm_config: None,
resources: PetriVmResources {
driver: driver.clone(),
log_source: params.logger.clone(),
},

guest_quirks,
vmm_quirks,
expected_boot_event,
override_expect_reset: false,

agent_image: artifacts.agent_image,
openhcl_agent_image: artifacts.openhcl_agent_image,
boot_device_type,

minimal_mode: true,
pipette_binary: artifacts.pipette_binary,
enable_serial: false,
prebuilt_initrd: None,
})
}

/// Whether this builder is in minimal mode.
pub fn is_minimal(&self) -> bool {
self.minimal_mode
}

/// Supply a pre-built initrd with pipette already injected.
///
/// When set, the builder skips the runtime gzip decompress/inject/
Expand Down Expand Up @@ -462,6 +548,19 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
Ok(tmp.into_temp_path())
}

/// Enable serial port output even in minimal mode.
///
/// Useful for diagnostics — the serial device overhead is negligible;
/// the cost comes from kernel console output, which is controlled via
/// the kernel cmdline (`quiet loglevel=0`).
///
/// Note: this currently only affects LinuxDirect boot (kernel cmdline
/// and emulated serial backends). UEFI paths are unaffected.
pub fn with_serial_output(mut self) -> Self {
self.enable_serial = true;
self
}

fn add_petri_scsi_controllers(self) -> Self {
let builder = self.add_vmbus_storage_controller(
&PETRI_SCSI_VTL0_CONTROLLER,
Expand Down Expand Up @@ -689,7 +788,9 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
using_vtl0_pipette: self.using_vtl0_pipette(),
using_vpci: self.boot_device_type.requires_vpci_boot(),
os_flavor: self.config.firmware.os_flavor(),
minimal_mode: self.minimal_mode,
uses_pipette_as_init: self.uses_pipette_as_init(),
enable_serial: self.enable_serial,
prebuilt_initrd: self.prebuilt_initrd.clone(),
has_agent_disk: self.agent_image.is_some(),
}
Expand Down
99 changes: 66 additions & 33 deletions petri/src/vm/openvmm/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ impl PetriVmConfigOpenVmm {
mesh: &mesh,
openvmm_path,
uses_pipette_as_init: properties.uses_pipette_as_init,
enable_serial: properties.enable_serial,
};

let mut chipset = VmManifestBuilder::new(
Expand Down Expand Up @@ -175,11 +176,23 @@ impl PetriVmConfigOpenVmm {
}
}

let SerialData {
mut emulated_serial_config,
serial_tasks: log_stream_tasks,
linux_direct_serial_agent,
} = setup.configure_serial(log_source)?;
let (emulated_serial_config, log_stream_tasks, linux_direct_serial_agent) =
if !properties.enable_serial {
// No emulated serial backends (OpenHCL VMBus serial stubs may still exist)
([None, None, None, None], Vec::new(), None)
} else {
let SerialData {
emulated_serial_config,
serial_tasks,
linux_direct_serial_agent,
} = setup.configure_serial(log_source)?;
(
emulated_serial_config,
serial_tasks,
linux_direct_serial_agent,
)
};
let mut emulated_serial_config = emulated_serial_config;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just make this mut on the previous line


let (video_dev, framebuffer, framebuffer_view) = match setup.config_video()? {
Some((v, fb, fba)) => {
Expand Down Expand Up @@ -244,12 +257,15 @@ impl PetriVmConfigOpenVmm {

// Configure the serial ports now that they have been updated by the
// OpenHCL configuration.
chipset = chipset.with_serial(emulated_serial_config);
// Set so that we don't pull serial data until the guest is
// ready. Otherwise, Linux will drop the input serial data
// on the floor during boot.
if matches!(firmware, Firmware::LinuxDirect { .. }) {
chipset = chipset.with_serial_wait_for_rts();
if properties.enable_serial {
chipset = chipset.with_serial(emulated_serial_config);
// Set so that we don't pull serial data until the guest is
// ready. Otherwise, Linux will drop the input serial data
// on the floor during boot.
if matches!(firmware, Firmware::LinuxDirect { .. }) && !properties.uses_pipette_as_init
{
chipset = chipset.with_serial_wait_for_rts();
Comment thread
jstarks marked this conversation as resolved.
}
}

// Extract video configuration
Expand All @@ -262,28 +278,38 @@ impl PetriVmConfigOpenVmm {
None => None,
};

// Add the Hyper-V Shutdown IC
let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
vmbus_devices.push((
DeviceVtl::Vtl0,
ShutdownIcHandle {
recv: shutdown_ic_recv,
}
.into_resource(),
));
// Add default VMBus devices (skipped in minimal mode).
let (shutdown_ic_send, kvp_ic_send) = if !properties.minimal_mode {
let (shutdown_ic_send, shutdown_ic_recv) = mesh::channel();
vmbus_devices.push((
DeviceVtl::Vtl0,
ShutdownIcHandle {
recv: shutdown_ic_recv,
}
.into_resource(),
));

// Add the Hyper-V KVP IC
let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
vmbus_devices.push((
DeviceVtl::Vtl0,
hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
));
let (kvp_ic_send, kvp_ic_recv) = mesh::channel();
vmbus_devices.push((
DeviceVtl::Vtl0,
hyperv_ic_resources::kvp::KvpIcHandle { recv: kvp_ic_recv }.into_resource(),
));

vmbus_devices.push((
DeviceVtl::Vtl0,
hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
));

// Add the Hyper-V timesync IC
vmbus_devices.push((
DeviceVtl::Vtl0,
hyperv_ic_resources::timesync::TimesyncIcHandle.into_resource(),
));
(shutdown_ic_send, kvp_ic_send)
} else {
// Minimal mode: no ICs. Create dummy senders so the fields
// are populated (calls to send_enlightened_shutdown will fail
// with a channel error, which is fine — minimal VMs shut down
// via reboot(2) directly).
let (shutdown_ic_send, _) = mesh::channel();
let (kvp_ic_send, _) = mesh::channel();
(shutdown_ic_send, kvp_ic_send)
Comment thread
jstarks marked this conversation as resolved.
};

// Make a vmbus vsock path for pipette connections
let (vmbus_vsock_listener, vmbus_vsock_path) = make_vsock_listener()?;
Expand Down Expand Up @@ -541,6 +567,7 @@ struct PetriVmConfigSetupCore<'a> {
mesh: &'a Mesh,
openvmm_path: &'a ResolvedArtifact,
uses_pipette_as_init: bool,
enable_serial: bool,
}

struct SerialData {
Expand Down Expand Up @@ -649,15 +676,21 @@ impl PetriVmConfigSetupCore<'_> {
"/bin/sh"
};

let serial_args = if self.enable_serial {
format!("{console} debug ")
} else {
String::new()
};
Comment thread
jstarks marked this conversation as resolved.

let cmdline =
format!("{console} debug panic=-1 rdinit={init} {VIRTIO_VSOCK_BLACKLIST}");
format!("{serial_args}panic=-1 rdinit={init} {VIRTIO_VSOCK_BLACKLIST}");

LoadMode::Linux {
kernel,
initrd: Some(initrd),
cmdline,
custom_dsdt: None,
enable_serial: true,
enable_serial: self.enable_serial,
}
}
(
Expand Down
4 changes: 3 additions & 1 deletion petri/src/vm/openvmm/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ impl PetriVmConfigOpenVmm {

let worker = Arc::new(worker);

let is_minimal = resources.properties.minimal_mode;

let mut vm = PetriVmOpenVmm::new(
super::runtime::PetriVmInner {
resources,
Expand All @@ -103,7 +105,7 @@ impl PetriVmConfigOpenVmm {
vm.resume().await?;

// Run basic save/restore test if it is supported
if supports_save_restore {
if supports_save_restore && !is_minimal {
tracing::info!("Testing save/restore");
vm.verify_save_restore().await?;
}
Expand Down
Loading