From 5eff30ec0a58967d16fafdee88fa94e14d46e417 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Wed, 6 Aug 2025 14:27:58 +0100 Subject: [PATCH 01/13] refactor: drop usage of std::io::Sink This type is only useful if we are dynamically dispatching io via the Read/Write traits, but we are not doing that (we converted everything to static dispatch a while back, for better or for worse, and to avoid conflicts with the PCI branch, I'm not reverting that here). Signed-off-by: Patrick Roy --- src/vmm/src/device_manager/legacy.rs | 6 +++--- src/vmm/src/device_manager/mod.rs | 2 +- src/vmm/src/devices/legacy/serial.rs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vmm/src/device_manager/legacy.rs b/src/vmm/src/device_manager/legacy.rs index d0194e24e62..77173433d38 100644 --- a/src/vmm/src/device_manager/legacy.rs +++ b/src/vmm/src/device_manager/legacy.rs @@ -104,7 +104,7 @@ impl PortIODeviceManager { SerialEventsWrapper { buffer_ready_event_fd: None, }, - SerialOut::Sink(std::io::sink()), + SerialOut::Sink, ), input: None, })); @@ -114,7 +114,7 @@ impl PortIODeviceManager { SerialEventsWrapper { buffer_ready_event_fd: None, }, - SerialOut::Sink(std::io::sink()), + SerialOut::Sink, ), input: None, })); @@ -249,7 +249,7 @@ mod tests { SerialEventsWrapper { buffer_ready_event_fd: None, }, - SerialOut::Sink(std::io::sink()), + SerialOut::Sink, ), input: None, })), diff --git a/src/vmm/src/device_manager/mod.rs b/src/vmm/src/device_manager/mod.rs index c7f6acabfe1..98069e956d5 100644 --- a/src/vmm/src/device_manager/mod.rs +++ b/src/vmm/src/device_manager/mod.rs @@ -555,7 +555,7 @@ pub(crate) mod tests { #[cfg(target_arch = "x86_64")] let legacy_devices = PortIODeviceManager::new( Arc::new(Mutex::new( - SerialDevice::new(None, SerialOut::Sink(std::io::sink())).unwrap(), + SerialDevice::new(None, SerialOut::Sink).unwrap(), )), Arc::new(Mutex::new( I8042Device::new(EventFd::new(libc::EFD_NONBLOCK).unwrap()).unwrap(), diff --git a/src/vmm/src/devices/legacy/serial.rs b/src/vmm/src/devices/legacy/serial.rs index afc47189c1e..17d50566a77 100644 --- a/src/vmm/src/devices/legacy/serial.rs +++ b/src/vmm/src/devices/legacy/serial.rs @@ -127,19 +127,19 @@ impl SerialEvents for SerialEventsWrapper { #[derive(Debug)] pub enum SerialOut { - Sink(std::io::Sink), + Sink, Stdout(std::io::Stdout), } impl std::io::Write for SerialOut { fn write(&mut self, buf: &[u8]) -> std::io::Result { match self { - Self::Sink(sink) => sink.write(buf), + Self::Sink => Ok(buf.len()), Self::Stdout(stdout) => stdout.write(buf), } } fn flush(&mut self) -> std::io::Result<()> { match self { - Self::Sink(sink) => sink.flush(), + Self::Sink => Ok(()), Self::Stdout(stdout) => stdout.flush(), } } @@ -407,7 +407,7 @@ mod tests { SerialEventsWrapper { buffer_ready_event_fd: None, }, - SerialOut::Sink(std::io::sink()), + SerialOut::Sink, ), input: None::, }; From 4d384cd2ee56daac33ea44369f22a7f7d8504db5 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Wed, 6 Aug 2025 15:11:10 +0100 Subject: [PATCH 02/13] refactor: clean up "event_observer" logic The `event_observer` field in the Vmm struct is of type Option. There are two problems 1. With the way the code is written, it will never be `None` 2. `Stdin` is a singleton, there is no need to store it _anywhere_. With that in mind, we can just remove this field, and update its two uses to just directly operate on std::io::stdin(). Since it never `None', we can also remove the logic that matches and handles the `None` case. Furthermore, the `Drop` impl used to print the same error message twice in case resetting stdin to canonical mode failed, so fix that to only print it once. Signed-off-by: Patrick Roy --- src/vmm/src/builder.rs | 3 --- src/vmm/src/lib.rs | 33 +++++++++++---------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 88d7f56cb4e..87686de961d 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -272,7 +272,6 @@ pub fn build_microvm_for_boot( )?; let vmm = Vmm { - events_observer: Some(std::io::stdin()), instance_info: instance_info.clone(), shutdown_exit_code: None, kvm, @@ -473,7 +472,6 @@ pub fn build_microvm_from_snapshot( DeviceManager::restore(device_ctor_args, µvm_state.device_states)?; let mut vmm = Vmm { - events_observer: Some(std::io::stdin()), instance_info: instance_info.clone(), shutdown_exit_code: None, kvm, @@ -722,7 +720,6 @@ pub(crate) mod tests { let (_, vcpus_exit_evt) = vm.create_vcpus(1).unwrap(); Vmm { - events_observer: Some(std::io::stdin()), instance_info: InstanceInfo::default(), shutdown_exit_code: None, kvm, diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index 7bb33411b7e..4bb0a064799 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -245,10 +245,6 @@ pub enum VmmError { Vm(#[from] vstate::vm::VmError), /// Kvm error: {0} Kvm(#[from] vstate::kvm::KvmError), - /// Error thrown by observer object on Vmm initialization: {0} - VmmObserverInit(vmm_sys_util::errno::Error), - /// Error thrown by observer object on Vmm teardown: {0} - VmmObserverTeardown(vmm_sys_util::errno::Error), /// VMGenID error: {0} VMGenID(#[from] VmGenIdError), /// Failed perform action on device: {0} @@ -293,7 +289,6 @@ pub enum DumpCpuConfigError { /// Contains the state and associated methods required for the Firecracker VMM. #[derive(Debug)] pub struct Vmm { - events_observer: Option, /// The [`InstanceInfo`] state of this [`Vmm`]. pub instance_info: InstanceInfo, shutdown_exit_code: Option, @@ -343,17 +338,16 @@ impl Vmm { let vcpu_count = vcpus.len(); let barrier = Arc::new(Barrier::new(vcpu_count + 1)); - if let Some(stdin) = self.events_observer.as_mut() { - // Set raw mode for stdin. - stdin.lock().set_raw_mode().inspect_err(|&err| { - warn!("Cannot set raw mode for the terminal. {:?}", err); - })?; + let stdin = std::io::stdin().lock(); + // Set raw mode for stdin. + stdin.set_raw_mode().inspect_err(|&err| { + warn!("Cannot set raw mode for the terminal. {:?}", err); + })?; - // Set non blocking stdin. - stdin.lock().set_non_block(true).inspect_err(|&err| { - warn!("Cannot set non block for the terminal. {:?}", err); - })?; - } + // Set non blocking stdin. + stdin.set_non_block(true).inspect_err(|&err| { + warn!("Cannot set non block for the terminal. {:?}", err); + })?; self.vcpus_handles.reserve(vcpu_count); @@ -760,13 +754,8 @@ impl Drop for Vmm { // has already been stopped by the event manager at this point. self.stop(self.shutdown_exit_code.unwrap_or(FcExitCode::Ok)); - if let Some(observer) = self.events_observer.as_mut() { - let res = observer.lock().set_canon_mode().inspect_err(|&err| { - warn!("Cannot set canonical mode for the terminal. {:?}", err); - }); - if let Err(err) = res { - warn!("{}", VmmError::VmmObserverTeardown(err)); - } + if let Err(err) = std::io::stdin().lock().set_canon_mode() { + warn!("Cannot set canonical mode for the terminal. {:?}", err); } // Write the metrics before exiting. From fc4b7dd66b60651cf668bc261ba726675f763f7e Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 10:21:09 +0100 Subject: [PATCH 03/13] refactor: call set_stdout_nonblocking() inside setup_serial_device() We always called it right before setup_serial_device(), so just move the call inside. This will also be helpful once setup_serial_device() can setup the serial device to print to a file, where we dont need to mess with stdout in the first place. Signed-off-by: Patrick Roy --- src/vmm/src/device_manager/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vmm/src/device_manager/mod.rs b/src/vmm/src/device_manager/mod.rs index 98069e956d5..34947307763 100644 --- a/src/vmm/src/device_manager/mod.rs +++ b/src/vmm/src/device_manager/mod.rs @@ -126,6 +126,8 @@ impl DeviceManager { fn setup_serial_device( event_manager: &mut EventManager, ) -> Result>, std::io::Error> { + Self::set_stdout_nonblocking(); + let serial = Arc::new(Mutex::new(SerialDevice::new( Some(std::io::stdin()), SerialOut::Stdout(std::io::stdout()), @@ -140,8 +142,6 @@ impl DeviceManager { vcpus_exit_evt: &EventFd, vm: &Vm, ) -> Result { - Self::set_stdout_nonblocking(); - // Create serial device let serial = Self::setup_serial_device(event_manager)?; let reset_evt = vcpus_exit_evt @@ -253,8 +253,6 @@ impl DeviceManager { .contains("console="); if cmdline_contains_console { - // Make stdout non-blocking. - Self::set_stdout_nonblocking(); let serial = Self::setup_serial_device(event_manager)?; self.mmio_devices.register_mmio_serial(vm, serial, None)?; self.mmio_devices.add_mmio_serial_to_cmdline(cmdline)?; From 3840aef8765c2e67edd1262063e967fbb85e7c6d Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 13:21:45 +0100 Subject: [PATCH 04/13] refactor: use setup_serial_device() on aarch64 restoration path Have teh aarch64 serial device restoration path use the common serial device initialization code in setup_serial_device(). The only functional change here is that now we do set_stdou_nonblocking() on the restore path, although I would wager that not doing so in the past was actually an oversight, because we do it for all booted VMs, and for restored VMs on x86. Signed-off-by: Patrick Roy --- src/vmm/src/device_manager/persist.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/vmm/src/device_manager/persist.rs b/src/vmm/src/device_manager/persist.rs index 74e71f3a6bf..757318d88a3 100644 --- a/src/vmm/src/device_manager/persist.rs +++ b/src/vmm/src/device_manager/persist.rs @@ -16,9 +16,7 @@ use super::mmio::*; use crate::arch::DeviceType; use crate::devices::acpi::vmgenid::{VMGenIDState, VMGenIdConstructorArgs, VmGenId, VmGenIdError}; #[cfg(target_arch = "aarch64")] -use crate::devices::legacy::serial::SerialOut; -#[cfg(target_arch = "aarch64")] -use crate::devices::legacy::{RTCDevice, SerialDevice}; +use crate::devices::legacy::RTCDevice; use crate::devices::virtio::balloon::persist::{BalloonConstructorArgs, BalloonState}; use crate::devices::virtio::balloon::{Balloon, BalloonError}; use crate::devices::virtio::block::BlockError; @@ -358,13 +356,8 @@ impl<'a> Persist<'a> for MMIODeviceManager { { for state in &state.legacy_devices { if state.type_ == DeviceType::Serial { - let serial = Arc::new(Mutex::new(SerialDevice::new( - Some(std::io::stdin()), - SerialOut::Stdout(std::io::stdout()), - )?)); - constructor_args - .event_manager - .add_subscriber(serial.clone()); + let serial = + crate::DeviceManager::setup_serial_device(constructor_args.event_manager)?; dev_manager.register_mmio_serial(vm, serial, Some(state.device_info))?; } From f0f6766efde1955989b3f27a9245bd2e4e3fd714 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 13:39:38 +0100 Subject: [PATCH 05/13] do not open log file for reading we only write to it, so no need to pass .read(true). Signed-off-by: Patrick Roy --- src/vmm/src/logger/logging.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vmm/src/logger/logging.rs b/src/vmm/src/logger/logging.rs index e5cdd8a33d3..21ede373991 100644 --- a/src/vmm/src/logger/logging.rs +++ b/src/vmm/src/logger/logging.rs @@ -64,7 +64,6 @@ impl Logger { if let Some(log_path) = config.log_path { let file = std::fs::OpenOptions::new() .custom_flags(libc::O_NONBLOCK) - .read(true) .write(true) .open(log_path) .map_err(LoggerUpdateError)?; From e3957ac6a659bafa0b3ec71a555d661f7427c347 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Wed, 6 Aug 2025 16:23:21 +0100 Subject: [PATCH 06/13] allow redirecting guest serial output to a file Add a new field, `serial_out_path`, to the logger configuration (available both via API and CLI) which can be set to the path of a file into which the output of the guest's serial console should be dumped. Have the file behave identically to our log file (e.g. it must already exist, Firecracker does not create it). If we redirect serial output to a log file, disable serial input via stdin. Signed-off-by: Patrick Roy --- CHANGELOG.md | 6 +++ .../src/api_server/parsed_request.rs | 2 + src/firecracker/src/api_server/request/mod.rs | 1 + .../src/api_server/request/serial.rs | 39 +++++++++++++++++++ src/firecracker/swagger/firecracker.yaml | 30 ++++++++++++++ src/vmm/src/arch/aarch64/fdt.rs | 2 +- src/vmm/src/builder.rs | 14 ++++++- src/vmm/src/device_manager/mod.rs | 39 ++++++++++++++----- src/vmm/src/device_manager/persist.rs | 6 ++- src/vmm/src/devices/legacy/serial.rs | 4 ++ src/vmm/src/logger/metrics.rs | 6 +++ src/vmm/src/resources.rs | 12 ++++++ src/vmm/src/rpc_interface.rs | 8 ++++ src/vmm/src/vmm_config/mod.rs | 1 + src/vmm/src/vmm_config/serial.rs | 14 +++++++ tests/host_tools/fcmetrics.py | 2 + 16 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 src/firecracker/src/api_server/request/serial.rs create mode 100644 src/vmm/src/vmm_config/serial.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 231891ca92b..e7eb43cf986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,12 @@ and this project adheres to requests to `/mmds/config` to enforce MMDS to always respond plain text contents in the IMDS format regardless of the `Accept` header in requests. Users need to regenerate snapshots. +- [#5350](https://github.com/firecracker-microvm/firecracker/pull/5350): Added a + `/serial` endpoint, which allows setting `serial_out_path` to the path of a + pre-created file into which Firecracker should redirect output from the + guest's serial console. Not configuring it means Firecracker will continue to + print serial output to stdout. Similarly to the logger, this configuration is + not persisted across snapshots. ### Changed diff --git a/src/firecracker/src/api_server/parsed_request.rs b/src/firecracker/src/api_server/parsed_request.rs index 10d5c3d97ea..216e7c7fcf2 100644 --- a/src/firecracker/src/api_server/parsed_request.rs +++ b/src/firecracker/src/api_server/parsed_request.rs @@ -27,6 +27,7 @@ use super::request::net::{parse_patch_net, parse_put_net}; use super::request::snapshot::{parse_patch_vm_state, parse_put_snapshot}; use super::request::version::parse_get_version; use super::request::vsock::parse_put_vsock; +use crate::api_server::request::serial::parse_put_serial; #[derive(Debug)] pub(crate) enum RequestAction { @@ -90,6 +91,7 @@ impl TryFrom<&Request> for ParsedRequest { (Method::Put, "cpu-config", Some(body)) => parse_put_cpu_config(body), (Method::Put, "drives", Some(body)) => parse_put_drive(body, path_tokens.next()), (Method::Put, "logger", Some(body)) => parse_put_logger(body), + (Method::Put, "serial", Some(body)) => parse_put_serial(body), (Method::Put, "machine-config", Some(body)) => parse_put_machine_config(body), (Method::Put, "metrics", Some(body)) => parse_put_metrics(body), (Method::Put, "mmds", Some(body)) => parse_put_mmds(body, path_tokens.next()), diff --git a/src/firecracker/src/api_server/request/mod.rs b/src/firecracker/src/api_server/request/mod.rs index 0c1622798f4..a406842d0a6 100644 --- a/src/firecracker/src/api_server/request/mod.rs +++ b/src/firecracker/src/api_server/request/mod.rs @@ -13,6 +13,7 @@ pub mod machine_configuration; pub mod metrics; pub mod mmds; pub mod net; +pub mod serial; pub mod snapshot; pub mod version; pub mod vsock; diff --git a/src/firecracker/src/api_server/request/serial.rs b/src/firecracker/src/api_server/request/serial.rs new file mode 100644 index 00000000000..a75895cece2 --- /dev/null +++ b/src/firecracker/src/api_server/request/serial.rs @@ -0,0 +1,39 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use micro_http::Body; +use vmm::logger::{IncMetric, METRICS}; +use vmm::rpc_interface::VmmAction; +use vmm::vmm_config::serial::SerialConfig; + +use crate::api_server::parsed_request::{ParsedRequest, RequestError}; + +pub(crate) fn parse_put_serial(body: &Body) -> Result { + METRICS.put_api_requests.serial_count.inc(); + let res = serde_json::from_slice::(body.raw()); + let config = res.inspect_err(|_| { + METRICS.put_api_requests.serial_fails.inc(); + })?; + Ok(ParsedRequest::new_sync(VmmAction::ConfigureSerial(config))) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + use crate::api_server::parsed_request::tests::vmm_action_from_request; + + #[test] + fn test_parse_put_serial_request() { + let body = r#"{"serial_out_path": "serial"}"#; + + let expected_config = SerialConfig { + serial_out_path: Some(PathBuf::from("serial")), + }; + assert_eq!( + vmm_action_from_request(parse_put_serial(&Body::new(body)).unwrap()), + VmmAction::ConfigureSerial(expected_config) + ); + } +} diff --git a/src/firecracker/swagger/firecracker.yaml b/src/firecracker/swagger/firecracker.yaml index 5a101ca204b..3b1a1869239 100644 --- a/src/firecracker/swagger/firecracker.yaml +++ b/src/firecracker/swagger/firecracker.yaml @@ -506,6 +506,27 @@ paths: schema: $ref: "#/definitions/Error" + /serial: + put: + summary: Configures the serial console + operationId: putSerialDevice + description: + Configure the serial console, which the guest can write its kernel logs to. Has no effect if + the serial console is not also enabled on the guest kernel command line + parameters: + - name: body + in: body + description: Serial console properties + required: true + schema: + $ref: "#/definitions/SerialDevice" + responses: + 204: + description: Serial device configured + default: + description: Internal server error + schema: + $ref: "#/definitions/Error" /network-interfaces/{iface_id}: put: @@ -1334,6 +1355,15 @@ definitions: rate_limiter: $ref: "#/definitions/RateLimiter" + SerialDevice: + type: object + description: + The configuration of the serial device + properties: + output_path: + type: string + description: Path to a file or named pipe on the host to which serial output should be written. + FirecrackerVersion: type: object description: diff --git a/src/vmm/src/arch/aarch64/fdt.rs b/src/vmm/src/arch/aarch64/fdt.rs index 6a50c0257a9..9946d3516cc 100644 --- a/src/vmm/src/arch/aarch64/fdt.rs +++ b/src/vmm/src/arch/aarch64/fdt.rs @@ -561,7 +561,7 @@ mod tests { cmdline.insert("console", "/dev/tty0").unwrap(); device_manager - .attach_legacy_devices_aarch64(&vm, &mut event_manager, &mut cmdline) + .attach_legacy_devices_aarch64(&vm, &mut event_manager, &mut cmdline, None) .unwrap(); let dummy = Arc::new(Mutex::new(DummyDevice::new())); device_manager diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 87686de961d..b5231f90d8d 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -170,7 +170,12 @@ pub fn build_microvm_for_boot( let (mut vcpus, vcpus_exit_evt) = vm.create_vcpus(vm_resources.machine_config.vcpu_count)?; vm.register_memory_regions(guest_memory)?; - let mut device_manager = DeviceManager::new(event_manager, &vcpus_exit_evt, &vm)?; + let mut device_manager = DeviceManager::new( + event_manager, + &vcpus_exit_evt, + &vm, + vm_resources.serial_out_path.as_ref(), + )?; let vm = Arc::new(vm); @@ -248,7 +253,12 @@ pub fn build_microvm_for_boot( } #[cfg(target_arch = "aarch64")] - device_manager.attach_legacy_devices_aarch64(&vm, event_manager, &mut boot_cmdline)?; + device_manager.attach_legacy_devices_aarch64( + &vm, + event_manager, + &mut boot_cmdline, + vm_resources.serial_out_path.as_ref(), + )?; device_manager.attach_vmgenid_device(vm.guest_memory(), &vm)?; diff --git a/src/vmm/src/device_manager/mod.rs b/src/vmm/src/device_manager/mod.rs index 34947307763..5cb8a73c2ec 100644 --- a/src/vmm/src/device_manager/mod.rs +++ b/src/vmm/src/device_manager/mod.rs @@ -7,6 +7,8 @@ use std::convert::Infallible; use std::fmt::Debug; +use std::os::unix::prelude::OpenOptionsExt; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use acpi::ACPIDeviceManager; @@ -125,13 +127,25 @@ impl DeviceManager { /// Sets up the serial device. fn setup_serial_device( event_manager: &mut EventManager, + output: Option<&PathBuf>, ) -> Result>, std::io::Error> { - Self::set_stdout_nonblocking(); + let (serial_in, serial_out) = match output { + Some(ref path) => ( + None, + std::fs::OpenOptions::new() + .custom_flags(libc::O_NONBLOCK) + .write(true) + .open(path) + .map(SerialOut::File)?, + ), + None => { + Self::set_stdout_nonblocking(); + + (Some(std::io::stdin()), SerialOut::Stdout(std::io::stdout())) + } + }; - let serial = Arc::new(Mutex::new(SerialDevice::new( - Some(std::io::stdin()), - SerialOut::Stdout(std::io::stdout()), - )?)); + let serial = Arc::new(Mutex::new(SerialDevice::new(serial_in, serial_out)?)); event_manager.add_subscriber(serial.clone()); Ok(serial) } @@ -141,9 +155,10 @@ impl DeviceManager { event_manager: &mut EventManager, vcpus_exit_evt: &EventFd, vm: &Vm, + serial_output: Option<&PathBuf>, ) -> Result { // Create serial device - let serial = Self::setup_serial_device(event_manager)?; + let serial = Self::setup_serial_device(event_manager, serial_output)?; let reset_evt = vcpus_exit_evt .try_clone() .map_err(DeviceManagerCreateError::EventFd)?; @@ -161,9 +176,11 @@ impl DeviceManager { event_manager: &mut EventManager, vcpus_exit_evt: &EventFd, vm: &Vm, + serial_output: Option<&PathBuf>, ) -> Result { #[cfg(target_arch = "x86_64")] - let legacy_devices = Self::create_legacy_devices(event_manager, vcpus_exit_evt, vm)?; + let legacy_devices = + Self::create_legacy_devices(event_manager, vcpus_exit_evt, vm, serial_output)?; Ok(DeviceManager { mmio_devices: MMIODeviceManager::new(), @@ -243,6 +260,7 @@ impl DeviceManager { vm: &Vm, event_manager: &mut EventManager, cmdline: &mut Cmdline, + serial_out_path: Option<&PathBuf>, ) -> Result<(), AttachDeviceError> { // Serial device setup. let cmdline_contains_console = cmdline @@ -253,7 +271,7 @@ impl DeviceManager { .contains("console="); if cmdline_contains_console { - let serial = Self::setup_serial_device(event_manager)?; + let serial = Self::setup_serial_device(event_manager, serial_out_path)?; self.mmio_devices.register_mmio_serial(vm, serial, None)?; self.mmio_devices.add_mmio_serial_to_cmdline(cmdline)?; } @@ -451,6 +469,7 @@ impl<'a> Persist<'a> for DeviceManager { constructor_args.event_manager, constructor_args.vcpus_exit_evt, constructor_args.vm, + constructor_args.vm_resources.serial_out_path.as_ref(), )?; // Restore MMIO devices @@ -580,7 +599,7 @@ pub(crate) mod tests { let mut cmdline = Cmdline::new(4096).unwrap(); let mut event_manager = EventManager::new().unwrap(); vmm.device_manager - .attach_legacy_devices_aarch64(&vmm.vm, &mut event_manager, &mut cmdline) + .attach_legacy_devices_aarch64(&vmm.vm, &mut event_manager, &mut cmdline, None) .unwrap(); assert!(vmm.device_manager.mmio_devices.rtc.is_some()); assert!(vmm.device_manager.mmio_devices.serial.is_none()); @@ -588,7 +607,7 @@ pub(crate) mod tests { let mut vmm = default_vmm(); cmdline.insert("console", "/dev/blah").unwrap(); vmm.device_manager - .attach_legacy_devices_aarch64(&vmm.vm, &mut event_manager, &mut cmdline) + .attach_legacy_devices_aarch64(&vmm.vm, &mut event_manager, &mut cmdline, None) .unwrap(); assert!(vmm.device_manager.mmio_devices.rtc.is_some()); assert!(vmm.device_manager.mmio_devices.serial.is_some()); diff --git a/src/vmm/src/device_manager/persist.rs b/src/vmm/src/device_manager/persist.rs index 757318d88a3..7c8320c45d3 100644 --- a/src/vmm/src/device_manager/persist.rs +++ b/src/vmm/src/device_manager/persist.rs @@ -356,8 +356,10 @@ impl<'a> Persist<'a> for MMIODeviceManager { { for state in &state.legacy_devices { if state.type_ == DeviceType::Serial { - let serial = - crate::DeviceManager::setup_serial_device(constructor_args.event_manager)?; + let serial = crate::DeviceManager::setup_serial_device( + constructor_args.event_manager, + constructor_args.vm_resources.serial_out_path.as_ref(), + )?; dev_manager.register_mmio_serial(vm, serial, Some(state.device_info))?; } diff --git a/src/vmm/src/devices/legacy/serial.rs b/src/vmm/src/devices/legacy/serial.rs index 17d50566a77..83e84a7bf56 100644 --- a/src/vmm/src/devices/legacy/serial.rs +++ b/src/vmm/src/devices/legacy/serial.rs @@ -7,6 +7,7 @@ //! Implements a wrapper over an UART serial device. use std::fmt::Debug; +use std::fs::File; use std::io::{self, Read, Stdin, Write}; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::{Arc, Barrier}; @@ -129,18 +130,21 @@ impl SerialEvents for SerialEventsWrapper { pub enum SerialOut { Sink, Stdout(std::io::Stdout), + File(File), } impl std::io::Write for SerialOut { fn write(&mut self, buf: &[u8]) -> std::io::Result { match self { Self::Sink => Ok(buf.len()), Self::Stdout(stdout) => stdout.write(buf), + Self::File(file) => file.write(buf), } } fn flush(&mut self) -> std::io::Result<()> { match self { Self::Sink => Ok(()), Self::Stdout(stdout) => stdout.flush(), + Self::File(file) => file.flush(), } } } diff --git a/src/vmm/src/logger/metrics.rs b/src/vmm/src/logger/metrics.rs index f03d196d5fa..d5098cbf748 100644 --- a/src/vmm/src/logger/metrics.rs +++ b/src/vmm/src/logger/metrics.rs @@ -421,6 +421,10 @@ pub struct PutRequestsMetrics { pub vsock_count: SharedIncMetric, /// Number of failures in creating a vsock device. pub vsock_fails: SharedIncMetric, + /// Number of PUTs to /serial + pub serial_count: SharedIncMetric, + /// Number of failed PUTs to /serial + pub serial_fails: SharedIncMetric, } impl PutRequestsMetrics { /// Const default construction. @@ -446,6 +450,8 @@ impl PutRequestsMetrics { mmds_fails: SharedIncMetric::new(), vsock_count: SharedIncMetric::new(), vsock_fails: SharedIncMetric::new(), + serial_count: SharedIncMetric::new(), + serial_fails: SharedIncMetric::new(), } } } diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index d29f76740fc..911ee5e5c99 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -28,6 +28,7 @@ use crate::vmm_config::machine_config::{ use crate::vmm_config::metrics::{MetricsConfig, MetricsConfigError, init_metrics}; use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError}; use crate::vmm_config::net::*; +use crate::vmm_config::serial::SerialConfig; use crate::vmm_config::vsock::*; use crate::vstate::memory; use crate::vstate::memory::{GuestRegionMmap, MemoryError}; @@ -86,6 +87,8 @@ pub struct VmmConfig { network_interfaces: Vec, vsock: Option, entropy: Option, + #[serde(skip)] + serial_config: Option, } /// A data structure that encapsulates the device configurations @@ -116,6 +119,8 @@ pub struct VmResources { pub boot_timer: bool, /// Whether or not to use PCIe transport for VirtIO devices. pub pci_enabled: bool, + /// Where serial console output should be written to + pub serial_out_path: Option, } impl VmResources { @@ -193,6 +198,10 @@ impl VmResources { resources.build_entropy_device(entropy_device_config)?; } + if let Some(serial_cfg) = vmm_config.serial_config { + resources.serial_out_path = serial_cfg.serial_out_path; + } + Ok(resources) } @@ -506,6 +515,8 @@ impl From<&VmResources> for VmmConfig { network_interfaces: resources.net_builder.configs(), vsock: resources.vsock.config(), entropy: resources.entropy.config(), + // serial_config is marked serde(skip) so that it doesnt end up in snapshots. + serial_config: None, } } } @@ -617,6 +628,7 @@ mod tests { mmds_size_limit: HTTP_MAX_PAYLOAD_SIZE, entropy: Default::default(), pci_enabled: false, + serial_out_path: None, } } diff --git a/src/vmm/src/rpc_interface.rs b/src/vmm/src/rpc_interface.rs index d26b1ba877d..85aab1af826 100644 --- a/src/vmm/src/rpc_interface.rs +++ b/src/vmm/src/rpc_interface.rs @@ -33,6 +33,7 @@ use crate::vmm_config::mmds::{MmdsConfig, MmdsConfigError}; use crate::vmm_config::net::{ NetworkInterfaceConfig, NetworkInterfaceError, NetworkInterfaceUpdateConfig, }; +use crate::vmm_config::serial::SerialConfig; use crate::vmm_config::snapshot::{CreateSnapshotParams, LoadSnapshotParams, SnapshotType}; use crate::vmm_config::vsock::{VsockConfigError, VsockDeviceConfig}; use crate::vmm_config::{self, RateLimiterUpdate}; @@ -50,6 +51,8 @@ pub enum VmmAction { /// Configure the metrics using as input the `MetricsConfig`. This action can only be called /// before the microVM has booted. ConfigureMetrics(MetricsConfig), + /// Configure the serial device. This action can only be called before the microVM has booted. + ConfigureSerial(SerialConfig), /// Create a snapshot using as input the `CreateSnapshotParams`. This action can only be called /// after the microVM has booted and only when the microVM is in `Paused` state. CreateSnapshot(CreateSnapshotParams), @@ -408,6 +411,10 @@ impl<'a> PrebootApiController<'a> { ConfigureMetrics(metrics_cfg) => vmm_config::metrics::init_metrics(metrics_cfg) .map(|()| VmmData::Empty) .map_err(VmmActionError::Metrics), + ConfigureSerial(serial_cfg) => { + self.vm_resources.serial_out_path = serial_cfg.serial_out_path; + Ok(VmmData::Empty) + } GetBalloonConfig => self.balloon_config(), GetFullVmConfig => { warn!( @@ -676,6 +683,7 @@ impl RuntimeApiController { ConfigureBootSource(_) | ConfigureLogger(_) | ConfigureMetrics(_) + | ConfigureSerial(_) | InsertBlockDevice(_) | InsertNetworkDevice(_) | LoadSnapshot(_) diff --git a/src/vmm/src/vmm_config/mod.rs b/src/vmm/src/vmm_config/mod.rs index c7afc5fc65f..0e244ad4328 100644 --- a/src/vmm/src/vmm_config/mod.rs +++ b/src/vmm/src/vmm_config/mod.rs @@ -31,6 +31,7 @@ pub mod mmds; /// Wrapper for configuring the network devices attached to the microVM. pub mod net; /// Wrapper for configuring microVM snapshots and the microVM state. +pub mod serial; pub mod snapshot; /// Wrapper for configuring the vsock devices attached to the microVM. pub mod vsock; diff --git a/src/vmm/src/vmm_config/serial.rs b/src/vmm/src/vmm_config/serial.rs new file mode 100644 index 00000000000..0433df95940 --- /dev/null +++ b/src/vmm/src/vmm_config/serial.rs @@ -0,0 +1,14 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::path::PathBuf; + +use serde::Deserialize; + +/// The body of a PUT /serial request. +#[derive(Debug, PartialEq, Eq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SerialConfig { + /// Named pipe or file used as output for guest serial console. + pub serial_out_path: Option, +} diff --git a/tests/host_tools/fcmetrics.py b/tests/host_tools/fcmetrics.py index 1b3cdcb96b1..e2a1862c21f 100644 --- a/tests/host_tools/fcmetrics.py +++ b/tests/host_tools/fcmetrics.py @@ -227,6 +227,8 @@ def validate_fc_metrics(metrics): "mmds_fails", "vsock_count", "vsock_fails", + "serial_count", + "serial_fails", ], "seccomp": [ "num_faults", From 86e75182f82bf33ba81ef8da2773f0284e3c556e Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Mon, 4 Aug 2025 16:07:00 +0100 Subject: [PATCH 07/13] refactor(test): capture screen log via file copy Copy the screen log file (which contains firecracker's stdout/guest dmesg if non-daemonized / no log file specified) to the test_results directory, to be included in artifacts, instead of explicitly reading one file and subsequently writing another file in Python code. Signed-off-by: Patrick Roy --- tests/conftest.py | 5 ++--- tests/framework/microvm.py | 10 ---------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 96ee285d192..ab485a3da46 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -384,6 +384,8 @@ def microvm_factory(request, record_property, results_dir, netns_factory): utils.run_cmd(["dmesg", "-dPx"]).stdout ) shutil.copy(f"/firecracker/build/img/{platform.machine()}/id_rsa", uvm_data) + if Path(uvm.screen_log).exists(): + shutil.copy(uvm.screen_log, uvm_data) uvm_root = Path(uvm.chroot()) for item in os.listdir(uvm_root): @@ -392,9 +394,6 @@ def microvm_factory(request, record_property, results_dir, netns_factory): continue dst = uvm_data / item shutil.copy(src, dst) - console_data = uvm.console_data - if console_data: - uvm_data.joinpath("guest-console.log").write_text(console_data) uvm_factory.kill() diff --git a/tests/framework/microvm.py b/tests/framework/microvm.py index 3c672e82e23..55e0aa15e65 100644 --- a/tests/framework/microvm.py +++ b/tests/framework/microvm.py @@ -463,16 +463,6 @@ def log_data(self): return "" return self.log_file.read_text() - @property - def console_data(self): - """Return the output of microVM's console""" - if self.screen_log is None: - return None - file = Path(self.screen_log) - if not file.exists(): - return None - return file.read_text(encoding="utf-8") - @property def state(self): """Get the InstanceInfo property and return the state field.""" From 49fe04cc9790946696581dd33cc2c6e289f33c27 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Mon, 4 Aug 2025 16:08:10 +0100 Subject: [PATCH 08/13] test: enable serial console in integration tests If SSH to guest is failing, we don't really get much useful in terms of logs in our ci artifacts. Enable serial console so that we get guest dmesg always, at least if we have the API server around (which is the case for the vast majority of tests). Since serial console is enabled by default now, this means we can stop explicitly passing modified bootargs in a bunch of tests now. Disable it for the tests that interact with the serial console via stdin/stdout (here we are not daemonizing, so we are capturing serial output anyway). Disable pylint's "too-many-statements" lint, because it started firing in microvm.spawn() Signed-off-by: Patrick Roy --- tests/framework/http_api.py | 1 + tests/framework/microvm.py | 29 ++++++++++++++----- .../integration_tests/functional/test_api.py | 4 ++- .../functional/test_api_server.py | 2 +- .../functional/test_cmd_line_parameters.py | 2 +- .../functional/test_cmd_line_start.py | 28 +++++++++--------- .../functional/test_kernel_cmdline.py | 2 +- .../functional/test_max_devices.py | 6 ++-- .../functional/test_serial_io.py | 15 ++++------ .../performance/test_initrd.py | 2 +- .../security/test_custom_seccomp.py | 2 +- tests/integration_tests/security/test_jail.py | 2 +- tests/pyproject.toml | 1 + 13 files changed, 54 insertions(+), 42 deletions(-) diff --git a/tests/framework/http_api.py b/tests/framework/http_api.py index 7534fe829d2..16990a2a927 100644 --- a/tests/framework/http_api.py +++ b/tests/framework/http_api.py @@ -132,3 +132,4 @@ def __init__(self, api_usocket_full_name, *, on_error=None): self.snapshot_load = Resource(self, "/snapshot/load") self.cpu_config = Resource(self, "/cpu-config") self.entropy = Resource(self, "/entropy") + self.serial = Resource(self, "/serial") diff --git a/tests/framework/microvm.py b/tests/framework/microvm.py index 55e0aa15e65..03f90905843 100644 --- a/tests/framework/microvm.py +++ b/tests/framework/microvm.py @@ -259,6 +259,7 @@ def __init__( self.api = None self.log_file = None + self.serial_out_path = None self.metrics_file = None self._spawned = False self._killed = False @@ -624,6 +625,7 @@ def add_pre_cmd(self, pre_cmd): def spawn( self, log_file="fc.log", + serial_out_path="serial.log", log_level="Debug", log_show_level=False, log_show_origin=False, @@ -654,6 +656,11 @@ def spawn( if log_show_origin: self.jailer.extra_args["show-log-origin"] = None + if serial_out_path is not None: + self.serial_out_path = Path(self.path) / serial_out_path + self.serial_out_path.touch() + self.create_jailed_resource(self.serial_out_path) + if metrics_path is not None: self.metrics_file = Path(self.path) / metrics_path self.metrics_file.touch() @@ -718,13 +725,18 @@ def spawn( # Firecracker process itself at least came up by checking # for the startup log message. Otherwise, you're on your own kid. if "config-file" in self.jailer.extra_args and self.iface: + assert not serial_out_path self.wait_for_ssh_up() elif "no-api" not in self.jailer.extra_args: if self.log_file and log_level in ("Trace", "Debug", "Info"): self.check_log_message("API server started.") else: self._wait_for_api_socket() + + if serial_out_path is not None: + self.api.serial.put(serial_out_path=serial_out_path) elif self.log_file and log_level in ("Trace", "Debug", "Info"): + assert not serial_out_path self.check_log_message("Running Firecracker") @retry(wait=wait_fixed(0.2), stop=stop_after_attempt(5), reraise=True) @@ -795,12 +807,9 @@ def basic_config( The function checks the response status code and asserts that the response is within the interval [200, 300). - If boot_args is None, the default boot_args in Firecracker is - reboot=k panic=1 nomodule 8250.nr_uarts=0 i8042.noaux i8042.nomux - i8042.nopnp i8042.dumbkbd swiotlb=noforce - - if PCI is disabled, Firecracker also passes to the guest pci=off - + If boot_args is None, the default boot_args used in tests is + reboot=k panic=1 nomodule swiotlb=noforce console=ttyS0 [pci=off] + which differs from Firecracker's default only in the enabling of the serial console. Reference: file:../../src/vmm/src/vmm_config/boot_source.rs::DEFAULT_KERNEL_CMDLINE """ self.api.machine_config.put( @@ -824,6 +833,10 @@ def basic_config( if boot_args is not None: self.boot_args = boot_args + else: + self.boot_args = "reboot=k panic=1 nomodule swiotlb=noforce console=ttyS0" + if not self.pci_enabled: + self.boot_args += " pci=off" boot_source_args = { "kernel_image_path": self.create_jailed_resource(self.kernel_file), "boot_args": self.boot_args, @@ -1313,9 +1326,9 @@ def open(self): time.sleep(0.2) attempt += 1 - screen_log_fd = os.open(self._vm.screen_log, os.O_RDONLY) + serial_log_fd = os.open(self._vm.screen_log, os.O_RDONLY) self._poller = select.poll() - self._poller.register(screen_log_fd, select.POLLIN | select.POLLHUP) + self._poller.register(serial_log_fd, select.POLLIN | select.POLLHUP) def tx(self, input_string, end="\n"): # pylint: disable=invalid-name diff --git a/tests/integration_tests/functional/test_api.py b/tests/integration_tests/functional/test_api.py index 55bb15d5eb4..32527e5c905 100644 --- a/tests/integration_tests/functional/test_api.py +++ b/tests/integration_tests/functional/test_api.py @@ -1136,8 +1136,10 @@ def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_nano): expected_cfg["boot-source"] = { "kernel_image_path": uvm_nano.get_jailed_resource(uvm_nano.kernel_file), "initrd_path": None, - "boot_args": None, + "boot_args": "reboot=k panic=1 nomodule swiotlb=noforce console=ttyS0", } + if not uvm_nano.pci_enabled: + expected_cfg["boot-source"]["boot_args"] += " pci=off" # no ipv4_address or imds_compat specified during PUT /mmds/config so we expect the default expected_cfg["mmds-config"] = { diff --git a/tests/integration_tests/functional/test_api_server.py b/tests/integration_tests/functional/test_api_server.py index b502dff7e6d..1df880c7f28 100644 --- a/tests/integration_tests/functional/test_api_server.py +++ b/tests/integration_tests/functional/test_api_server.py @@ -23,7 +23,7 @@ def test_api_socket_in_use(uvm_plain): sock = socket.socket(socket.AF_UNIX) sock.bind(microvm.jailer.api_socket_path()) - microvm.spawn(log_level="warn") + microvm.spawn(log_level="warn", serial_out_path=None) msg = "Failed to open the API socket at: /run/firecracker.socket. Check that it is not already used." microvm.check_log_message(msg) diff --git a/tests/integration_tests/functional/test_cmd_line_parameters.py b/tests/integration_tests/functional/test_cmd_line_parameters.py index 9c2c79d9552..59eebf5d42e 100644 --- a/tests/integration_tests/functional/test_cmd_line_parameters.py +++ b/tests/integration_tests/functional/test_cmd_line_parameters.py @@ -29,7 +29,7 @@ def test_describe_snapshot_all_versions( jailer_binary_path=firecracker_release.jailer, ) # FIXME: Once only FC versions >= 1.12 are supported, drop log_level="warn" - vm.spawn(log_level="warn") + vm.spawn(log_level="warn", serial_out_path=None) vm.basic_config(track_dirty_pages=True) vm.start() snapshot = vm.snapshot_diff() diff --git a/tests/integration_tests/functional/test_cmd_line_start.py b/tests/integration_tests/functional/test_cmd_line_start.py index d4c6c270b8d..3d45fa9d694 100644 --- a/tests/integration_tests/functional/test_cmd_line_start.py +++ b/tests/integration_tests/functional/test_cmd_line_start.py @@ -117,7 +117,7 @@ def test_config_start_with_api(uvm_plain, vm_config_file): """ test_microvm = uvm_plain vm_config = _configure_vm_from_json(test_microvm, vm_config_file) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) assert test_microvm.state == "Running" @@ -134,7 +134,7 @@ def test_config_start_no_api(uvm_plain, vm_config_file): test_microvm = uvm_plain _configure_vm_from_json(test_microvm, vm_config_file) test_microvm.jailer.extra_args.update({"no-api": None}) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) # Get names of threads in Firecracker. cmd = f"ps -T --no-headers -p {test_microvm.firecracker_pid} | awk '{{print $5}}'" @@ -165,7 +165,7 @@ def test_config_start_no_api_exit(uvm_plain, vm_config_file): _configure_network_interface(test_microvm) test_microvm.jailer.extra_args.update({"no-api": None}) - test_microvm.spawn() # Start Firecracker and MicroVM + test_microvm.spawn(serial_out_path=None) # Start Firecracker and MicroVM test_microvm.ssh.run("reboot") # Exit test_microvm.mark_killed() # waits for process to terminate @@ -189,7 +189,7 @@ def test_config_bad_machine_config(uvm_plain, vm_config_file): test_microvm = uvm_plain _configure_vm_from_json(test_microvm, vm_config_file) test_microvm.jailer.extra_args.update({"no-api": None}) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) test_microvm.check_log_message("Configuration for VMM from one single json failed") test_microvm.mark_killed() @@ -215,7 +215,7 @@ def test_config_machine_config_params(uvm_plain, test_config): _configure_vm_from_json(test_microvm, vm_config_file) test_microvm.jailer.extra_args.update({"no-api": None}) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) should_fail = False if cpu_template_used and "C3" not in SUPPORTED_CPU_TEMPLATES: @@ -247,7 +247,7 @@ def test_config_start_with_limit(uvm_plain, vm_config_file): _configure_vm_from_json(test_microvm, vm_config_file) test_microvm.jailer.extra_args.update({"http-api-max-payload-size": "250"}) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) assert test_microvm.state == "Running" @@ -277,7 +277,7 @@ def test_config_with_default_limit(uvm_plain, vm_config_file): test_microvm = uvm_plain _configure_vm_from_json(test_microvm, vm_config_file) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) assert test_microvm.state == "Running" @@ -311,7 +311,7 @@ def test_start_with_metadata(uvm_plain): metadata_file = DIR / "metadata.json" _add_metadata_file(test_microvm, metadata_file) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) test_microvm.check_log_message("Successfully added metadata to mmds from file") @@ -332,7 +332,7 @@ def test_start_with_metadata_limit(uvm_plain): metadata_file = DIR / "metadata.json" _add_metadata_file(test_microvm, metadata_file) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) test_microvm.check_log_message( "Populating MMDS from file failed: The MMDS patch request doesn't fit." @@ -352,7 +352,7 @@ def test_start_with_metadata_default_limit(uvm_plain): _add_metadata_file(test_microvm, metadata_file) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) test_microvm.check_log_message( "Populating MMDS from file failed: The MMDS patch request doesn't fit." @@ -372,7 +372,7 @@ def test_start_with_missing_metadata(uvm_plain): test_microvm.metadata_file = vm_metadata_path try: - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) except: # pylint: disable=bare-except pass finally: @@ -395,7 +395,7 @@ def test_start_with_invalid_metadata(uvm_plain): test_microvm.metadata_file = vm_metadata_path try: - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) except: # pylint: disable=bare-except pass finally: @@ -418,7 +418,7 @@ def test_config_start_and_mmds_with_api(uvm_plain, vm_config_file): _configure_network_interface(test_microvm) # Network namespace has already been created. - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) data_store = { "latest": { @@ -480,7 +480,7 @@ def test_with_config_and_metadata_no_api(uvm_plain, vm_config_file, metadata_fil _add_metadata_file(test_microvm, metadata_file) _configure_network_interface(test_microvm) test_microvm.jailer.extra_args.update({"no-api": None}) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) # Get MMDS version and IPv4 address configured from the file. version, ipv4_address, imds_compat = _get_optional_fields_from_file(vm_config_file) diff --git a/tests/integration_tests/functional/test_kernel_cmdline.py b/tests/integration_tests/functional/test_kernel_cmdline.py index e4e4c122aa9..520fd88822e 100644 --- a/tests/integration_tests/functional/test_kernel_cmdline.py +++ b/tests/integration_tests/functional/test_kernel_cmdline.py @@ -14,7 +14,7 @@ def test_init_params(uvm_plain): """ vm = uvm_plain vm.help.enable_console() - vm.spawn() + vm.spawn(serial_out_path=None) vm.memory_monitor = None # We will override the init with /bin/cat so that we try to read the diff --git a/tests/integration_tests/functional/test_max_devices.py b/tests/integration_tests/functional/test_max_devices.py index 7cf9922c77b..54153b27d2d 100644 --- a/tests/integration_tests/functional/test_max_devices.py +++ b/tests/integration_tests/functional/test_max_devices.py @@ -18,9 +18,9 @@ def max_devices(uvm): match platform.machine(): case "aarch64": # On aarch64, IRQs are available from 32 to 127. We always use one IRQ each for - # the VMGenID and RTC devices, so the maximum number of devices supported - # at the same time is 94. - return 94 + # the VMGenID, RTC and serial devices, so the maximum number of devices supported + # at the same time is 93. + return 93 case "x86_64": # IRQs are available from 5 to 23. We always use one IRQ for VMGenID device, so # the maximum number of devices supported at the same time is 18. diff --git a/tests/integration_tests/functional/test_serial_io.py b/tests/integration_tests/functional/test_serial_io.py index 353496576e4..3bcdd20d2c0 100644 --- a/tests/integration_tests/functional/test_serial_io.py +++ b/tests/integration_tests/functional/test_serial_io.py @@ -51,11 +51,10 @@ def test_serial_after_snapshot(uvm_plain, microvm_factory): """ microvm = uvm_plain microvm.help.enable_console() - microvm.spawn() + microvm.spawn(serial_out_path=None) microvm.basic_config( vcpu_count=2, mem_size_mib=256, - boot_args="console=ttyS0 reboot=k panic=1 swiotlb=noforce", ) serial = Serial(microvm) serial.open() @@ -72,7 +71,7 @@ def test_serial_after_snapshot(uvm_plain, microvm_factory): # Load microVM clone from snapshot. vm = microvm_factory.build() vm.help.enable_console() - vm.spawn() + vm.spawn(serial_out_path=None) vm.restore_from_snapshot(snapshot, resume=True) serial = Serial(vm) serial.open() @@ -92,16 +91,14 @@ def test_serial_console_login(uvm_plain_any): """ microvm = uvm_plain_any microvm.help.enable_console() - microvm.spawn() + microvm.spawn(serial_out_path=None) # We don't need to monitor the memory for this test because we are # just rebooting and the process dies before pmap gets the RSS. microvm.memory_monitor = None # Set up the microVM with 1 vCPU and a serial console. - microvm.basic_config( - vcpu_count=1, boot_args="console=ttyS0 reboot=k panic=1 swiotlb=noforce" - ) + microvm.basic_config(vcpu_count=1) microvm.start() @@ -146,7 +143,6 @@ def test_serial_dos(uvm_plain_any): # Set up the microVM with 1 vCPU and a serial console. microvm.basic_config( vcpu_count=1, - boot_args="console=ttyS0 reboot=k panic=1 swiotlb=noforce", ) microvm.add_net_iface() microvm.start() @@ -174,13 +170,12 @@ def test_serial_block(uvm_plain_any): """ test_microvm = uvm_plain_any test_microvm.help.enable_console() - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) # Set up the microVM with 1 vCPU so we make sure the vCPU thread # responsible for the SSH connection will also run the serial. test_microvm.basic_config( vcpu_count=1, mem_size_mib=512, - boot_args="console=ttyS0 reboot=k panic=1 swiotlb=noforce", ) test_microvm.add_net_iface() test_microvm.start() diff --git a/tests/integration_tests/performance/test_initrd.py b/tests/integration_tests/performance/test_initrd.py index 7b92644efa6..1bc84933fe9 100644 --- a/tests/integration_tests/performance/test_initrd.py +++ b/tests/integration_tests/performance/test_initrd.py @@ -29,7 +29,7 @@ def test_microvm_initrd_with_serial(uvm_with_initrd, huge_pages): """ vm = uvm_with_initrd vm.help.enable_console() - vm.spawn() + vm.spawn(serial_out_path=None) vm.memory_monitor = None vm.basic_config( diff --git a/tests/integration_tests/security/test_custom_seccomp.py b/tests/integration_tests/security/test_custom_seccomp.py index 05f9b9aa96e..ffcedbbd653 100644 --- a/tests/integration_tests/security/test_custom_seccomp.py +++ b/tests/integration_tests/security/test_custom_seccomp.py @@ -123,7 +123,7 @@ def test_invalid_bpf(uvm_plain): test_microvm.create_jailed_resource(bpf_path) test_microvm.jailer.extra_args.update({"seccomp-filter": bpf_path.name}) - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) # give time for the process to get killed time.sleep(1) assert "Seccomp error: Filter deserialization failed" in test_microvm.log_data diff --git a/tests/integration_tests/security/test_jail.py b/tests/integration_tests/security/test_jail.py index 277d33b17b3..e9cdfc2c644 100644 --- a/tests/integration_tests/security/test_jail.py +++ b/tests/integration_tests/security/test_jail.py @@ -165,7 +165,7 @@ def test_arbitrary_usocket_location(uvm_plain): test_microvm = uvm_plain test_microvm.jailer.extra_args = {"api-sock": "api.socket"} - test_microvm.spawn() + test_microvm.spawn(serial_out_path=None) check_stats( os.path.join(test_microvm.jailer.chroot_path(), "api.socket"), diff --git a/tests/pyproject.toml b/tests/pyproject.toml index 8824affce78..3fe5721420a 100644 --- a/tests/pyproject.toml +++ b/tests/pyproject.toml @@ -56,4 +56,5 @@ disable = [ "too-many-positional-arguments", "too-few-public-methods", "too-many-branches", + "too-many-statements", ] From b9fd2b9f8e6a256eff54069a48af0c1faf9aba37 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 13:34:42 +0100 Subject: [PATCH 09/13] test: add test for serial out to file Add a test that verifies having serial output go to a file works. Signed-off-by: Patrick Roy --- tests/integration_tests/functional/test_serial_io.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/integration_tests/functional/test_serial_io.py b/tests/integration_tests/functional/test_serial_io.py index 3bcdd20d2c0..65a3c14b6bb 100644 --- a/tests/integration_tests/functional/test_serial_io.py +++ b/tests/integration_tests/functional/test_serial_io.py @@ -228,3 +228,10 @@ def test_no_serial_fd_error_when_daemonized(uvm_plain): test_microvm.start() assert REGISTER_FAILED_WARNING not in test_microvm.log_data + + +def test_serial_file_output(uvm_any): + """Test that redirecting serial console output to a file works for booted and restored VMs""" + uvm_any.ssh.check_output("echo 'hello' > /dev/ttyS0") + + assert b"hello" in uvm_any.serial_out_path.read_bytes() From a9a6ab7ada1ac84bde54e2df4da5bdcce16afc5e Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 12:23:40 +0100 Subject: [PATCH 10/13] refactor(test): eliminate overcomplicated state machine In test_serial_console_login, we were implementing something that takes three lines (send and receive data via serial) via an overcomplicated state machine system. Eliminate the 100 lines of state machine code and instead just... do the 3 lines of serial.tx/rx. Signed-off-by: Patrick Roy --- tests/framework/state_machine.py | 58 ------------------- .../functional/test_serial_io.py | 38 +----------- 2 files changed, 3 insertions(+), 93 deletions(-) delete mode 100644 tests/framework/state_machine.py diff --git a/tests/framework/state_machine.py b/tests/framework/state_machine.py deleted file mode 100644 index 1d8dd664e6b..00000000000 --- a/tests/framework/state_machine.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -"""Defines a stream based string matcher and a generic state object.""" - - -class MatchStaticString: - """Match a static string versus input.""" - - # Prevent state objects from being collected by pytest. - __test__ = False - - def __init__(self, match_string): - """Initialize using specified match string.""" - self._string = match_string - self._input = "" - - def match(self, input_char) -> bool: - """ - Check if `_input` matches the match `_string`. - - Process one char at a time and build `_input` string. - Preserve built `_input` if partially matches `_string`. - Return True when `_input` is the same as `_string`. - """ - if input_char == "": - return False - self._input += str(input_char) - if self._input == self._string[: len(self._input)]: - if len(self._input) == len(self._string): - self._input = "" - return True - return False - - self._input = self._input[1:] - return False - - -class TestState(MatchStaticString): - """Generic test state object.""" - - # Prevent state objects from being collected by pytest. - __test__ = False - - def __init__(self, match_string=""): - """Initialize state fields.""" - MatchStaticString.__init__(self, match_string) - print("\n*** Current test state: ", str(self), end="") - - def handle_input(self, serial, input_char): - """Handle input event and return next state.""" - - def __repr__(self): - """Leverages the __str__ method to describe the TestState.""" - return self.__str__() - - def __str__(self): - """Return state name.""" - return self.__class__.__name__ diff --git a/tests/integration_tests/functional/test_serial_io.py b/tests/integration_tests/functional/test_serial_io.py index 65a3c14b6bb..90601cfdf6f 100644 --- a/tests/integration_tests/functional/test_serial_io.py +++ b/tests/integration_tests/functional/test_serial_io.py @@ -11,40 +11,10 @@ from framework import utils from framework.microvm import Serial -from framework.state_machine import TestState PLATFORM = platform.machine() -class WaitTerminal(TestState): - """Initial state when we wait for the login prompt.""" - - def handle_input(self, serial, input_char) -> TestState: - """Handle input and return next state.""" - if self.match(input_char): - serial.tx("id") - return WaitIDResult("uid=0(root) gid=0(root) groups=0(root)") - return self - - -class WaitIDResult(TestState): - """Wait for the console to show the result of the 'id' shell command.""" - - def handle_input(self, unused_serial, input_char) -> TestState: - """Handle input and return next state.""" - if self.match(input_char): - return TestFinished() - return self - - -class TestFinished(TestState): - """Test complete and successful.""" - - def handle_input(self, unused_serial, _) -> TestState: - """Return self since the test is about to end.""" - return self - - def test_serial_after_snapshot(uvm_plain, microvm_factory): """ Serial I/O after restoring from a snapshot. @@ -104,11 +74,9 @@ def test_serial_console_login(uvm_plain_any): serial = Serial(microvm) serial.open() - current_state = WaitTerminal("ubuntu-fc-uvm:") - - while not isinstance(current_state, TestFinished): - output_char = serial.rx_char() - current_state = current_state.handle_input(serial, output_char) + serial.rx("ubuntu-fc-uvm:") + serial.tx("id") + serial.rx("uid=0(root) gid=0(root) groups=0(root)") def get_total_mem_size(pid): From 69d14c72d7f7c0095c2381efca3d2e68196eb9fc Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Mon, 4 Aug 2025 16:06:17 +0100 Subject: [PATCH 11/13] test: take snapshot on failure If a test fails, take a snapshot fo the microvms involved, and upload it as a CI artifact, for post-mortem analysis. Signed-off-by: Patrick Roy --- tests/conftest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index ab485a3da46..7a6423e9d6f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -378,6 +378,14 @@ def microvm_factory(request, record_property, results_dir, netns_factory): uvm.flush_metrics() except: # pylint: disable=bare-except pass + + try: + uvm.snapshot_full( + mem_path="post_failure.mem", vmstate_path="post_failure.vmstate" + ) + except: # pylint: disable=bare-except + pass + uvm_data = results_dir / uvm.id uvm_data.mkdir() uvm_data.joinpath("host-dmesg.log").write_text( From 223d1f6705f0edf88d950e94c2f5eb1e3c7834d4 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 12:03:50 +0100 Subject: [PATCH 12/13] test(perf): remove bidirectional throughput tests These have only caused confusion, as they're output often makes no sense, and we generally just end up ignoring them (but have discussions about it every single time). Let's remove them. Signed-off-by: Patrick Roy --- tests/framework/utils_iperf.py | 37 +++++-------------- .../performance/test_network.py | 8 +--- .../performance/test_vsock.py | 7 +--- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/tests/framework/utils_iperf.py b/tests/framework/utils_iperf.py index aa2b663c1c7..9d0d064159c 100644 --- a/tests/framework/utils_iperf.py +++ b/tests/framework/utils_iperf.py @@ -65,12 +65,12 @@ def run_test(self, first_free_cpu): clients = [] for client_idx in range(self._num_clients): - client_mode = self.client_mode(client_idx) - client_mode_flag = self.client_mode_to_iperf3_flag(client_mode) client_future = executor.submit( - self.spawn_iperf3_client, client_idx, client_mode_flag + self.spawn_iperf3_client, + client_idx, + self.client_mode_to_iperf3_flag, ) - clients.append((client_mode, client_future)) + clients.append((self._mode, client_future)) data = {"cpu_load_raw": cpu_load_future.result(), "g2h": [], "h2g": []} @@ -79,31 +79,12 @@ def run_test(self, first_free_cpu): return data - def client_mode(self, client_idx): - """Converts client index into client mode""" - match self._mode: - case "g2h": - client_mode = "g2h" - case "h2g": - client_mode = "h2g" - case "bd": - # in bidirectional mode we alternate - # modes - if client_idx % 2 == 0: - client_mode = "g2h" - else: - client_mode = "h2g" - return client_mode - - @staticmethod - def client_mode_to_iperf3_flag(client_mode): + @property + def client_mode_to_iperf3_flag(self): """Converts client mode into iperf3 mode flag""" - match client_mode: - case "g2h": - client_mode_flag = "" - case "h2g": - client_mode_flag = "-R" - return client_mode_flag + if self._mode == "h2g": + return "-R" + return "" def spawn_iperf3_client(self, client_idx, client_mode_flag): """ diff --git a/tests/integration_tests/performance/test_network.py b/tests/integration_tests/performance/test_network.py index 74ad26c26a8..62e73e865ca 100644 --- a/tests/integration_tests/performance/test_network.py +++ b/tests/integration_tests/performance/test_network.py @@ -91,7 +91,7 @@ def test_network_latency(network_microvm, metrics): @pytest.mark.timeout(120) @pytest.mark.parametrize("network_microvm", [1, 2], indirect=True) @pytest.mark.parametrize("payload_length", ["128K", "1024K"], ids=["p128K", "p1024K"]) -@pytest.mark.parametrize("mode", ["g2h", "h2g", "bd"]) +@pytest.mark.parametrize("mode", ["g2h", "h2g"]) def test_network_tcp_throughput( network_microvm, payload_length, @@ -109,12 +109,6 @@ def test_network_tcp_throughput( # Time (in seconds) for which iperf runs after warmup is done runtime_sec = 20 - # We run bi-directional tests only on uVM with more than 2 vCPus - # because we need to pin one iperf3/direction per vCPU, and since we - # have two directions, we need at least two vCPUs. - if mode == "bd" and network_microvm.vcpus_count < 2: - pytest.skip("bidrectional test only done with at least 2 vcpus") - metrics.set_dimensions( { "performance_test": "test_network_tcp_throughput", diff --git a/tests/integration_tests/performance/test_vsock.py b/tests/integration_tests/performance/test_vsock.py index 402e7ff66b5..9c705e665b7 100644 --- a/tests/integration_tests/performance/test_vsock.py +++ b/tests/integration_tests/performance/test_vsock.py @@ -73,7 +73,7 @@ def guest_command(self, port_offset): @pytest.mark.nonci @pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"]) @pytest.mark.parametrize("payload_length", ["64K", "1024K"], ids=["p64K", "p1024K"]) -@pytest.mark.parametrize("mode", ["g2h", "h2g", "bd"]) +@pytest.mark.parametrize("mode", ["g2h", "h2g"]) def test_vsock_throughput( uvm_plain_acpi, vcpus, @@ -85,11 +85,6 @@ def test_vsock_throughput( """ Test vsock throughput for multiple vm configurations. """ - # We run bi-directional tests only on uVM with more than 2 vCPus - # because we need to pin one iperf3/direction per vCPU, and since we - # have two directions, we need at least two vCPUs. - if mode == "bd" and vcpus < 2: - pytest.skip("bidrectional test only done with at least 2 vcpus") mem_size_mib = 1024 vm = uvm_plain_acpi From cfe6923d68c30b39a823dcf439abff3698053216 Mon Sep 17 00:00:00 2001 From: Patrick Roy Date: Thu, 7 Aug 2025 08:27:58 +0100 Subject: [PATCH 13/13] test: hack: tmp: disable serial for A/B tests When A/B-testing, the "A" revision firecracker binary does not know about the new --serial-out-path CLI argument. Thus, disable the serial console for all A/B-tests. It gets quite ugly to do for the vulnerability tests, because the .spawn() call is burried deep in the fixtures. This commit should be reverted after merging the PR Signed-off-by: Patrick Roy --- tests/conftest.py | 5 ++++- tests/framework/microvm.py | 5 ++++- tests/integration_tests/performance/test_block.py | 4 ++-- tests/integration_tests/performance/test_boottime.py | 2 +- tests/integration_tests/performance/test_memory_overhead.py | 2 +- tests/integration_tests/performance/test_network.py | 2 +- tests/integration_tests/performance/test_snapshot.py | 4 ++-- tests/integration_tests/performance/test_vsock.py | 2 +- tests/integration_tests/security/test_vulnerabilities.py | 1 + 9 files changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7a6423e9d6f..267ef0ffa7a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -605,7 +605,10 @@ def uvm_booted( ): """Return a booted uvm""" uvm = microvm_factory.build(guest_kernel, rootfs, pci=pci_enabled) - uvm.spawn() + if getattr(microvm_factory, "hack_no_serial", False): + uvm.spawn(serial_out_path=None) + else: + uvm.spawn() uvm.basic_config(vcpu_count=vcpu_count, mem_size_mib=mem_size_mib) uvm.set_cpu_template(cpu_template) uvm.add_net_iface() diff --git a/tests/framework/microvm.py b/tests/framework/microvm.py index 03f90905843..7ab4ea051f7 100644 --- a/tests/framework/microvm.py +++ b/tests/framework/microvm.py @@ -1231,7 +1231,10 @@ def build(self, kernel=None, rootfs=None, **kwargs): def build_from_snapshot(self, snapshot: Snapshot): """Build a microvm from a snapshot""" vm = self.build() - vm.spawn() + if getattr(self, "hack_no_serial", False): + vm.spawn(serial_out_path=None) + else: + vm.spawn() vm.restore_from_snapshot(snapshot, resume=True) return vm diff --git a/tests/integration_tests/performance/test_block.py b/tests/integration_tests/performance/test_block.py index 8882ee0717c..47b1f466daa 100644 --- a/tests/integration_tests/performance/test_block.py +++ b/tests/integration_tests/performance/test_block.py @@ -174,7 +174,7 @@ def test_block_performance( Execute block device emulation benchmarking scenarios. """ vm = uvm_plain_acpi - vm.spawn(log_level="Info", emit_metrics=True) + vm.spawn(log_level="Info", emit_metrics=True, serial_out_path=None) vm.basic_config(vcpu_count=vcpus, mem_size_mib=GUEST_MEM_MIB) vm.add_net_iface() # Add a secondary block device for benchmark tests. @@ -223,7 +223,7 @@ def test_block_vhost_user_performance( """ vm = uvm_plain_acpi - vm.spawn(log_level="Info", emit_metrics=True) + vm.spawn(log_level="Info", emit_metrics=True, serial_out_path=None) vm.basic_config(vcpu_count=vcpus, mem_size_mib=GUEST_MEM_MIB) vm.add_net_iface() diff --git a/tests/integration_tests/performance/test_boottime.py b/tests/integration_tests/performance/test_boottime.py index d80bf026a39..e8f77f0b62c 100644 --- a/tests/integration_tests/performance/test_boottime.py +++ b/tests/integration_tests/performance/test_boottime.py @@ -100,7 +100,7 @@ def launch_vm_with_boot_timer( """Launches a microVM with guest-timer and returns the reported metrics for it""" vm = microvm_factory.build(guest_kernel_acpi, rootfs_rw, pci=pci_enabled) vm.jailer.extra_args.update({"boot-timer": None}) - vm.spawn() + vm.spawn(serial_out_path=None) vm.basic_config( vcpu_count=vcpu_count, mem_size_mib=mem_size_mib, diff --git a/tests/integration_tests/performance/test_memory_overhead.py b/tests/integration_tests/performance/test_memory_overhead.py index 2f4888c95ea..9e31d106afe 100644 --- a/tests/integration_tests/performance/test_memory_overhead.py +++ b/tests/integration_tests/performance/test_memory_overhead.py @@ -47,7 +47,7 @@ def test_memory_overhead( microvm = microvm_factory.build( guest_kernel_acpi, rootfs, pci=pci_enabled, monitor_memory=False ) - microvm.spawn(emit_metrics=True) + microvm.spawn(emit_metrics=True, serial_out_path=None) microvm.basic_config(vcpu_count=vcpu_count, mem_size_mib=mem_size_mib) microvm.add_net_iface() microvm.start() diff --git a/tests/integration_tests/performance/test_network.py b/tests/integration_tests/performance/test_network.py index 62e73e865ca..89450e9f996 100644 --- a/tests/integration_tests/performance/test_network.py +++ b/tests/integration_tests/performance/test_network.py @@ -46,7 +46,7 @@ def network_microvm(request, uvm_plain_acpi): guest_vcpus = request.param vm = uvm_plain_acpi - vm.spawn(log_level="Info", emit_metrics=True) + vm.spawn(log_level="Info", emit_metrics=True, serial_out_path=None) vm.basic_config(vcpu_count=guest_vcpus, mem_size_mib=guest_mem_mib) vm.add_net_iface() vm.start() diff --git a/tests/integration_tests/performance/test_snapshot.py b/tests/integration_tests/performance/test_snapshot.py index b4e9afabb67..38980396107 100644 --- a/tests/integration_tests/performance/test_snapshot.py +++ b/tests/integration_tests/performance/test_snapshot.py @@ -52,7 +52,7 @@ def boot_vm(self, microvm_factory, guest_kernel, rootfs, pci_enabled) -> Microvm monitor_memory=False, pci=pci_enabled, ) - vm.spawn(log_level="Info", emit_metrics=True) + vm.spawn(log_level="Info", emit_metrics=True, serial_out_path=None) vm.time_api_requests = False vm.basic_config( vcpu_count=self.vcpus, @@ -271,7 +271,7 @@ def test_snapshot_create_latency( """Measure the latency of creating a Full snapshot""" vm = uvm_plain - vm.spawn() + vm.spawn(serial_out_path=None) vm.basic_config( vcpu_count=2, mem_size_mib=512, diff --git a/tests/integration_tests/performance/test_vsock.py b/tests/integration_tests/performance/test_vsock.py index 9c705e665b7..ef7d2ac4ab1 100644 --- a/tests/integration_tests/performance/test_vsock.py +++ b/tests/integration_tests/performance/test_vsock.py @@ -88,7 +88,7 @@ def test_vsock_throughput( mem_size_mib = 1024 vm = uvm_plain_acpi - vm.spawn(log_level="Info", emit_metrics=True) + vm.spawn(log_level="Info", emit_metrics=True, serial_out_path=None) vm.basic_config(vcpu_count=vcpus, mem_size_mib=mem_size_mib) vm.add_net_iface() # Create a vsock device diff --git a/tests/integration_tests/security/test_vulnerabilities.py b/tests/integration_tests/security/test_vulnerabilities.py index 01b8e9c595b..606e49758d0 100644 --- a/tests/integration_tests/security/test_vulnerabilities.py +++ b/tests/integration_tests/security/test_vulnerabilities.py @@ -211,6 +211,7 @@ def microvm_factory_a(record_property): bin_dir = git_clone(Path("../build") / revision_a, revision_a).resolve() record_property("firecracker_bin", str(bin_dir / "firecracker")) uvm_factory = MicroVMFactory(bin_dir) + uvm_factory.hack_no_serial = True yield uvm_factory uvm_factory.kill()