xref: /aosp_15_r20/external/crosvm/e2e_tests/tests/boot.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2020 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::time::Duration;
6 
7 use fixture::vm::Config;
8 use fixture::vm::TestVm;
9 
10 #[test]
boot_test_vm() -> anyhow::Result<()>11 fn boot_test_vm() -> anyhow::Result<()> {
12     let mut vm = TestVm::new(Config::new()).unwrap();
13     assert_eq!(vm.exec_in_guest("echo 42")?.stdout.trim(), "42");
14     Ok(())
15 }
16 
17 #[test]
boot_custom_vm_kernel_initrd() -> anyhow::Result<()>18 fn boot_custom_vm_kernel_initrd() -> anyhow::Result<()> {
19     let cfg = Config::new()
20     .with_kernel("https://storage.googleapis.com/crosvm/integration_tests/benchmarks/custom-guest-bzimage-x86_64-r0001")
21     .with_initrd("https://storage.googleapis.com/crosvm/integration_tests/benchmarks/custom-initramfs.cpio.gz-r0005")
22     // Use a non-sense file as rootfs to prove delegate correctly function in initrd
23     .with_rootfs("https://storage.googleapis.com/crosvm/integration_tests/guest-bzimage-aarch64-r0007")
24     .with_stdout_hardware("serial").extra_args(vec!["--mem".to_owned(), "512".to_owned()]);
25     let mut vm = TestVm::new(cfg).unwrap();
26     assert_eq!(
27         vm.exec_in_guest_async("echo 42")?
28             .with_timeout(Duration::from_secs(500))
29             .wait_ok(&mut vm)?
30             .stdout
31             .trim(),
32         "42"
33     );
34     Ok(())
35 }
36 
37 #[test]
boot_test_vm_uring() -> anyhow::Result<()>38 fn boot_test_vm_uring() -> anyhow::Result<()> {
39     let mut vm = TestVm::new(
40         Config::new().extra_args(vec!["--async-executor".to_string(), "uring".to_string()]),
41     )
42     .unwrap();
43     assert_eq!(vm.exec_in_guest("echo 42")?.stdout.trim(), "42");
44     Ok(())
45 }
46 
47 #[cfg(any(target_os = "android", target_os = "linux"))]
48 #[test]
boot_test_vm_odirect()49 fn boot_test_vm_odirect() {
50     let mut vm = TestVm::new(Config::new().o_direct()).unwrap();
51     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
52 }
53 
54 #[cfg(any(target_os = "android", target_os = "linux"))]
55 #[test]
boot_test_vm_config_file()56 fn boot_test_vm_config_file() {
57     let mut vm = TestVm::new_with_config_file(Config::new()).unwrap();
58     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
59 }
60 
61 /*
62  * VCPU-level suspend/resume tests (which does NOT suspend the devices)
63  */
64 
65 #[cfg(any(target_os = "android", target_os = "linux"))]
66 #[test]
vcpu_suspend_resume_succeeds()67 fn vcpu_suspend_resume_succeeds() {
68     // There is no easy way for us to check if the VM is actually suspended. But at
69     // least exercise the code-path.
70     let mut vm = TestVm::new(Config::new()).unwrap();
71     vm.suspend().unwrap();
72     vm.resume().unwrap();
73     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
74 }
75 
76 #[cfg(any(target_os = "android", target_os = "linux"))]
77 #[test]
vcpu_suspend_resume_succeeds_with_pvclock()78 fn vcpu_suspend_resume_succeeds_with_pvclock() {
79     // There is no easy way for us to check if the VM is actually suspended. But at
80     // least exercise the code-path.
81     let mut config = Config::new();
82     config = config.extra_args(vec!["--pvclock".to_string()]);
83     let mut vm = TestVm::new(config).unwrap();
84     vm.suspend().unwrap();
85     vm.resume().unwrap();
86     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
87 }
88 
89 /*
90  * Full suspend/resume tests (which suspend the devices and vcpus)
91  */
92 
93 #[cfg(any(target_os = "android", target_os = "linux"))]
94 #[test]
full_suspend_resume_test_suspend_resume_full()95 fn full_suspend_resume_test_suspend_resume_full() {
96     // There is no easy way for us to check if the VM is actually suspended. But at
97     // least exercise the code-path.
98     let mut config = Config::new();
99     config = config.with_stdout_hardware("legacy-virtio-console");
100     // Why this test is called "full"? Can anyone explain...?
101     config = config.extra_args(vec![
102         "--no-usb".to_string(),
103         "--no-balloon".to_string(),
104         "--no-rng".to_string(),
105     ]);
106     let mut vm = TestVm::new(config).unwrap();
107     vm.suspend_full().unwrap();
108     vm.resume_full().unwrap();
109     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
110 }
111 
112 #[cfg(any(target_os = "android", target_os = "linux"))]
113 #[test]
full_suspend_resume_with_pvclock()114 fn full_suspend_resume_with_pvclock() {
115     // There is no easy way for us to check if the VM is actually suspended. But at
116     // least exercise the code-path.
117     let mut config = Config::new();
118     config = config.with_stdout_hardware("legacy-virtio-console");
119     config = config.extra_args(vec![
120         "--no-usb".to_string(),
121         "--no-balloon".to_string(),
122         "--no-rng".to_string(),
123         "--pvclock".to_string(),
124     ]);
125     let mut vm = TestVm::new(config).unwrap();
126     vm.suspend_full().unwrap();
127     vm.resume_full().unwrap();
128     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
129 }
130 
131 #[cfg(any(target_os = "android", target_os = "linux"))]
132 #[test]
vcpu_suspend_resume_with_pvclock_adjusts_guest_clocks()133 fn vcpu_suspend_resume_with_pvclock_adjusts_guest_clocks() {
134     use readclock::ClockValues;
135 
136     // SUSPEND_DURATION defines how long the VM should be suspended
137     const SUSPEND_DURATION: Duration = Duration::from_secs(2);
138     const ALLOWANCE: Duration = Duration::from_secs(1);
139 
140     // Launch a VM with pvclock option
141     let mut config = Config::new();
142     config = config.with_stdout_hardware("legacy-virtio-console");
143     config = config.extra_args(vec![
144         "--no-usb".to_string(),
145         "--no-balloon".to_string(),
146         "--no-rng".to_string(),
147         "--pvclock".to_string(),
148     ]);
149     let mut vm = TestVm::new(config).unwrap();
150 
151     // Mount the proc fs
152     vm.exec_in_guest("mount proc /proc -t proc").unwrap();
153     // Ensure that the kernel has virtio-pvclock
154     assert_eq!(
155         vm.exec_in_guest("cat /proc/config.gz | gunzip | grep '^CONFIG_VIRTIO_PVCLOCK'")
156             .unwrap()
157             .stdout
158             .trim(),
159         "CONFIG_VIRTIO_PVCLOCK=y"
160     );
161 
162     let guest_clocks_before = vm.guest_clock_values().unwrap();
163     let host_clocks_before = ClockValues::now();
164     vm.suspend().unwrap();
165     println!("Sleeping {SUSPEND_DURATION:?}...");
166     std::thread::sleep(SUSPEND_DURATION);
167     vm.resume().unwrap();
168     // Sleep a bit, to give the guest a chance to move the CLOCK_BOOTTIME value forward.
169     std::thread::sleep(SUSPEND_DURATION);
170     let guest_clocks_after = vm.guest_clock_values().unwrap();
171     let host_clocks_after = ClockValues::now();
172     // Calculating in f64 since the result may be negative
173     let guest_mono_diff = guest_clocks_after.clock_monotonic().as_secs_f64()
174         - guest_clocks_before.clock_monotonic().as_secs_f64();
175     let guest_boot_diff = guest_clocks_after.clock_boottime().as_secs_f64()
176         - guest_clocks_before.clock_boottime().as_secs_f64();
177     let host_boot_diff = host_clocks_after.clock_boottime().as_secs_f64()
178         - host_clocks_before.clock_boottime().as_secs_f64();
179 
180     assert!(host_boot_diff > SUSPEND_DURATION.as_secs_f64());
181     // Although the BOOTTIME and MONOTONIC behavior varies in general for some real-world factors
182     // like the implementation of the kernel, the virtualization platforms and hardware issues,
183     // when virtio-pvclock is in use, crosvm does its best effort to maintain the following
184     // invariants to make the guest's userland peaceful:
185 
186     // Invariants 1: Guest's MONOTONIC behaves as if they are stopped during the VM is suspended in
187     // terms of crosvm's VM instance running state. In other words, the guest's monotonic
188     // difference is smaller than the "real" time experienced by the host by SUSPEND_DURATION.
189     let monotonic_error = guest_mono_diff + SUSPEND_DURATION.as_secs_f64() - host_boot_diff;
190     assert!(monotonic_error < ALLOWANCE.as_secs_f64());
191 
192     // Invariants 2: Subtracting Guest's MONOTONIC from the Guest's BOOTTIME should be
193     // equal to the total duration that the VM was in the "suspended" state as noted
194     // in the Invariants 1.
195     let guest_suspend_duration = guest_boot_diff - guest_mono_diff;
196     let boottime_error = (guest_suspend_duration - SUSPEND_DURATION.as_secs_f64()).abs();
197     assert!(boottime_error < ALLOWANCE.as_secs_f64());
198 }
199 
200 #[cfg(any(target_os = "android", target_os = "linux"))]
201 #[test]
boot_test_vm_disable_sandbox()202 fn boot_test_vm_disable_sandbox() {
203     let mut vm = TestVm::new(Config::new().disable_sandbox()).unwrap();
204     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
205 }
206 
207 #[cfg(any(target_os = "android", target_os = "linux"))]
208 #[test]
boot_test_vm_disable_sandbox_odirect()209 fn boot_test_vm_disable_sandbox_odirect() {
210     let mut vm = TestVm::new(Config::new().disable_sandbox().o_direct()).unwrap();
211     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
212 }
213 
214 #[cfg(any(target_os = "android", target_os = "linux"))]
215 #[test]
boot_test_vm_disable_sandbox_config_file()216 fn boot_test_vm_disable_sandbox_config_file() {
217     let mut vm = TestVm::new_with_config_file(Config::new().disable_sandbox()).unwrap();
218     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
219 }
220 
221 #[cfg(any(target_os = "android", target_os = "linux"))]
222 #[test]
boot_test_disable_sandbox_suspend_resume()223 fn boot_test_disable_sandbox_suspend_resume() {
224     // There is no easy way for us to check if the VM is actually suspended. But at
225     // least exercise the code-path.
226     let mut vm = TestVm::new(Config::new().disable_sandbox()).unwrap();
227     vm.suspend().unwrap();
228     vm.resume().unwrap();
229     assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
230 }
231