xref: /cloud-hypervisor/tests/integration.rs (revision 38380198e1660348e54cc69a6355bcd1f92e8cae)
1 // Copyright © 2020 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 #![allow(clippy::undocumented_unsafe_blocks)]
6 // When enabling the `mshv` feature, we skip quite some tests and
7 // hence have known dead-code. This annotation silences dead-code
8 // related warnings for our quality workflow to pass.
9 #![allow(dead_code)]
10 
11 extern crate test_infra;
12 
13 use std::collections::HashMap;
14 use std::io::{BufRead, Read, Seek, Write};
15 use std::net::TcpListener;
16 use std::os::unix::io::AsRawFd;
17 use std::path::PathBuf;
18 use std::process::{Child, Command, Stdio};
19 use std::string::String;
20 use std::sync::mpsc::Receiver;
21 use std::sync::{mpsc, Mutex};
22 use std::time::Duration;
23 use std::{fs, io, thread};
24 
25 use net_util::MacAddr;
26 use test_infra::*;
27 use vmm_sys_util::tempdir::TempDir;
28 use vmm_sys_util::tempfile::TempFile;
29 use wait_timeout::ChildExt;
30 
31 // Constant taken from the VMM crate.
32 const MAX_NUM_PCI_SEGMENTS: u16 = 96;
33 
34 #[cfg(target_arch = "x86_64")]
35 mod x86_64 {
36     pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw";
37     pub const JAMMY_VFIO_IMAGE_NAME: &str =
38         "jammy-server-cloudimg-amd64-custom-vfio-20241012-0.raw";
39     pub const FOCAL_IMAGE_NAME_QCOW2: &str = "focal-server-cloudimg-amd64-custom-20210609-0.qcow2";
40     pub const FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE: &str =
41         "focal-server-cloudimg-amd64-custom-20210609-0-backing.qcow2";
42     pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhd";
43     pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-amd64-custom-20210609-0.vhdx";
44     pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-amd64-custom-20241017-0.raw";
45     pub const WINDOWS_IMAGE_NAME: &str = "windows-server-2022-amd64-2.raw";
46     pub const OVMF_NAME: &str = "CLOUDHV.fd";
47     pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'IO-APIC.*ttyS0' /proc/interrupts || true";
48 }
49 
50 #[cfg(target_arch = "x86_64")]
51 use x86_64::*;
52 
53 #[cfg(target_arch = "aarch64")]
54 mod aarch64 {
55     pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0.raw";
56     pub const FOCAL_IMAGE_UPDATE_KERNEL_NAME: &str =
57         "focal-server-cloudimg-arm64-custom-20210929-0-update-kernel.raw";
58     pub const FOCAL_IMAGE_NAME_QCOW2: &str = "focal-server-cloudimg-arm64-custom-20210929-0.qcow2";
59     pub const FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE: &str =
60         "focal-server-cloudimg-arm64-custom-20210929-0-backing.qcow2";
61     pub const FOCAL_IMAGE_NAME_VHD: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhd";
62     pub const FOCAL_IMAGE_NAME_VHDX: &str = "focal-server-cloudimg-arm64-custom-20210929-0.vhdx";
63     pub const JAMMY_IMAGE_NAME: &str = "jammy-server-cloudimg-arm64-custom-20220329-0.raw";
64     pub const WINDOWS_IMAGE_NAME: &str = "windows-11-iot-enterprise-aarch64.raw";
65     pub const OVMF_NAME: &str = "CLOUDHV_EFI.fd";
66     pub const GREP_SERIAL_IRQ_CMD: &str = "grep -c 'GICv3.*uart-pl011' /proc/interrupts || true";
67     pub const GREP_PMU_IRQ_CMD: &str = "grep -c 'GICv3.*arm-pmu' /proc/interrupts || true";
68 }
69 
70 #[cfg(target_arch = "aarch64")]
71 use aarch64::*;
72 
73 const DIRECT_KERNEL_BOOT_CMDLINE: &str =
74     "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1";
75 
76 const CONSOLE_TEST_STRING: &str = "Started OpenBSD Secure Shell server";
77 
78 // This enum exists to make it more convenient to
79 // implement test for both D-Bus and REST APIs.
80 enum TargetApi {
81     // API socket
82     HttpApi(String),
83     // well known service name, object path
84     DBusApi(String, String),
85 }
86 
87 impl TargetApi {
88     fn new_http_api(tmp_dir: &TempDir) -> Self {
89         Self::HttpApi(temp_api_path(tmp_dir))
90     }
91 
92     fn new_dbus_api(tmp_dir: &TempDir) -> Self {
93         // `tmp_dir` is in the form of "/tmp/chXXXXXX"
94         // and we take the `chXXXXXX` part as a unique identifier for the guest
95         let id = tmp_dir.as_path().file_name().unwrap().to_str().unwrap();
96 
97         Self::DBusApi(
98             format!("org.cloudhypervisor.{id}"),
99             format!("/org/cloudhypervisor/{id}"),
100         )
101     }
102 
103     fn guest_args(&self) -> Vec<String> {
104         match self {
105             TargetApi::HttpApi(api_socket) => {
106                 vec![format!("--api-socket={}", api_socket.as_str())]
107             }
108             TargetApi::DBusApi(service_name, object_path) => {
109                 vec![
110                     format!("--dbus-service-name={}", service_name.as_str()),
111                     format!("--dbus-object-path={}", object_path.as_str()),
112                 ]
113             }
114         }
115     }
116 
117     fn remote_args(&self) -> Vec<String> {
118         // `guest_args` and `remote_args` are consistent with each other
119         self.guest_args()
120     }
121 
122     fn remote_command(&self, command: &str, arg: Option<&str>) -> bool {
123         let mut cmd = Command::new(clh_command("ch-remote"));
124         cmd.args(self.remote_args());
125         cmd.arg(command);
126 
127         if let Some(arg) = arg {
128             cmd.arg(arg);
129         }
130 
131         let output = cmd.output().unwrap();
132         if output.status.success() {
133             true
134         } else {
135             eprintln!("Error running ch-remote command: {:?}", &cmd);
136             let stderr = String::from_utf8_lossy(&output.stderr);
137             eprintln!("stderr: {stderr}");
138             false
139         }
140     }
141 }
142 
143 // Start cloud-hypervisor with no VM parameters, only the API server running.
144 // From the API: Create a VM, boot it and check that it looks as expected.
145 fn _test_api_create_boot(target_api: TargetApi, guest: Guest) {
146     let mut child = GuestCommand::new(&guest)
147         .args(target_api.guest_args())
148         .capture_output()
149         .spawn()
150         .unwrap();
151 
152     thread::sleep(std::time::Duration::new(1, 0));
153 
154     // Verify API server is running
155     assert!(target_api.remote_command("ping", None));
156 
157     // Create the VM first
158     let cpu_count: u8 = 4;
159     let request_body = guest.api_create_body(
160         cpu_count,
161         direct_kernel_boot_path().to_str().unwrap(),
162         DIRECT_KERNEL_BOOT_CMDLINE,
163     );
164 
165     let temp_config_path = guest.tmp_dir.as_path().join("config");
166     std::fs::write(&temp_config_path, request_body).unwrap();
167     let create_config = temp_config_path.as_os_str().to_str().unwrap();
168 
169     assert!(target_api.remote_command("create", Some(create_config),));
170 
171     // Then boot it
172     assert!(target_api.remote_command("boot", None));
173     thread::sleep(std::time::Duration::new(20, 0));
174 
175     let r = std::panic::catch_unwind(|| {
176         // Check that the VM booted as expected
177         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
178         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
179     });
180 
181     kill_child(&mut child);
182     let output = child.wait_with_output().unwrap();
183 
184     handle_child_output(r, &output);
185 }
186 
187 // Start cloud-hypervisor with no VM parameters, only the API server running.
188 // From the API: Create a VM, boot it and check it can be shutdown and then
189 // booted again
190 fn _test_api_shutdown(target_api: TargetApi, guest: Guest) {
191     let mut child = GuestCommand::new(&guest)
192         .args(target_api.guest_args())
193         .capture_output()
194         .spawn()
195         .unwrap();
196 
197     thread::sleep(std::time::Duration::new(1, 0));
198 
199     // Verify API server is running
200     assert!(target_api.remote_command("ping", None));
201 
202     // Create the VM first
203     let cpu_count: u8 = 4;
204     let request_body = guest.api_create_body(
205         cpu_count,
206         direct_kernel_boot_path().to_str().unwrap(),
207         DIRECT_KERNEL_BOOT_CMDLINE,
208     );
209 
210     let temp_config_path = guest.tmp_dir.as_path().join("config");
211     std::fs::write(&temp_config_path, request_body).unwrap();
212     let create_config = temp_config_path.as_os_str().to_str().unwrap();
213 
214     let r = std::panic::catch_unwind(|| {
215         assert!(target_api.remote_command("create", Some(create_config)));
216 
217         // Then boot it
218         assert!(target_api.remote_command("boot", None));
219 
220         guest.wait_vm_boot(None).unwrap();
221 
222         // Check that the VM booted as expected
223         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
224         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
225 
226         // Sync and shutdown without powering off to prevent filesystem
227         // corruption.
228         guest.ssh_command("sync").unwrap();
229         guest.ssh_command("sudo shutdown -H now").unwrap();
230 
231         // Wait for the guest to be fully shutdown
232         thread::sleep(std::time::Duration::new(20, 0));
233 
234         // Then shut it down
235         assert!(target_api.remote_command("shutdown", None));
236 
237         // Then boot it again
238         assert!(target_api.remote_command("boot", None));
239 
240         guest.wait_vm_boot(None).unwrap();
241 
242         // Check that the VM booted as expected
243         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
244         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
245     });
246 
247     kill_child(&mut child);
248     let output = child.wait_with_output().unwrap();
249 
250     handle_child_output(r, &output);
251 }
252 
253 // Start cloud-hypervisor with no VM parameters, only the API server running.
254 // From the API: Create a VM, boot it and check it can be deleted and then recreated
255 // booted again.
256 fn _test_api_delete(target_api: TargetApi, guest: Guest) {
257     let mut child = GuestCommand::new(&guest)
258         .args(target_api.guest_args())
259         .capture_output()
260         .spawn()
261         .unwrap();
262 
263     thread::sleep(std::time::Duration::new(1, 0));
264 
265     // Verify API server is running
266     assert!(target_api.remote_command("ping", None));
267 
268     // Create the VM first
269     let cpu_count: u8 = 4;
270     let request_body = guest.api_create_body(
271         cpu_count,
272         direct_kernel_boot_path().to_str().unwrap(),
273         DIRECT_KERNEL_BOOT_CMDLINE,
274     );
275     let temp_config_path = guest.tmp_dir.as_path().join("config");
276     std::fs::write(&temp_config_path, request_body).unwrap();
277     let create_config = temp_config_path.as_os_str().to_str().unwrap();
278 
279     let r = std::panic::catch_unwind(|| {
280         assert!(target_api.remote_command("create", Some(create_config)));
281 
282         // Then boot it
283         assert!(target_api.remote_command("boot", None));
284 
285         guest.wait_vm_boot(None).unwrap();
286 
287         // Check that the VM booted as expected
288         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
289         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
290 
291         // Sync and shutdown without powering off to prevent filesystem
292         // corruption.
293         guest.ssh_command("sync").unwrap();
294         guest.ssh_command("sudo shutdown -H now").unwrap();
295 
296         // Wait for the guest to be fully shutdown
297         thread::sleep(std::time::Duration::new(20, 0));
298 
299         // Then delete it
300         assert!(target_api.remote_command("delete", None));
301 
302         assert!(target_api.remote_command("create", Some(create_config)));
303 
304         // Then boot it again
305         assert!(target_api.remote_command("boot", None));
306 
307         guest.wait_vm_boot(None).unwrap();
308 
309         // Check that the VM booted as expected
310         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
311         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
312     });
313 
314     kill_child(&mut child);
315     let output = child.wait_with_output().unwrap();
316 
317     handle_child_output(r, &output);
318 }
319 
320 // Start cloud-hypervisor with no VM parameters, only the API server running.
321 // From the API: Create a VM, boot it and check that it looks as expected.
322 // Then we pause the VM, check that it's no longer available.
323 // Finally we resume the VM and check that it's available.
324 fn _test_api_pause_resume(target_api: TargetApi, guest: Guest) {
325     let mut child = GuestCommand::new(&guest)
326         .args(target_api.guest_args())
327         .capture_output()
328         .spawn()
329         .unwrap();
330 
331     thread::sleep(std::time::Duration::new(1, 0));
332 
333     // Verify API server is running
334     assert!(target_api.remote_command("ping", None));
335 
336     // Create the VM first
337     let cpu_count: u8 = 4;
338     let request_body = guest.api_create_body(
339         cpu_count,
340         direct_kernel_boot_path().to_str().unwrap(),
341         DIRECT_KERNEL_BOOT_CMDLINE,
342     );
343 
344     let temp_config_path = guest.tmp_dir.as_path().join("config");
345     std::fs::write(&temp_config_path, request_body).unwrap();
346     let create_config = temp_config_path.as_os_str().to_str().unwrap();
347 
348     assert!(target_api.remote_command("create", Some(create_config)));
349 
350     // Then boot it
351     assert!(target_api.remote_command("boot", None));
352     thread::sleep(std::time::Duration::new(20, 0));
353 
354     let r = std::panic::catch_unwind(|| {
355         // Check that the VM booted as expected
356         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
357         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
358 
359         // We now pause the VM
360         assert!(target_api.remote_command("pause", None));
361 
362         // Check pausing again fails
363         assert!(!target_api.remote_command("pause", None));
364 
365         thread::sleep(std::time::Duration::new(2, 0));
366 
367         // SSH into the VM should fail
368         ssh_command_ip(
369             "grep -c processor /proc/cpuinfo",
370             &guest.network.guest_ip,
371             2,
372             5,
373         )
374         .unwrap_err();
375 
376         // Resume the VM
377         assert!(target_api.remote_command("resume", None));
378 
379         // Check resuming again fails
380         assert!(!target_api.remote_command("resume", None));
381 
382         thread::sleep(std::time::Duration::new(2, 0));
383 
384         // Now we should be able to SSH back in and get the right number of CPUs
385         assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
386     });
387 
388     kill_child(&mut child);
389     let output = child.wait_with_output().unwrap();
390 
391     handle_child_output(r, &output);
392 }
393 
394 fn _test_pty_interaction(pty_path: PathBuf) {
395     let mut cf = std::fs::OpenOptions::new()
396         .write(true)
397         .read(true)
398         .open(pty_path)
399         .unwrap();
400 
401     // Some dumb sleeps but we don't want to write
402     // before the console is up and we don't want
403     // to try and write the next line before the
404     // login process is ready.
405     thread::sleep(std::time::Duration::new(5, 0));
406     assert_eq!(cf.write(b"cloud\n").unwrap(), 6);
407     thread::sleep(std::time::Duration::new(2, 0));
408     assert_eq!(cf.write(b"cloud123\n").unwrap(), 9);
409     thread::sleep(std::time::Duration::new(2, 0));
410     assert_eq!(cf.write(b"echo test_pty_console\n").unwrap(), 22);
411     thread::sleep(std::time::Duration::new(2, 0));
412 
413     // read pty and ensure they have a login shell
414     // some fairly hacky workarounds to avoid looping
415     // forever in case the channel is blocked getting output
416     let ptyc = pty_read(cf);
417     let mut empty = 0;
418     let mut prev = String::new();
419     loop {
420         thread::sleep(std::time::Duration::new(2, 0));
421         match ptyc.try_recv() {
422             Ok(line) => {
423                 empty = 0;
424                 prev = prev + &line;
425                 if prev.contains("test_pty_console") {
426                     break;
427                 }
428             }
429             Err(mpsc::TryRecvError::Empty) => {
430                 empty += 1;
431                 assert!(empty <= 5, "No login on pty");
432             }
433             _ => {
434                 panic!("No login on pty")
435             }
436         }
437     }
438 }
439 
440 fn prepare_virtiofsd(tmp_dir: &TempDir, shared_dir: &str) -> (std::process::Child, String) {
441     let mut workload_path = dirs::home_dir().unwrap();
442     workload_path.push("workloads");
443 
444     let mut virtiofsd_path = workload_path;
445     virtiofsd_path.push("virtiofsd");
446     let virtiofsd_path = String::from(virtiofsd_path.to_str().unwrap());
447 
448     let virtiofsd_socket_path =
449         String::from(tmp_dir.as_path().join("virtiofs.sock").to_str().unwrap());
450 
451     // Start the daemon
452     let child = Command::new(virtiofsd_path.as_str())
453         .args(["--shared-dir", shared_dir])
454         .args(["--socket-path", virtiofsd_socket_path.as_str()])
455         .args(["--cache", "never"])
456         .spawn()
457         .unwrap();
458 
459     thread::sleep(std::time::Duration::new(10, 0));
460 
461     (child, virtiofsd_socket_path)
462 }
463 
464 fn prepare_vubd(
465     tmp_dir: &TempDir,
466     blk_img: &str,
467     num_queues: usize,
468     rdonly: bool,
469     direct: bool,
470 ) -> (std::process::Child, String) {
471     let mut workload_path = dirs::home_dir().unwrap();
472     workload_path.push("workloads");
473 
474     let mut blk_file_path = workload_path;
475     blk_file_path.push(blk_img);
476     let blk_file_path = String::from(blk_file_path.to_str().unwrap());
477 
478     let vubd_socket_path = String::from(tmp_dir.as_path().join("vub.sock").to_str().unwrap());
479 
480     // Start the daemon
481     let child = Command::new(clh_command("vhost_user_block"))
482         .args([
483             "--block-backend",
484             format!(
485                 "path={blk_file_path},socket={vubd_socket_path},num_queues={num_queues},readonly={rdonly},direct={direct}"
486             )
487             .as_str(),
488         ])
489         .spawn()
490         .unwrap();
491 
492     thread::sleep(std::time::Duration::new(10, 0));
493 
494     (child, vubd_socket_path)
495 }
496 
497 fn temp_vsock_path(tmp_dir: &TempDir) -> String {
498     String::from(tmp_dir.as_path().join("vsock").to_str().unwrap())
499 }
500 
501 fn temp_api_path(tmp_dir: &TempDir) -> String {
502     String::from(
503         tmp_dir
504             .as_path()
505             .join("cloud-hypervisor.sock")
506             .to_str()
507             .unwrap(),
508     )
509 }
510 
511 fn temp_event_monitor_path(tmp_dir: &TempDir) -> String {
512     String::from(tmp_dir.as_path().join("event.json").to_str().unwrap())
513 }
514 
515 // Creates the directory and returns the path.
516 fn temp_snapshot_dir_path(tmp_dir: &TempDir) -> String {
517     let snapshot_dir = String::from(tmp_dir.as_path().join("snapshot").to_str().unwrap());
518     std::fs::create_dir(&snapshot_dir).unwrap();
519     snapshot_dir
520 }
521 
522 fn temp_vmcore_file_path(tmp_dir: &TempDir) -> String {
523     let vmcore_file = String::from(tmp_dir.as_path().join("vmcore").to_str().unwrap());
524     vmcore_file
525 }
526 
527 // Creates the path for direct kernel boot and return the path.
528 // For x86_64, this function returns the vmlinux kernel path.
529 // For AArch64, this function returns the PE kernel path.
530 fn direct_kernel_boot_path() -> PathBuf {
531     let mut workload_path = dirs::home_dir().unwrap();
532     workload_path.push("workloads");
533 
534     let mut kernel_path = workload_path;
535     #[cfg(target_arch = "x86_64")]
536     kernel_path.push("vmlinux");
537     #[cfg(target_arch = "aarch64")]
538     kernel_path.push("Image");
539 
540     kernel_path
541 }
542 
543 fn edk2_path() -> PathBuf {
544     let mut workload_path = dirs::home_dir().unwrap();
545     workload_path.push("workloads");
546     let mut edk2_path = workload_path;
547     edk2_path.push(OVMF_NAME);
548 
549     edk2_path
550 }
551 
552 fn cloud_hypervisor_release_path() -> String {
553     let mut workload_path = dirs::home_dir().unwrap();
554     workload_path.push("workloads");
555 
556     let mut ch_release_path = workload_path;
557     #[cfg(target_arch = "x86_64")]
558     ch_release_path.push("cloud-hypervisor-static");
559     #[cfg(target_arch = "aarch64")]
560     ch_release_path.push("cloud-hypervisor-static-aarch64");
561 
562     ch_release_path.into_os_string().into_string().unwrap()
563 }
564 
565 fn prepare_vhost_user_net_daemon(
566     tmp_dir: &TempDir,
567     ip: &str,
568     tap: Option<&str>,
569     mtu: Option<u16>,
570     num_queues: usize,
571     client_mode: bool,
572 ) -> (std::process::Command, String) {
573     let vunet_socket_path = String::from(tmp_dir.as_path().join("vunet.sock").to_str().unwrap());
574 
575     // Start the daemon
576     let mut net_params = format!(
577         "ip={ip},mask=255.255.255.0,socket={vunet_socket_path},num_queues={num_queues},queue_size=1024,client={client_mode}"
578     );
579 
580     if let Some(tap) = tap {
581         net_params.push_str(format!(",tap={tap}").as_str());
582     }
583 
584     if let Some(mtu) = mtu {
585         net_params.push_str(format!(",mtu={mtu}").as_str());
586     }
587 
588     let mut command = Command::new(clh_command("vhost_user_net"));
589     command.args(["--net-backend", net_params.as_str()]);
590 
591     (command, vunet_socket_path)
592 }
593 
594 fn prepare_swtpm_daemon(tmp_dir: &TempDir) -> (std::process::Command, String) {
595     let swtpm_tpm_dir = String::from(tmp_dir.as_path().join("swtpm").to_str().unwrap());
596     let swtpm_socket_path = String::from(
597         tmp_dir
598             .as_path()
599             .join("swtpm")
600             .join("swtpm.sock")
601             .to_str()
602             .unwrap(),
603     );
604     std::fs::create_dir(&swtpm_tpm_dir).unwrap();
605 
606     let mut swtpm_command = Command::new("swtpm");
607     let swtpm_args = [
608         "socket",
609         "--tpmstate",
610         &format!("dir={swtpm_tpm_dir}"),
611         "--ctrl",
612         &format!("type=unixio,path={swtpm_socket_path}"),
613         "--flags",
614         "startup-clear",
615         "--tpm2",
616     ];
617     swtpm_command.args(swtpm_args);
618 
619     (swtpm_command, swtpm_socket_path)
620 }
621 
622 fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool {
623     let mut cmd = Command::new(clh_command("ch-remote"));
624     cmd.args([&format!("--api-socket={api_socket}"), command]);
625 
626     if let Some(arg) = arg {
627         cmd.arg(arg);
628     }
629     let output = cmd.output().unwrap();
630     if output.status.success() {
631         true
632     } else {
633         eprintln!("Error running ch-remote command: {:?}", &cmd);
634         let stderr = String::from_utf8_lossy(&output.stderr);
635         eprintln!("stderr: {stderr}");
636         false
637     }
638 }
639 
640 fn remote_command_w_output(api_socket: &str, command: &str, arg: Option<&str>) -> (bool, Vec<u8>) {
641     let mut cmd = Command::new(clh_command("ch-remote"));
642     cmd.args([&format!("--api-socket={api_socket}"), command]);
643 
644     if let Some(arg) = arg {
645         cmd.arg(arg);
646     }
647 
648     let output = cmd.output().expect("Failed to launch ch-remote");
649 
650     (output.status.success(), output.stdout)
651 }
652 
653 fn resize_command(
654     api_socket: &str,
655     desired_vcpus: Option<u8>,
656     desired_ram: Option<usize>,
657     desired_balloon: Option<usize>,
658     event_file: Option<&str>,
659 ) -> bool {
660     let mut cmd = Command::new(clh_command("ch-remote"));
661     cmd.args([&format!("--api-socket={api_socket}"), "resize"]);
662 
663     if let Some(desired_vcpus) = desired_vcpus {
664         cmd.arg(format!("--cpus={desired_vcpus}"));
665     }
666 
667     if let Some(desired_ram) = desired_ram {
668         cmd.arg(format!("--memory={desired_ram}"));
669     }
670 
671     if let Some(desired_balloon) = desired_balloon {
672         cmd.arg(format!("--balloon={desired_balloon}"));
673     }
674 
675     let ret = cmd.status().expect("Failed to launch ch-remote").success();
676 
677     if let Some(event_path) = event_file {
678         let latest_events = [
679             &MetaEvent {
680                 event: "resizing".to_string(),
681                 device_id: None,
682             },
683             &MetaEvent {
684                 event: "resized".to_string(),
685                 device_id: None,
686             },
687         ];
688         // See: #5938
689         thread::sleep(std::time::Duration::new(1, 0));
690         assert!(check_latest_events_exact(&latest_events, event_path));
691     }
692 
693     ret
694 }
695 
696 fn resize_zone_command(api_socket: &str, id: &str, desired_size: &str) -> bool {
697     let mut cmd = Command::new(clh_command("ch-remote"));
698     cmd.args([
699         &format!("--api-socket={api_socket}"),
700         "resize-zone",
701         &format!("--id={id}"),
702         &format!("--size={desired_size}"),
703     ]);
704 
705     cmd.status().expect("Failed to launch ch-remote").success()
706 }
707 
708 // setup OVS-DPDK bridge and ports
709 fn setup_ovs_dpdk() {
710     // setup OVS-DPDK
711     assert!(exec_host_command_status("service openvswitch-switch start").success());
712     assert!(exec_host_command_status("ovs-vsctl init").success());
713     assert!(
714         exec_host_command_status("ovs-vsctl set Open_vSwitch . other_config:dpdk-init=true")
715             .success()
716     );
717     assert!(exec_host_command_status("service openvswitch-switch restart").success());
718 
719     // Create OVS-DPDK bridge and ports
720     assert!(exec_host_command_status(
721         "ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev",
722     )
723     .success());
724     assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient1").success());
725     assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient2").success());
726     assert!(exec_host_command_status("ip link set up dev ovsbr0").success());
727     assert!(exec_host_command_status("service openvswitch-switch restart").success());
728 }
729 fn cleanup_ovs_dpdk() {
730     assert!(exec_host_command_status("ovs-vsctl del-br ovsbr0").success());
731     exec_host_command_status("rm -f ovs-vsctl /tmp/dpdkvhostclient1 /tmp/dpdkvhostclient2");
732 }
733 // Setup two guests and ensure they are connected through ovs-dpdk
734 fn setup_ovs_dpdk_guests(
735     guest1: &Guest,
736     guest2: &Guest,
737     api_socket: &str,
738     release_binary: bool,
739 ) -> (Child, Child) {
740     setup_ovs_dpdk();
741 
742     let clh_path = if !release_binary {
743         clh_command("cloud-hypervisor")
744     } else {
745         cloud_hypervisor_release_path()
746     };
747 
748     let mut child1 = GuestCommand::new_with_binary_path(guest1, &clh_path)
749                     .args(["--cpus", "boot=2"])
750                     .args(["--memory", "size=0,shared=on"])
751                     .args(["--memory-zone", "id=mem0,size=1G,shared=on,host_numa_node=0"])
752                     .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
753                     .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
754                     .default_disks()
755                     .args(["--net", guest1.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient1,num_queues=2,queue_size=256,vhost_mode=server"])
756                     .capture_output()
757                     .spawn()
758                     .unwrap();
759 
760     #[cfg(target_arch = "x86_64")]
761     let guest_net_iface = "ens5";
762     #[cfg(target_arch = "aarch64")]
763     let guest_net_iface = "enp0s5";
764 
765     let r = std::panic::catch_unwind(|| {
766         guest1.wait_vm_boot(None).unwrap();
767 
768         guest1
769             .ssh_command(&format!(
770                 "sudo ip addr add 172.100.0.1/24 dev {guest_net_iface}"
771             ))
772             .unwrap();
773         guest1
774             .ssh_command(&format!("sudo ip link set up dev {guest_net_iface}"))
775             .unwrap();
776 
777         let guest_ip = guest1.network.guest_ip.clone();
778         thread::spawn(move || {
779             ssh_command_ip(
780                 "nc -l 12345",
781                 &guest_ip,
782                 DEFAULT_SSH_RETRIES,
783                 DEFAULT_SSH_TIMEOUT,
784             )
785             .unwrap();
786         });
787     });
788     if r.is_err() {
789         cleanup_ovs_dpdk();
790 
791         let _ = child1.kill();
792         let output = child1.wait_with_output().unwrap();
793         handle_child_output(r, &output);
794         panic!("Test should already be failed/panicked"); // To explicitly mark this block never return
795     }
796 
797     let mut child2 = GuestCommand::new_with_binary_path(guest2, &clh_path)
798                     .args(["--api-socket", api_socket])
799                     .args(["--cpus", "boot=2"])
800                     .args(["--memory", "size=0,shared=on"])
801                     .args(["--memory-zone", "id=mem0,size=1G,shared=on,host_numa_node=0"])
802                     .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
803                     .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
804                     .default_disks()
805                     .args(["--net", guest2.default_net_string().as_str(), "vhost_user=true,socket=/tmp/dpdkvhostclient2,num_queues=2,queue_size=256,vhost_mode=server"])
806                     .capture_output()
807                     .spawn()
808                     .unwrap();
809 
810     let r = std::panic::catch_unwind(|| {
811         guest2.wait_vm_boot(None).unwrap();
812 
813         guest2
814             .ssh_command(&format!(
815                 "sudo ip addr add 172.100.0.2/24 dev {guest_net_iface}"
816             ))
817             .unwrap();
818         guest2
819             .ssh_command(&format!("sudo ip link set up dev {guest_net_iface}"))
820             .unwrap();
821 
822         // Check the connection works properly between the two VMs
823         guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap();
824     });
825     if r.is_err() {
826         cleanup_ovs_dpdk();
827 
828         let _ = child1.kill();
829         let _ = child2.kill();
830         let output = child2.wait_with_output().unwrap();
831         handle_child_output(r, &output);
832         panic!("Test should already be failed/panicked"); // To explicitly mark this block never return
833     }
834 
835     (child1, child2)
836 }
837 
838 enum FwType {
839     Ovmf,
840     RustHypervisorFirmware,
841 }
842 
843 fn fw_path(_fw_type: FwType) -> String {
844     let mut workload_path = dirs::home_dir().unwrap();
845     workload_path.push("workloads");
846 
847     let mut fw_path = workload_path;
848     #[cfg(target_arch = "aarch64")]
849     fw_path.push("CLOUDHV_EFI.fd");
850     #[cfg(target_arch = "x86_64")]
851     {
852         match _fw_type {
853             FwType::Ovmf => fw_path.push(OVMF_NAME),
854             FwType::RustHypervisorFirmware => fw_path.push("hypervisor-fw"),
855         }
856     }
857 
858     fw_path.to_str().unwrap().to_string()
859 }
860 
861 #[derive(Debug)]
862 struct MetaEvent {
863     event: String,
864     device_id: Option<String>,
865 }
866 
867 impl MetaEvent {
868     pub fn match_with_json_event(&self, v: &serde_json::Value) -> bool {
869         let mut matched = false;
870         if v["event"].as_str().unwrap() == self.event {
871             if let Some(device_id) = &self.device_id {
872                 if v["properties"]["id"].as_str().unwrap() == device_id {
873                     matched = true
874                 }
875             } else {
876                 matched = true;
877             }
878         }
879         matched
880     }
881 }
882 
883 // Parse the event_monitor file based on the format that each event
884 // is followed by a double newline
885 fn parse_event_file(event_file: &str) -> Vec<serde_json::Value> {
886     let content = fs::read(event_file).unwrap();
887     let mut ret = Vec::new();
888     for entry in String::from_utf8_lossy(&content)
889         .trim()
890         .split("\n\n")
891         .collect::<Vec<&str>>()
892     {
893         ret.push(serde_json::from_str(entry).unwrap());
894     }
895 
896     ret
897 }
898 
899 // Return true if all events from the input 'expected_events' are matched sequentially
900 // with events from the 'event_file'
901 fn check_sequential_events(expected_events: &[&MetaEvent], event_file: &str) -> bool {
902     let json_events = parse_event_file(event_file);
903     let len = expected_events.len();
904     let mut idx = 0;
905     for e in &json_events {
906         if idx == len {
907             break;
908         }
909         if expected_events[idx].match_with_json_event(e) {
910             idx += 1;
911         }
912     }
913 
914     let ret = idx == len;
915 
916     if !ret {
917         eprintln!(
918             "\n\n==== Start 'check_sequential_events' failed ==== \
919              \n\nexpected_events={expected_events:?}\nactual_events={json_events:?} \
920              \n\n==== End 'check_sequential_events' failed ====",
921         );
922     }
923 
924     ret
925 }
926 
927 // Return true if all events from the input 'expected_events' are matched exactly
928 // with events from the 'event_file'
929 fn check_sequential_events_exact(expected_events: &[&MetaEvent], event_file: &str) -> bool {
930     let json_events = parse_event_file(event_file);
931     assert!(expected_events.len() <= json_events.len());
932     let json_events = &json_events[..expected_events.len()];
933 
934     for (idx, e) in json_events.iter().enumerate() {
935         if !expected_events[idx].match_with_json_event(e) {
936             eprintln!(
937                 "\n\n==== Start 'check_sequential_events_exact' failed ==== \
938                  \n\nexpected_events={expected_events:?}\nactual_events={json_events:?} \
939                  \n\n==== End 'check_sequential_events_exact' failed ====",
940             );
941 
942             return false;
943         }
944     }
945 
946     true
947 }
948 
949 // Return true if events from the input 'latest_events' are matched exactly
950 // with the most recent events from the 'event_file'
951 fn check_latest_events_exact(latest_events: &[&MetaEvent], event_file: &str) -> bool {
952     let json_events = parse_event_file(event_file);
953     assert!(latest_events.len() <= json_events.len());
954     let json_events = &json_events[(json_events.len() - latest_events.len())..];
955 
956     for (idx, e) in json_events.iter().enumerate() {
957         if !latest_events[idx].match_with_json_event(e) {
958             eprintln!(
959                 "\n\n==== Start 'check_latest_events_exact' failed ==== \
960                  \n\nexpected_events={latest_events:?}\nactual_events={json_events:?} \
961                  \n\n==== End 'check_latest_events_exact' failed ====",
962             );
963 
964             return false;
965         }
966     }
967 
968     true
969 }
970 
971 fn test_cpu_topology(threads_per_core: u8, cores_per_package: u8, packages: u8, use_fw: bool) {
972     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
973     let guest = Guest::new(Box::new(focal));
974     let total_vcpus = threads_per_core * cores_per_package * packages;
975     let direct_kernel_boot_path = direct_kernel_boot_path();
976     let mut kernel_path = direct_kernel_boot_path.to_str().unwrap();
977     let fw_path = fw_path(FwType::RustHypervisorFirmware);
978     if use_fw {
979         kernel_path = fw_path.as_str();
980     }
981 
982     let mut child = GuestCommand::new(&guest)
983         .args([
984             "--cpus",
985             &format!(
986                 "boot={total_vcpus},topology={threads_per_core}:{cores_per_package}:1:{packages}"
987             ),
988         ])
989         .args(["--memory", "size=512M"])
990         .args(["--kernel", kernel_path])
991         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
992         .default_disks()
993         .default_net()
994         .capture_output()
995         .spawn()
996         .unwrap();
997 
998     let r = std::panic::catch_unwind(|| {
999         guest.wait_vm_boot(None).unwrap();
1000         assert_eq!(
1001             guest.get_cpu_count().unwrap_or_default(),
1002             u32::from(total_vcpus)
1003         );
1004         assert_eq!(
1005             guest
1006                 .ssh_command("lscpu | grep \"per core\" | cut -f 2 -d \":\" | sed \"s# *##\"")
1007                 .unwrap()
1008                 .trim()
1009                 .parse::<u8>()
1010                 .unwrap_or(0),
1011             threads_per_core
1012         );
1013 
1014         assert_eq!(
1015             guest
1016                 .ssh_command("lscpu | grep \"per socket\" | cut -f 2 -d \":\" | sed \"s# *##\"")
1017                 .unwrap()
1018                 .trim()
1019                 .parse::<u8>()
1020                 .unwrap_or(0),
1021             cores_per_package
1022         );
1023 
1024         assert_eq!(
1025             guest
1026                 .ssh_command("lscpu | grep \"Socket\" | cut -f 2 -d \":\" | sed \"s# *##\"")
1027                 .unwrap()
1028                 .trim()
1029                 .parse::<u8>()
1030                 .unwrap_or(0),
1031             packages
1032         );
1033 
1034         #[cfg(target_arch = "x86_64")]
1035         {
1036             let mut cpu_id = 0;
1037             for package_id in 0..packages {
1038                 for core_id in 0..cores_per_package {
1039                     for _ in 0..threads_per_core {
1040                         assert_eq!(
1041                             guest
1042                                 .ssh_command(&format!("cat /sys/devices/system/cpu/cpu{cpu_id}/topology/physical_package_id"))
1043                                 .unwrap()
1044                                 .trim()
1045                                 .parse::<u8>()
1046                                 .unwrap_or(0),
1047                             package_id
1048                         );
1049 
1050                         assert_eq!(
1051                             guest
1052                                 .ssh_command(&format!(
1053                                     "cat /sys/devices/system/cpu/cpu{cpu_id}/topology/core_id"
1054                                 ))
1055                                 .unwrap()
1056                                 .trim()
1057                                 .parse::<u8>()
1058                                 .unwrap_or(0),
1059                             core_id
1060                         );
1061 
1062                         cpu_id += 1;
1063                     }
1064                 }
1065             }
1066         }
1067     });
1068 
1069     kill_child(&mut child);
1070     let output = child.wait_with_output().unwrap();
1071 
1072     handle_child_output(r, &output);
1073 }
1074 
1075 #[allow(unused_variables)]
1076 fn _test_guest_numa_nodes(acpi: bool) {
1077     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1078     let guest = Guest::new(Box::new(focal));
1079     let api_socket = temp_api_path(&guest.tmp_dir);
1080     #[cfg(target_arch = "x86_64")]
1081     let kernel_path = direct_kernel_boot_path();
1082     #[cfg(target_arch = "aarch64")]
1083     let kernel_path = if acpi {
1084         edk2_path()
1085     } else {
1086         direct_kernel_boot_path()
1087     };
1088 
1089     let mut child = GuestCommand::new(&guest)
1090         .args(["--cpus", "boot=6,max=12"])
1091         .args(["--memory", "size=0,hotplug_method=virtio-mem"])
1092         .args([
1093             "--memory-zone",
1094             "id=mem0,size=1G,hotplug_size=3G",
1095             "id=mem1,size=2G,hotplug_size=3G",
1096             "id=mem2,size=3G,hotplug_size=3G",
1097         ])
1098         .args([
1099             "--numa",
1100             "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0",
1101             "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1",
1102             "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2",
1103         ])
1104         .args(["--kernel", kernel_path.to_str().unwrap()])
1105         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1106         .args(["--api-socket", &api_socket])
1107         .capture_output()
1108         .default_disks()
1109         .default_net()
1110         .spawn()
1111         .unwrap();
1112 
1113     let r = std::panic::catch_unwind(|| {
1114         guest.wait_vm_boot(None).unwrap();
1115 
1116         guest.check_numa_common(
1117             Some(&[960_000, 1_920_000, 2_880_000]),
1118             Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]),
1119             Some(&["10 15 20", "20 10 25", "25 30 10"]),
1120         );
1121 
1122         // AArch64 currently does not support hotplug, and therefore we only
1123         // test hotplug-related function on x86_64 here.
1124         #[cfg(target_arch = "x86_64")]
1125         {
1126             guest.enable_memory_hotplug();
1127 
1128             // Resize every memory zone and check each associated NUMA node
1129             // has been assigned the right amount of memory.
1130             resize_zone_command(&api_socket, "mem0", "4G");
1131             resize_zone_command(&api_socket, "mem1", "4G");
1132             resize_zone_command(&api_socket, "mem2", "4G");
1133             // Resize to the maximum amount of CPUs and check each NUMA
1134             // node has been assigned the right CPUs set.
1135             resize_command(&api_socket, Some(12), None, None, None);
1136             thread::sleep(std::time::Duration::new(5, 0));
1137 
1138             guest.check_numa_common(
1139                 Some(&[3_840_000, 3_840_000, 3_840_000]),
1140                 Some(&[vec![0, 1, 2, 9], vec![3, 4, 6, 7, 8], vec![5, 10, 11]]),
1141                 None,
1142             );
1143         }
1144     });
1145 
1146     kill_child(&mut child);
1147     let output = child.wait_with_output().unwrap();
1148 
1149     handle_child_output(r, &output);
1150 }
1151 
1152 #[allow(unused_variables)]
1153 fn _test_power_button(acpi: bool) {
1154     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1155     let guest = Guest::new(Box::new(focal));
1156     let mut cmd = GuestCommand::new(&guest);
1157     let api_socket = temp_api_path(&guest.tmp_dir);
1158 
1159     #[cfg(target_arch = "x86_64")]
1160     let kernel_path = direct_kernel_boot_path();
1161     #[cfg(target_arch = "aarch64")]
1162     let kernel_path = if acpi {
1163         edk2_path()
1164     } else {
1165         direct_kernel_boot_path()
1166     };
1167 
1168     cmd.args(["--cpus", "boot=1"])
1169         .args(["--memory", "size=512M"])
1170         .args(["--kernel", kernel_path.to_str().unwrap()])
1171         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1172         .capture_output()
1173         .default_disks()
1174         .default_net()
1175         .args(["--api-socket", &api_socket]);
1176 
1177     let child = cmd.spawn().unwrap();
1178 
1179     let r = std::panic::catch_unwind(|| {
1180         guest.wait_vm_boot(None).unwrap();
1181         assert!(remote_command(&api_socket, "power-button", None));
1182     });
1183 
1184     let output = child.wait_with_output().unwrap();
1185     assert!(output.status.success());
1186     handle_child_output(r, &output);
1187 }
1188 
1189 type PrepareNetDaemon = dyn Fn(
1190     &TempDir,
1191     &str,
1192     Option<&str>,
1193     Option<u16>,
1194     usize,
1195     bool,
1196 ) -> (std::process::Command, String);
1197 
1198 fn test_vhost_user_net(
1199     tap: Option<&str>,
1200     num_queues: usize,
1201     prepare_daemon: &PrepareNetDaemon,
1202     generate_host_mac: bool,
1203     client_mode_daemon: bool,
1204 ) {
1205     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1206     let guest = Guest::new(Box::new(focal));
1207     let api_socket = temp_api_path(&guest.tmp_dir);
1208 
1209     let kernel_path = direct_kernel_boot_path();
1210 
1211     let host_mac = if generate_host_mac {
1212         Some(MacAddr::local_random())
1213     } else {
1214         None
1215     };
1216 
1217     let mtu = Some(3000);
1218 
1219     let (mut daemon_command, vunet_socket_path) = prepare_daemon(
1220         &guest.tmp_dir,
1221         &guest.network.host_ip,
1222         tap,
1223         mtu,
1224         num_queues,
1225         client_mode_daemon,
1226     );
1227 
1228     let net_params = format!(
1229         "vhost_user=true,mac={},socket={},num_queues={},queue_size=1024{},vhost_mode={},mtu=3000",
1230         guest.network.guest_mac,
1231         vunet_socket_path,
1232         num_queues,
1233         if let Some(host_mac) = host_mac {
1234             format!(",host_mac={host_mac}")
1235         } else {
1236             "".to_owned()
1237         },
1238         if client_mode_daemon {
1239             "server"
1240         } else {
1241             "client"
1242         },
1243     );
1244 
1245     let mut ch_command = GuestCommand::new(&guest);
1246     ch_command
1247         .args(["--cpus", format!("boot={}", num_queues / 2).as_str()])
1248         .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"])
1249         .args(["--kernel", kernel_path.to_str().unwrap()])
1250         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1251         .default_disks()
1252         .args(["--net", net_params.as_str()])
1253         .args(["--api-socket", &api_socket])
1254         .capture_output();
1255 
1256     let mut daemon_child: std::process::Child;
1257     let mut child: std::process::Child;
1258 
1259     if client_mode_daemon {
1260         child = ch_command.spawn().unwrap();
1261         // Make sure the VMM is waiting for the backend to connect
1262         thread::sleep(std::time::Duration::new(10, 0));
1263         daemon_child = daemon_command.spawn().unwrap();
1264     } else {
1265         daemon_child = daemon_command.spawn().unwrap();
1266         // Make sure the backend is waiting for the VMM to connect
1267         thread::sleep(std::time::Duration::new(10, 0));
1268         child = ch_command.spawn().unwrap();
1269     }
1270 
1271     let r = std::panic::catch_unwind(|| {
1272         guest.wait_vm_boot(None).unwrap();
1273 
1274         if let Some(tap_name) = tap {
1275             let tap_count = exec_host_command_output(&format!("ip link | grep -c {tap_name}"));
1276             assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1");
1277         }
1278 
1279         if let Some(host_mac) = tap {
1280             let mac_count = exec_host_command_output(&format!("ip link | grep -c {host_mac}"));
1281             assert_eq!(String::from_utf8_lossy(&mac_count.stdout).trim(), "1");
1282         }
1283 
1284         #[cfg(target_arch = "aarch64")]
1285         let iface = "enp0s4";
1286         #[cfg(target_arch = "x86_64")]
1287         let iface = "ens4";
1288 
1289         assert_eq!(
1290             guest
1291                 .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str())
1292                 .unwrap()
1293                 .trim(),
1294             "3000"
1295         );
1296 
1297         // 1 network interface + default localhost ==> 2 interfaces
1298         // It's important to note that this test is fully exercising the
1299         // vhost-user-net implementation and the associated backend since
1300         // it does not define any --net network interface. That means all
1301         // the ssh communication in that test happens through the network
1302         // interface backed by vhost-user-net.
1303         assert_eq!(
1304             guest
1305                 .ssh_command("ip -o link | wc -l")
1306                 .unwrap()
1307                 .trim()
1308                 .parse::<u32>()
1309                 .unwrap_or_default(),
1310             2
1311         );
1312 
1313         // The following pci devices will appear on guest with PCI-MSI
1314         // interrupt vectors assigned.
1315         // 1 virtio-console with 3 vectors: config, Rx, Tx
1316         // 1 virtio-blk     with 2 vectors: config, Request
1317         // 1 virtio-blk     with 2 vectors: config, Request
1318         // 1 virtio-rng     with 2 vectors: config, Request
1319         // Since virtio-net has 2 queue pairs, its vectors is as follows:
1320         // 1 virtio-net     with 5 vectors: config, Rx (2), Tx (2)
1321         // Based on the above, the total vectors should 14.
1322         #[cfg(target_arch = "x86_64")]
1323         let grep_cmd = "grep -c PCI-MSI /proc/interrupts";
1324         #[cfg(target_arch = "aarch64")]
1325         let grep_cmd = "grep -c ITS-PCI-MSIX /proc/interrupts";
1326         assert_eq!(
1327             guest
1328                 .ssh_command(grep_cmd)
1329                 .unwrap()
1330                 .trim()
1331                 .parse::<u32>()
1332                 .unwrap_or_default(),
1333             10 + (num_queues as u32)
1334         );
1335 
1336         // ACPI feature is needed.
1337         #[cfg(target_arch = "x86_64")]
1338         {
1339             guest.enable_memory_hotplug();
1340 
1341             // Add RAM to the VM
1342             let desired_ram = 1024 << 20;
1343             resize_command(&api_socket, None, Some(desired_ram), None, None);
1344 
1345             thread::sleep(std::time::Duration::new(10, 0));
1346 
1347             // Here by simply checking the size (through ssh), we validate
1348             // the connection is still working, which means vhost-user-net
1349             // keeps working after the resize.
1350             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
1351         }
1352     });
1353 
1354     kill_child(&mut child);
1355     let output = child.wait_with_output().unwrap();
1356 
1357     thread::sleep(std::time::Duration::new(5, 0));
1358     let _ = daemon_child.kill();
1359     let _ = daemon_child.wait();
1360 
1361     handle_child_output(r, &output);
1362 }
1363 
1364 type PrepareBlkDaemon = dyn Fn(&TempDir, &str, usize, bool, bool) -> (std::process::Child, String);
1365 
1366 fn test_vhost_user_blk(
1367     num_queues: usize,
1368     readonly: bool,
1369     direct: bool,
1370     prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>,
1371 ) {
1372     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1373     let guest = Guest::new(Box::new(focal));
1374     let api_socket = temp_api_path(&guest.tmp_dir);
1375 
1376     let kernel_path = direct_kernel_boot_path();
1377 
1378     let (blk_params, daemon_child) = {
1379         let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap();
1380         // Start the daemon
1381         let (daemon_child, vubd_socket_path) =
1382             prepare_daemon(&guest.tmp_dir, "blk.img", num_queues, readonly, direct);
1383 
1384         (
1385             format!(
1386                 "vhost_user=true,socket={vubd_socket_path},num_queues={num_queues},queue_size=128",
1387             ),
1388             Some(daemon_child),
1389         )
1390     };
1391 
1392     let mut child = GuestCommand::new(&guest)
1393         .args(["--cpus", format!("boot={num_queues}").as_str()])
1394         .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"])
1395         .args(["--kernel", kernel_path.to_str().unwrap()])
1396         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1397         .args([
1398             "--disk",
1399             format!(
1400                 "path={}",
1401                 guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
1402             )
1403             .as_str(),
1404             format!(
1405                 "path={}",
1406                 guest.disk_config.disk(DiskType::CloudInit).unwrap()
1407             )
1408             .as_str(),
1409             blk_params.as_str(),
1410         ])
1411         .default_net()
1412         .args(["--api-socket", &api_socket])
1413         .capture_output()
1414         .spawn()
1415         .unwrap();
1416 
1417     let r = std::panic::catch_unwind(|| {
1418         guest.wait_vm_boot(None).unwrap();
1419 
1420         // Check both if /dev/vdc exists and if the block size is 16M.
1421         assert_eq!(
1422             guest
1423                 .ssh_command("lsblk | grep vdc | grep -c 16M")
1424                 .unwrap()
1425                 .trim()
1426                 .parse::<u32>()
1427                 .unwrap_or_default(),
1428             1
1429         );
1430 
1431         // Check if this block is RO or RW.
1432         assert_eq!(
1433             guest
1434                 .ssh_command("lsblk | grep vdc | awk '{print $5}'")
1435                 .unwrap()
1436                 .trim()
1437                 .parse::<u32>()
1438                 .unwrap_or_default(),
1439             readonly as u32
1440         );
1441 
1442         // Check if the number of queues in /sys/block/vdc/mq matches the
1443         // expected num_queues.
1444         assert_eq!(
1445             guest
1446                 .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l")
1447                 .unwrap()
1448                 .trim()
1449                 .parse::<u32>()
1450                 .unwrap_or_default(),
1451             num_queues as u32
1452         );
1453 
1454         // Mount the device
1455         let mount_ro_rw_flag = if readonly { "ro,noload" } else { "rw" };
1456         guest.ssh_command("mkdir mount_image").unwrap();
1457         guest
1458             .ssh_command(
1459                 format!("sudo mount -o {mount_ro_rw_flag} -t ext4 /dev/vdc mount_image/").as_str(),
1460             )
1461             .unwrap();
1462 
1463         // Check the content of the block device. The file "foo" should
1464         // contain "bar".
1465         assert_eq!(
1466             guest.ssh_command("cat mount_image/foo").unwrap().trim(),
1467             "bar"
1468         );
1469 
1470         // ACPI feature is needed.
1471         #[cfg(target_arch = "x86_64")]
1472         {
1473             guest.enable_memory_hotplug();
1474 
1475             // Add RAM to the VM
1476             let desired_ram = 1024 << 20;
1477             resize_command(&api_socket, None, Some(desired_ram), None, None);
1478 
1479             thread::sleep(std::time::Duration::new(10, 0));
1480 
1481             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
1482 
1483             // Check again the content of the block device after the resize
1484             // has been performed.
1485             assert_eq!(
1486                 guest.ssh_command("cat mount_image/foo").unwrap().trim(),
1487                 "bar"
1488             );
1489         }
1490 
1491         // Unmount the device
1492         guest.ssh_command("sudo umount /dev/vdc").unwrap();
1493         guest.ssh_command("rm -r mount_image").unwrap();
1494     });
1495 
1496     kill_child(&mut child);
1497     let output = child.wait_with_output().unwrap();
1498 
1499     if let Some(mut daemon_child) = daemon_child {
1500         thread::sleep(std::time::Duration::new(5, 0));
1501         let _ = daemon_child.kill();
1502         let _ = daemon_child.wait();
1503     }
1504 
1505     handle_child_output(r, &output);
1506 }
1507 
1508 fn test_boot_from_vhost_user_blk(
1509     num_queues: usize,
1510     readonly: bool,
1511     direct: bool,
1512     prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>,
1513 ) {
1514     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1515     let guest = Guest::new(Box::new(focal));
1516 
1517     let kernel_path = direct_kernel_boot_path();
1518 
1519     let disk_path = guest.disk_config.disk(DiskType::OperatingSystem).unwrap();
1520 
1521     let (blk_boot_params, daemon_child) = {
1522         let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap();
1523         // Start the daemon
1524         let (daemon_child, vubd_socket_path) = prepare_daemon(
1525             &guest.tmp_dir,
1526             disk_path.as_str(),
1527             num_queues,
1528             readonly,
1529             direct,
1530         );
1531 
1532         (
1533             format!(
1534                 "vhost_user=true,socket={vubd_socket_path},num_queues={num_queues},queue_size=128",
1535             ),
1536             Some(daemon_child),
1537         )
1538     };
1539 
1540     let mut child = GuestCommand::new(&guest)
1541         .args(["--cpus", format!("boot={num_queues}").as_str()])
1542         .args(["--memory", "size=512M,shared=on"])
1543         .args(["--kernel", kernel_path.to_str().unwrap()])
1544         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1545         .args([
1546             "--disk",
1547             blk_boot_params.as_str(),
1548             format!(
1549                 "path={}",
1550                 guest.disk_config.disk(DiskType::CloudInit).unwrap()
1551             )
1552             .as_str(),
1553         ])
1554         .default_net()
1555         .capture_output()
1556         .spawn()
1557         .unwrap();
1558 
1559     let r = std::panic::catch_unwind(|| {
1560         guest.wait_vm_boot(None).unwrap();
1561 
1562         // Just check the VM booted correctly.
1563         assert_eq!(guest.get_cpu_count().unwrap_or_default(), num_queues as u32);
1564         assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
1565     });
1566     kill_child(&mut child);
1567     let output = child.wait_with_output().unwrap();
1568 
1569     if let Some(mut daemon_child) = daemon_child {
1570         thread::sleep(std::time::Duration::new(5, 0));
1571         let _ = daemon_child.kill();
1572         let _ = daemon_child.wait();
1573     }
1574 
1575     handle_child_output(r, &output);
1576 }
1577 
1578 fn _test_virtio_fs(
1579     prepare_daemon: &dyn Fn(&TempDir, &str) -> (std::process::Child, String),
1580     hotplug: bool,
1581     pci_segment: Option<u16>,
1582 ) {
1583     #[cfg(target_arch = "aarch64")]
1584     let focal_image = if hotplug {
1585         FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string()
1586     } else {
1587         FOCAL_IMAGE_NAME.to_string()
1588     };
1589     #[cfg(target_arch = "x86_64")]
1590     let focal_image = FOCAL_IMAGE_NAME.to_string();
1591     let focal = UbuntuDiskConfig::new(focal_image);
1592     let guest = Guest::new(Box::new(focal));
1593     let api_socket = temp_api_path(&guest.tmp_dir);
1594 
1595     let mut workload_path = dirs::home_dir().unwrap();
1596     workload_path.push("workloads");
1597 
1598     let mut shared_dir = workload_path;
1599     shared_dir.push("shared_dir");
1600 
1601     #[cfg(target_arch = "x86_64")]
1602     let kernel_path = direct_kernel_boot_path();
1603     #[cfg(target_arch = "aarch64")]
1604     let kernel_path = if hotplug {
1605         edk2_path()
1606     } else {
1607         direct_kernel_boot_path()
1608     };
1609 
1610     let (mut daemon_child, virtiofsd_socket_path) =
1611         prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap());
1612 
1613     let mut guest_command = GuestCommand::new(&guest);
1614     guest_command
1615         .args(["--cpus", "boot=1"])
1616         .args(["--memory", "size=512M,hotplug_size=2048M,shared=on"])
1617         .args(["--kernel", kernel_path.to_str().unwrap()])
1618         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1619         .default_disks()
1620         .default_net()
1621         .args(["--api-socket", &api_socket]);
1622     if pci_segment.is_some() {
1623         guest_command.args([
1624             "--platform",
1625             &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"),
1626         ]);
1627     }
1628 
1629     let fs_params = format!(
1630         "id=myfs0,tag=myfs,socket={},num_queues=1,queue_size=1024{}",
1631         virtiofsd_socket_path,
1632         if let Some(pci_segment) = pci_segment {
1633             format!(",pci_segment={pci_segment}")
1634         } else {
1635             "".to_owned()
1636         }
1637     );
1638 
1639     if !hotplug {
1640         guest_command.args(["--fs", fs_params.as_str()]);
1641     }
1642 
1643     let mut child = guest_command.capture_output().spawn().unwrap();
1644 
1645     let r = std::panic::catch_unwind(|| {
1646         guest.wait_vm_boot(None).unwrap();
1647 
1648         if hotplug {
1649             // Add fs to the VM
1650             let (cmd_success, cmd_output) =
1651                 remote_command_w_output(&api_socket, "add-fs", Some(&fs_params));
1652             assert!(cmd_success);
1653 
1654             if let Some(pci_segment) = pci_segment {
1655                 assert!(String::from_utf8_lossy(&cmd_output).contains(&format!(
1656                     "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}"
1657                 )));
1658             } else {
1659                 assert!(String::from_utf8_lossy(&cmd_output)
1660                     .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}"));
1661             }
1662 
1663             thread::sleep(std::time::Duration::new(10, 0));
1664         }
1665 
1666         // Mount shared directory through virtio_fs filesystem
1667         guest
1668             .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/")
1669             .unwrap();
1670 
1671         // Check file1 exists and its content is "foo"
1672         assert_eq!(
1673             guest.ssh_command("cat mount_dir/file1").unwrap().trim(),
1674             "foo"
1675         );
1676         // Check file2 does not exist
1677         guest
1678             .ssh_command("[ ! -f 'mount_dir/file2' ] || true")
1679             .unwrap();
1680 
1681         // Check file3 exists and its content is "bar"
1682         assert_eq!(
1683             guest.ssh_command("cat mount_dir/file3").unwrap().trim(),
1684             "bar"
1685         );
1686 
1687         // ACPI feature is needed.
1688         #[cfg(target_arch = "x86_64")]
1689         {
1690             guest.enable_memory_hotplug();
1691 
1692             // Add RAM to the VM
1693             let desired_ram = 1024 << 20;
1694             resize_command(&api_socket, None, Some(desired_ram), None, None);
1695 
1696             thread::sleep(std::time::Duration::new(30, 0));
1697             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
1698 
1699             // After the resize, check again that file1 exists and its
1700             // content is "foo".
1701             assert_eq!(
1702                 guest.ssh_command("cat mount_dir/file1").unwrap().trim(),
1703                 "foo"
1704             );
1705         }
1706 
1707         if hotplug {
1708             // Remove from VM
1709             guest.ssh_command("sudo umount mount_dir").unwrap();
1710             assert!(remote_command(&api_socket, "remove-device", Some("myfs0")));
1711         }
1712     });
1713 
1714     let (r, hotplug_daemon_child) = if r.is_ok() && hotplug {
1715         thread::sleep(std::time::Duration::new(10, 0));
1716         let (daemon_child, virtiofsd_socket_path) =
1717             prepare_daemon(&guest.tmp_dir, shared_dir.to_str().unwrap());
1718 
1719         let r = std::panic::catch_unwind(|| {
1720             thread::sleep(std::time::Duration::new(10, 0));
1721             let fs_params = format!(
1722                 "id=myfs0,tag=myfs,socket={},num_queues=1,queue_size=1024{}",
1723                 virtiofsd_socket_path,
1724                 if let Some(pci_segment) = pci_segment {
1725                     format!(",pci_segment={pci_segment}")
1726                 } else {
1727                     "".to_owned()
1728                 }
1729             );
1730 
1731             // Add back and check it works
1732             let (cmd_success, cmd_output) =
1733                 remote_command_w_output(&api_socket, "add-fs", Some(&fs_params));
1734             assert!(cmd_success);
1735             if let Some(pci_segment) = pci_segment {
1736                 assert!(String::from_utf8_lossy(&cmd_output).contains(&format!(
1737                     "{{\"id\":\"myfs0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}"
1738                 )));
1739             } else {
1740                 assert!(String::from_utf8_lossy(&cmd_output)
1741                     .contains("{\"id\":\"myfs0\",\"bdf\":\"0000:00:06.0\"}"));
1742             }
1743 
1744             thread::sleep(std::time::Duration::new(10, 0));
1745             // Mount shared directory through virtio_fs filesystem
1746             guest
1747                 .ssh_command("mkdir -p mount_dir && sudo mount -t virtiofs myfs mount_dir/")
1748                 .unwrap();
1749 
1750             // Check file1 exists and its content is "foo"
1751             assert_eq!(
1752                 guest.ssh_command("cat mount_dir/file1").unwrap().trim(),
1753                 "foo"
1754             );
1755         });
1756 
1757         (r, Some(daemon_child))
1758     } else {
1759         (r, None)
1760     };
1761 
1762     kill_child(&mut child);
1763     let output = child.wait_with_output().unwrap();
1764 
1765     let _ = daemon_child.kill();
1766     let _ = daemon_child.wait();
1767 
1768     if let Some(mut daemon_child) = hotplug_daemon_child {
1769         let _ = daemon_child.kill();
1770         let _ = daemon_child.wait();
1771     }
1772 
1773     handle_child_output(r, &output);
1774 }
1775 
1776 fn test_virtio_pmem(discard_writes: bool, specify_size: bool) {
1777     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1778     let guest = Guest::new(Box::new(focal));
1779 
1780     let kernel_path = direct_kernel_boot_path();
1781 
1782     let pmem_temp_file = TempFile::new().unwrap();
1783     pmem_temp_file.as_file().set_len(128 << 20).unwrap();
1784 
1785     std::process::Command::new("mkfs.ext4")
1786         .arg(pmem_temp_file.as_path())
1787         .output()
1788         .expect("Expect creating disk image to succeed");
1789 
1790     let mut child = GuestCommand::new(&guest)
1791         .args(["--cpus", "boot=1"])
1792         .args(["--memory", "size=512M"])
1793         .args(["--kernel", kernel_path.to_str().unwrap()])
1794         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1795         .default_disks()
1796         .default_net()
1797         .args([
1798             "--pmem",
1799             format!(
1800                 "file={}{}{}",
1801                 pmem_temp_file.as_path().to_str().unwrap(),
1802                 if specify_size { ",size=128M" } else { "" },
1803                 if discard_writes {
1804                     ",discard_writes=on"
1805                 } else {
1806                     ""
1807                 }
1808             )
1809             .as_str(),
1810         ])
1811         .capture_output()
1812         .spawn()
1813         .unwrap();
1814 
1815     let r = std::panic::catch_unwind(|| {
1816         guest.wait_vm_boot(None).unwrap();
1817 
1818         // Check for the presence of /dev/pmem0
1819         assert_eq!(
1820             guest.ssh_command("ls /dev/pmem0").unwrap().trim(),
1821             "/dev/pmem0"
1822         );
1823 
1824         // Check changes persist after reboot
1825         assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), "");
1826         assert_eq!(guest.ssh_command("ls /mnt").unwrap(), "lost+found\n");
1827         guest
1828             .ssh_command("echo test123 | sudo tee /mnt/test")
1829             .unwrap();
1830         assert_eq!(guest.ssh_command("sudo umount /mnt").unwrap(), "");
1831         assert_eq!(guest.ssh_command("ls /mnt").unwrap(), "");
1832 
1833         guest.reboot_linux(0, None);
1834         assert_eq!(guest.ssh_command("sudo mount /dev/pmem0 /mnt").unwrap(), "");
1835         assert_eq!(
1836             guest
1837                 .ssh_command("sudo cat /mnt/test || true")
1838                 .unwrap()
1839                 .trim(),
1840             if discard_writes { "" } else { "test123" }
1841         );
1842     });
1843 
1844     kill_child(&mut child);
1845     let output = child.wait_with_output().unwrap();
1846 
1847     handle_child_output(r, &output);
1848 }
1849 
1850 fn get_fd_count(pid: u32) -> usize {
1851     fs::read_dir(format!("/proc/{pid}/fd")).unwrap().count()
1852 }
1853 
1854 fn _test_virtio_vsock(hotplug: bool) {
1855     let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1856     let guest = Guest::new(Box::new(focal));
1857 
1858     #[cfg(target_arch = "x86_64")]
1859     let kernel_path = direct_kernel_boot_path();
1860     #[cfg(target_arch = "aarch64")]
1861     let kernel_path = if hotplug {
1862         edk2_path()
1863     } else {
1864         direct_kernel_boot_path()
1865     };
1866 
1867     let socket = temp_vsock_path(&guest.tmp_dir);
1868     let api_socket = temp_api_path(&guest.tmp_dir);
1869 
1870     let mut cmd = GuestCommand::new(&guest);
1871     cmd.args(["--api-socket", &api_socket]);
1872     cmd.args(["--cpus", "boot=1"]);
1873     cmd.args(["--memory", "size=512M"]);
1874     cmd.args(["--kernel", kernel_path.to_str().unwrap()]);
1875     cmd.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]);
1876     cmd.default_disks();
1877     cmd.default_net();
1878 
1879     if !hotplug {
1880         cmd.args(["--vsock", format!("cid=3,socket={socket}").as_str()]);
1881     }
1882 
1883     let mut child = cmd.capture_output().spawn().unwrap();
1884 
1885     let r = std::panic::catch_unwind(|| {
1886         guest.wait_vm_boot(None).unwrap();
1887 
1888         if hotplug {
1889             let (cmd_success, cmd_output) = remote_command_w_output(
1890                 &api_socket,
1891                 "add-vsock",
1892                 Some(format!("cid=3,socket={socket},id=test0").as_str()),
1893             );
1894             assert!(cmd_success);
1895             assert!(String::from_utf8_lossy(&cmd_output)
1896                 .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}"));
1897             thread::sleep(std::time::Duration::new(10, 0));
1898             // Check adding a second one fails
1899             assert!(!remote_command(
1900                 &api_socket,
1901                 "add-vsock",
1902                 Some("cid=1234,socket=/tmp/fail")
1903             ));
1904         }
1905 
1906         // Validate vsock works as expected.
1907         guest.check_vsock(socket.as_str());
1908         guest.reboot_linux(0, None);
1909         // Validate vsock still works after a reboot.
1910         guest.check_vsock(socket.as_str());
1911 
1912         if hotplug {
1913             assert!(remote_command(&api_socket, "remove-device", Some("test0")));
1914         }
1915     });
1916 
1917     kill_child(&mut child);
1918     let output = child.wait_with_output().unwrap();
1919 
1920     handle_child_output(r, &output);
1921 }
1922 
1923 fn get_ksm_pages_shared() -> u32 {
1924     fs::read_to_string("/sys/kernel/mm/ksm/pages_shared")
1925         .unwrap()
1926         .trim()
1927         .parse::<u32>()
1928         .unwrap()
1929 }
1930 
1931 fn test_memory_mergeable(mergeable: bool) {
1932     let memory_param = if mergeable {
1933         "mergeable=on"
1934     } else {
1935         "mergeable=off"
1936     };
1937 
1938     // We are assuming the rest of the system in our CI is not using mergeable memory
1939     let ksm_ps_init = get_ksm_pages_shared();
1940     assert!(ksm_ps_init == 0);
1941 
1942     let focal1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1943     let guest1 = Guest::new(Box::new(focal1));
1944     let mut child1 = GuestCommand::new(&guest1)
1945         .args(["--cpus", "boot=1"])
1946         .args(["--memory", format!("size=512M,{memory_param}").as_str()])
1947         .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
1948         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1949         .default_disks()
1950         .args(["--net", guest1.default_net_string().as_str()])
1951         .args(["--serial", "tty", "--console", "off"])
1952         .capture_output()
1953         .spawn()
1954         .unwrap();
1955 
1956     let r = std::panic::catch_unwind(|| {
1957         guest1.wait_vm_boot(None).unwrap();
1958     });
1959     if r.is_err() {
1960         kill_child(&mut child1);
1961         let output = child1.wait_with_output().unwrap();
1962         handle_child_output(r, &output);
1963         panic!("Test should already be failed/panicked"); // To explicitly mark this block never return
1964     }
1965 
1966     let ksm_ps_guest1 = get_ksm_pages_shared();
1967 
1968     let focal2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
1969     let guest2 = Guest::new(Box::new(focal2));
1970     let mut child2 = GuestCommand::new(&guest2)
1971         .args(["--cpus", "boot=1"])
1972         .args(["--memory", format!("size=512M,{memory_param}").as_str()])
1973         .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
1974         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1975         .default_disks()
1976         .args(["--net", guest2.default_net_string().as_str()])
1977         .args(["--serial", "tty", "--console", "off"])
1978         .capture_output()
1979         .spawn()
1980         .unwrap();
1981 
1982     let r = std::panic::catch_unwind(|| {
1983         guest2.wait_vm_boot(None).unwrap();
1984         let ksm_ps_guest2 = get_ksm_pages_shared();
1985 
1986         if mergeable {
1987             println!(
1988                 "ksm pages_shared after vm1 booted '{ksm_ps_guest1}', ksm pages_shared after vm2 booted '{ksm_ps_guest2}'"
1989             );
1990             // We are expecting the number of shared pages to increase as the number of VM increases
1991             assert!(ksm_ps_guest1 < ksm_ps_guest2);
1992         } else {
1993             assert!(ksm_ps_guest1 == 0);
1994             assert!(ksm_ps_guest2 == 0);
1995         }
1996     });
1997 
1998     kill_child(&mut child1);
1999     kill_child(&mut child2);
2000 
2001     let output = child1.wait_with_output().unwrap();
2002     child2.wait().unwrap();
2003 
2004     handle_child_output(r, &output);
2005 }
2006 
2007 fn _get_vmm_overhead(pid: u32, guest_memory_size: u32) -> HashMap<String, u32> {
2008     let smaps = fs::File::open(format!("/proc/{pid}/smaps")).unwrap();
2009     let reader = io::BufReader::new(smaps);
2010 
2011     let mut skip_map: bool = false;
2012     let mut region_name: String = "".to_string();
2013     let mut region_maps = HashMap::new();
2014     for line in reader.lines() {
2015         let l = line.unwrap();
2016 
2017         if l.contains('-') {
2018             let values: Vec<&str> = l.split_whitespace().collect();
2019             region_name = values.last().unwrap().trim().to_string();
2020             if region_name == "0" {
2021                 region_name = "anonymous".to_string()
2022             }
2023         }
2024 
2025         // Each section begins with something that looks like:
2026         // Size:               2184 kB
2027         if l.starts_with("Size:") {
2028             let values: Vec<&str> = l.split_whitespace().collect();
2029             let map_size = values[1].parse::<u32>().unwrap();
2030             // We skip the assigned guest RAM map, its RSS is only
2031             // dependent on the guest actual memory usage.
2032             // Everything else can be added to the VMM overhead.
2033             skip_map = map_size >= guest_memory_size;
2034             continue;
2035         }
2036 
2037         // If this is a map we're taking into account, then we only
2038         // count the RSS. The sum of all counted RSS is the VMM overhead.
2039         if !skip_map && l.starts_with("Rss:") {
2040             let values: Vec<&str> = l.split_whitespace().collect();
2041             let value = values[1].trim().parse::<u32>().unwrap();
2042             *region_maps.entry(region_name.clone()).or_insert(0) += value;
2043         }
2044     }
2045 
2046     region_maps
2047 }
2048 
2049 fn get_vmm_overhead(pid: u32, guest_memory_size: u32) -> u32 {
2050     let mut total = 0;
2051 
2052     for (region_name, value) in &_get_vmm_overhead(pid, guest_memory_size) {
2053         eprintln!("{region_name}: {value}");
2054         total += value;
2055     }
2056 
2057     total
2058 }
2059 
2060 fn process_rss_kib(pid: u32) -> usize {
2061     let command = format!("ps -q {pid} -o rss=");
2062     let rss = exec_host_command_output(&command);
2063     String::from_utf8_lossy(&rss.stdout).trim().parse().unwrap()
2064 }
2065 
2066 // 10MB is our maximum accepted overhead.
2067 const MAXIMUM_VMM_OVERHEAD_KB: u32 = 10 * 1024;
2068 
2069 #[derive(PartialEq, Eq, PartialOrd)]
2070 struct Counters {
2071     rx_bytes: u64,
2072     rx_frames: u64,
2073     tx_bytes: u64,
2074     tx_frames: u64,
2075     read_bytes: u64,
2076     write_bytes: u64,
2077     read_ops: u64,
2078     write_ops: u64,
2079 }
2080 
2081 fn get_counters(api_socket: &str) -> Counters {
2082     // Get counters
2083     let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "counters", None);
2084     assert!(cmd_success);
2085 
2086     let counters: HashMap<&str, HashMap<&str, u64>> =
2087         serde_json::from_slice(&cmd_output).unwrap_or_default();
2088 
2089     let rx_bytes = *counters.get("_net2").unwrap().get("rx_bytes").unwrap();
2090     let rx_frames = *counters.get("_net2").unwrap().get("rx_frames").unwrap();
2091     let tx_bytes = *counters.get("_net2").unwrap().get("tx_bytes").unwrap();
2092     let tx_frames = *counters.get("_net2").unwrap().get("tx_frames").unwrap();
2093 
2094     let read_bytes = *counters.get("_disk0").unwrap().get("read_bytes").unwrap();
2095     let write_bytes = *counters.get("_disk0").unwrap().get("write_bytes").unwrap();
2096     let read_ops = *counters.get("_disk0").unwrap().get("read_ops").unwrap();
2097     let write_ops = *counters.get("_disk0").unwrap().get("write_ops").unwrap();
2098 
2099     Counters {
2100         rx_bytes,
2101         rx_frames,
2102         tx_bytes,
2103         tx_frames,
2104         read_bytes,
2105         write_bytes,
2106         read_ops,
2107         write_ops,
2108     }
2109 }
2110 
2111 fn pty_read(mut pty: std::fs::File) -> Receiver<String> {
2112     let (tx, rx) = mpsc::channel::<String>();
2113     thread::spawn(move || loop {
2114         thread::sleep(std::time::Duration::new(1, 0));
2115         let mut buf = [0; 512];
2116         match pty.read(&mut buf) {
2117             Ok(_bytes) => {
2118                 let output = std::str::from_utf8(&buf).unwrap().to_string();
2119                 match tx.send(output) {
2120                     Ok(_) => (),
2121                     Err(_) => break,
2122                 }
2123             }
2124             Err(_) => break,
2125         }
2126     });
2127     rx
2128 }
2129 
2130 fn get_pty_path(api_socket: &str, pty_type: &str) -> PathBuf {
2131     let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None);
2132     assert!(cmd_success);
2133     let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default();
2134     assert_eq!("Pty", info["config"][pty_type]["mode"]);
2135     PathBuf::from(
2136         info["config"][pty_type]["file"]
2137             .as_str()
2138             .expect("Missing pty path"),
2139     )
2140 }
2141 
2142 // VFIO test network setup.
2143 // We reserve a different IP class for it: 172.18.0.0/24.
2144 #[cfg(target_arch = "x86_64")]
2145 fn setup_vfio_network_interfaces() {
2146     // 'vfio-br0'
2147     assert!(exec_host_command_status("sudo ip link add name vfio-br0 type bridge").success());
2148     assert!(exec_host_command_status("sudo ip link set vfio-br0 up").success());
2149     assert!(exec_host_command_status("sudo ip addr add 172.18.0.1/24 dev vfio-br0").success());
2150     // 'vfio-tap0'
2151     assert!(exec_host_command_status("sudo ip tuntap add vfio-tap0 mode tap").success());
2152     assert!(exec_host_command_status("sudo ip link set vfio-tap0 master vfio-br0").success());
2153     assert!(exec_host_command_status("sudo ip link set vfio-tap0 up").success());
2154     // 'vfio-tap1'
2155     assert!(exec_host_command_status("sudo ip tuntap add vfio-tap1 mode tap").success());
2156     assert!(exec_host_command_status("sudo ip link set vfio-tap1 master vfio-br0").success());
2157     assert!(exec_host_command_status("sudo ip link set vfio-tap1 up").success());
2158     // 'vfio-tap2'
2159     assert!(exec_host_command_status("sudo ip tuntap add vfio-tap2 mode tap").success());
2160     assert!(exec_host_command_status("sudo ip link set vfio-tap2 master vfio-br0").success());
2161     assert!(exec_host_command_status("sudo ip link set vfio-tap2 up").success());
2162     // 'vfio-tap3'
2163     assert!(exec_host_command_status("sudo ip tuntap add vfio-tap3 mode tap").success());
2164     assert!(exec_host_command_status("sudo ip link set vfio-tap3 master vfio-br0").success());
2165     assert!(exec_host_command_status("sudo ip link set vfio-tap3 up").success());
2166 }
2167 
2168 // Tear VFIO test network down
2169 #[cfg(target_arch = "x86_64")]
2170 fn cleanup_vfio_network_interfaces() {
2171     assert!(exec_host_command_status("sudo ip link del vfio-br0").success());
2172     assert!(exec_host_command_status("sudo ip link del vfio-tap0").success());
2173     assert!(exec_host_command_status("sudo ip link del vfio-tap1").success());
2174     assert!(exec_host_command_status("sudo ip link del vfio-tap2").success());
2175     assert!(exec_host_command_status("sudo ip link del vfio-tap3").success());
2176 }
2177 
2178 fn balloon_size(api_socket: &str) -> u64 {
2179     let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None);
2180     assert!(cmd_success);
2181 
2182     let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default();
2183     let total_mem = &info["config"]["memory"]["size"]
2184         .to_string()
2185         .parse::<u64>()
2186         .unwrap();
2187     let actual_mem = &info["memory_actual_size"]
2188         .to_string()
2189         .parse::<u64>()
2190         .unwrap();
2191     total_mem - actual_mem
2192 }
2193 
2194 fn vm_state(api_socket: &str) -> String {
2195     let (cmd_success, cmd_output) = remote_command_w_output(api_socket, "info", None);
2196     assert!(cmd_success);
2197 
2198     let info: serde_json::Value = serde_json::from_slice(&cmd_output).unwrap_or_default();
2199     let state = &info["state"].as_str().unwrap();
2200 
2201     state.to_string()
2202 }
2203 
2204 // This test validates that it can find the virtio-iommu device at first.
2205 // It also verifies that both disks and the network card are attached to
2206 // the virtual IOMMU by looking at /sys/kernel/iommu_groups directory.
2207 // The last interesting part of this test is that it exercises the network
2208 // interface attached to the virtual IOMMU since this is the one used to
2209 // send all commands through SSH.
2210 fn _test_virtio_iommu(acpi: bool) {
2211     // Virtio-iommu support is ready in recent kernel (v5.14). But the kernel in
2212     // Focal image is still old.
2213     // So if ACPI is enabled on AArch64, we use a modified Focal image in which
2214     // the kernel binary has been updated.
2215     #[cfg(target_arch = "aarch64")]
2216     let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string();
2217     #[cfg(target_arch = "x86_64")]
2218     let focal_image = FOCAL_IMAGE_NAME.to_string();
2219     let focal = UbuntuDiskConfig::new(focal_image);
2220     let guest = Guest::new(Box::new(focal));
2221 
2222     #[cfg(target_arch = "x86_64")]
2223     let kernel_path = direct_kernel_boot_path();
2224     #[cfg(target_arch = "aarch64")]
2225     let kernel_path = if acpi {
2226         edk2_path()
2227     } else {
2228         direct_kernel_boot_path()
2229     };
2230 
2231     let mut child = GuestCommand::new(&guest)
2232         .args(["--cpus", "boot=1"])
2233         .args(["--memory", "size=512M"])
2234         .args(["--kernel", kernel_path.to_str().unwrap()])
2235         .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2236         .args([
2237             "--disk",
2238             format!(
2239                 "path={},iommu=on",
2240                 guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
2241             )
2242             .as_str(),
2243             format!(
2244                 "path={},iommu=on",
2245                 guest.disk_config.disk(DiskType::CloudInit).unwrap()
2246             )
2247             .as_str(),
2248         ])
2249         .args(["--net", guest.default_net_string_w_iommu().as_str()])
2250         .capture_output()
2251         .spawn()
2252         .unwrap();
2253 
2254     let r = std::panic::catch_unwind(|| {
2255         guest.wait_vm_boot(None).unwrap();
2256 
2257         // Verify the virtio-iommu device is present.
2258         assert!(guest
2259             .does_device_vendor_pair_match("0x1057", "0x1af4")
2260             .unwrap_or_default());
2261 
2262         // On AArch64, if the guest system boots from FDT, the behavior of IOMMU is a bit
2263         // different with ACPI.
2264         // All devices on the PCI bus will be attached to the virtual IOMMU, except the
2265         // virtio-iommu device itself. So these devices will all be added to IOMMU groups,
2266         // and appear under folder '/sys/kernel/iommu_groups/'.
2267         // The result is, in the case of FDT, IOMMU group '0' contains "0000:00:01.0"
2268         // which is the console. The first disk "0000:00:02.0" is in group '1'.
2269         // While on ACPI, console device is not attached to IOMMU. So the IOMMU group '0'
2270         // contains "0000:00:02.0" which is the first disk.
2271         //
2272         // Verify the iommu group of the first disk.
2273         let iommu_group = !acpi as i32;
2274         assert_eq!(
2275             guest
2276                 .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str())
2277                 .unwrap()
2278                 .trim(),
2279             "0000:00:02.0"
2280         );
2281 
2282         // Verify the iommu group of the second disk.
2283         let iommu_group = if acpi { 1 } else { 2 };
2284         assert_eq!(
2285             guest
2286                 .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str())
2287                 .unwrap()
2288                 .trim(),
2289             "0000:00:03.0"
2290         );
2291 
2292         // Verify the iommu group of the network card.
2293         let iommu_group = if acpi { 2 } else { 3 };
2294         assert_eq!(
2295             guest
2296                 .ssh_command(format!("ls /sys/kernel/iommu_groups/{iommu_group}/devices").as_str())
2297                 .unwrap()
2298                 .trim(),
2299             "0000:00:04.0"
2300         );
2301     });
2302 
2303     kill_child(&mut child);
2304     let output = child.wait_with_output().unwrap();
2305 
2306     handle_child_output(r, &output);
2307 }
2308 
2309 fn get_reboot_count(guest: &Guest) -> u32 {
2310     guest
2311         .ssh_command("sudo last | grep -c reboot")
2312         .unwrap()
2313         .trim()
2314         .parse::<u32>()
2315         .unwrap_or_default()
2316 }
2317 
2318 fn enable_guest_watchdog(guest: &Guest, watchdog_sec: u32) {
2319     // Check for PCI device
2320     assert!(guest
2321         .does_device_vendor_pair_match("0x1063", "0x1af4")
2322         .unwrap_or_default());
2323 
2324     // Enable systemd watchdog
2325     guest
2326         .ssh_command(&format!(
2327             "echo RuntimeWatchdogSec={watchdog_sec}s | sudo tee -a /etc/systemd/system.conf"
2328         ))
2329         .unwrap();
2330 
2331     guest.ssh_command("sudo systemctl daemon-reexec").unwrap();
2332 }
2333 
2334 fn make_guest_panic(guest: &Guest) {
2335     // Check for pvpanic device
2336     assert!(guest
2337         .does_device_vendor_pair_match("0x0011", "0x1b36")
2338         .unwrap_or_default());
2339 
2340     // Trigger guest a panic
2341     guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap();
2342 }
2343 
2344 mod common_parallel {
2345     use std::fs::OpenOptions;
2346     use std::io::SeekFrom;
2347 
2348     use crate::*;
2349 
2350     #[test]
2351     #[cfg(target_arch = "x86_64")]
2352     fn test_focal_hypervisor_fw() {
2353         test_simple_launch(fw_path(FwType::RustHypervisorFirmware), FOCAL_IMAGE_NAME)
2354     }
2355 
2356     #[test]
2357     #[cfg(target_arch = "x86_64")]
2358     fn test_focal_ovmf() {
2359         test_simple_launch(fw_path(FwType::Ovmf), FOCAL_IMAGE_NAME)
2360     }
2361 
2362     #[cfg(target_arch = "x86_64")]
2363     fn test_simple_launch(fw_path: String, disk_path: &str) {
2364         let disk_config = Box::new(UbuntuDiskConfig::new(disk_path.to_string()));
2365         let guest = Guest::new(disk_config);
2366         let event_path = temp_event_monitor_path(&guest.tmp_dir);
2367 
2368         let mut child = GuestCommand::new(&guest)
2369             .args(["--cpus", "boot=1"])
2370             .args(["--memory", "size=512M"])
2371             .args(["--kernel", fw_path.as_str()])
2372             .default_disks()
2373             .default_net()
2374             .args(["--serial", "tty", "--console", "off"])
2375             .args(["--event-monitor", format!("path={event_path}").as_str()])
2376             .capture_output()
2377             .spawn()
2378             .unwrap();
2379 
2380         let r = std::panic::catch_unwind(|| {
2381             guest.wait_vm_boot(Some(120)).unwrap();
2382 
2383             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
2384             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
2385             assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000");
2386 
2387             let expected_sequential_events = [
2388                 &MetaEvent {
2389                     event: "starting".to_string(),
2390                     device_id: None,
2391                 },
2392                 &MetaEvent {
2393                     event: "booting".to_string(),
2394                     device_id: None,
2395                 },
2396                 &MetaEvent {
2397                     event: "booted".to_string(),
2398                     device_id: None,
2399                 },
2400                 &MetaEvent {
2401                     event: "activated".to_string(),
2402                     device_id: Some("_disk0".to_string()),
2403                 },
2404                 &MetaEvent {
2405                     event: "reset".to_string(),
2406                     device_id: Some("_disk0".to_string()),
2407                 },
2408             ];
2409             assert!(check_sequential_events(
2410                 &expected_sequential_events,
2411                 &event_path
2412             ));
2413 
2414             // It's been observed on the Bionic image that udev and snapd
2415             // services can cause some delay in the VM's shutdown. Disabling
2416             // them improves the reliability of this test.
2417             let _ = guest.ssh_command("sudo systemctl disable udev");
2418             let _ = guest.ssh_command("sudo systemctl stop udev");
2419             let _ = guest.ssh_command("sudo systemctl disable snapd");
2420             let _ = guest.ssh_command("sudo systemctl stop snapd");
2421 
2422             guest.ssh_command("sudo poweroff").unwrap();
2423             thread::sleep(std::time::Duration::new(20, 0));
2424             let latest_events = [
2425                 &MetaEvent {
2426                     event: "shutdown".to_string(),
2427                     device_id: None,
2428                 },
2429                 &MetaEvent {
2430                     event: "deleted".to_string(),
2431                     device_id: None,
2432                 },
2433                 &MetaEvent {
2434                     event: "shutdown".to_string(),
2435                     device_id: None,
2436                 },
2437             ];
2438             assert!(check_latest_events_exact(&latest_events, &event_path));
2439         });
2440 
2441         kill_child(&mut child);
2442         let output = child.wait_with_output().unwrap();
2443 
2444         handle_child_output(r, &output);
2445     }
2446 
2447     #[test]
2448     fn test_multi_cpu() {
2449         let jammy_image = JAMMY_IMAGE_NAME.to_string();
2450         let jammy = UbuntuDiskConfig::new(jammy_image);
2451         let guest = Guest::new(Box::new(jammy));
2452 
2453         let mut cmd = GuestCommand::new(&guest);
2454         cmd.args(["--cpus", "boot=2,max=4"])
2455             .args(["--memory", "size=512M"])
2456             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2457             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2458             .capture_output()
2459             .default_disks()
2460             .default_net();
2461 
2462         let mut child = cmd.spawn().unwrap();
2463 
2464         let r = std::panic::catch_unwind(|| {
2465             guest.wait_vm_boot(Some(120)).unwrap();
2466 
2467             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2);
2468 
2469             assert_eq!(
2470                 guest
2471                     .ssh_command(
2472                         r#"sudo dmesg | grep "smp: Brought up" | sed "s/\[\ *[0-9.]*\] //""#
2473                     )
2474                     .unwrap()
2475                     .trim(),
2476                 "smp: Brought up 1 node, 2 CPUs"
2477             );
2478         });
2479 
2480         kill_child(&mut child);
2481         let output = child.wait_with_output().unwrap();
2482 
2483         handle_child_output(r, &output);
2484     }
2485 
2486     #[test]
2487     fn test_cpu_topology_421() {
2488         test_cpu_topology(4, 2, 1, false);
2489     }
2490 
2491     #[test]
2492     fn test_cpu_topology_142() {
2493         test_cpu_topology(1, 4, 2, false);
2494     }
2495 
2496     #[test]
2497     fn test_cpu_topology_262() {
2498         test_cpu_topology(2, 6, 2, false);
2499     }
2500 
2501     #[test]
2502     #[cfg(target_arch = "x86_64")]
2503     #[cfg(not(feature = "mshv"))]
2504     fn test_cpu_physical_bits() {
2505         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2506         let guest = Guest::new(Box::new(focal));
2507         let max_phys_bits: u8 = 36;
2508         let mut child = GuestCommand::new(&guest)
2509             .args(["--cpus", &format!("max_phys_bits={max_phys_bits}")])
2510             .args(["--memory", "size=512M"])
2511             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2512             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2513             .default_disks()
2514             .default_net()
2515             .capture_output()
2516             .spawn()
2517             .unwrap();
2518 
2519         let r = std::panic::catch_unwind(|| {
2520             guest.wait_vm_boot(None).unwrap();
2521 
2522             assert!(
2523                     guest
2524                         .ssh_command("lscpu | grep \"Address sizes:\" | cut -f 2 -d \":\" | sed \"s# *##\" | cut -f 1 -d \" \"")
2525                         .unwrap()
2526                         .trim()
2527                         .parse::<u8>()
2528                         .unwrap_or(max_phys_bits + 1) <= max_phys_bits,
2529                 );
2530         });
2531 
2532         kill_child(&mut child);
2533         let output = child.wait_with_output().unwrap();
2534 
2535         handle_child_output(r, &output);
2536     }
2537 
2538     #[test]
2539     fn test_cpu_affinity() {
2540         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2541         let guest = Guest::new(Box::new(focal));
2542 
2543         // We need the host to have at least 4 CPUs if we want to be able
2544         // to run this test.
2545         let host_cpus_count = exec_host_command_output("nproc");
2546         assert!(
2547             String::from_utf8_lossy(&host_cpus_count.stdout)
2548                 .trim()
2549                 .parse::<u16>()
2550                 .unwrap_or(0)
2551                 >= 4
2552         );
2553 
2554         let mut child = GuestCommand::new(&guest)
2555             .args(["--cpus", "boot=2,affinity=[0@[0,2],1@[1,3]]"])
2556             .args(["--memory", "size=512M"])
2557             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2558             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2559             .default_disks()
2560             .default_net()
2561             .capture_output()
2562             .spawn()
2563             .unwrap();
2564 
2565         let r = std::panic::catch_unwind(|| {
2566             guest.wait_vm_boot(None).unwrap();
2567             let pid = child.id();
2568             let taskset_vcpu0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str());
2569             assert_eq!(String::from_utf8_lossy(&taskset_vcpu0.stdout).trim(), "0,2");
2570             let taskset_vcpu1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep vcpu1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str());
2571             assert_eq!(String::from_utf8_lossy(&taskset_vcpu1.stdout).trim(), "1,3");
2572         });
2573 
2574         kill_child(&mut child);
2575         let output = child.wait_with_output().unwrap();
2576         handle_child_output(r, &output);
2577     }
2578 
2579     #[test]
2580     fn test_virtio_queue_affinity() {
2581         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2582         let guest = Guest::new(Box::new(focal));
2583 
2584         // We need the host to have at least 4 CPUs if we want to be able
2585         // to run this test.
2586         let host_cpus_count = exec_host_command_output("nproc");
2587         assert!(
2588             String::from_utf8_lossy(&host_cpus_count.stdout)
2589                 .trim()
2590                 .parse::<u16>()
2591                 .unwrap_or(0)
2592                 >= 4
2593         );
2594 
2595         let mut child = GuestCommand::new(&guest)
2596             .args(["--cpus", "boot=4"])
2597             .args(["--memory", "size=512M"])
2598             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2599             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2600             .args([
2601                 "--disk",
2602                 format!(
2603                     "path={}",
2604                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
2605                 )
2606                 .as_str(),
2607                 format!(
2608                     "path={},num_queues=4,queue_affinity=[0@[0,2],1@[1,3],2@[1],3@[3]]",
2609                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
2610                 )
2611                 .as_str(),
2612             ])
2613             .default_net()
2614             .capture_output()
2615             .spawn()
2616             .unwrap();
2617 
2618         let r = std::panic::catch_unwind(|| {
2619             guest.wait_vm_boot(None).unwrap();
2620             let pid = child.id();
2621             let taskset_q0 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q0 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str());
2622             assert_eq!(String::from_utf8_lossy(&taskset_q0.stdout).trim(), "0,2");
2623             let taskset_q1 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q1 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str());
2624             assert_eq!(String::from_utf8_lossy(&taskset_q1.stdout).trim(), "1,3");
2625             let taskset_q2 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q2 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str());
2626             assert_eq!(String::from_utf8_lossy(&taskset_q2.stdout).trim(), "1");
2627             let taskset_q3 = exec_host_command_output(format!("taskset -pc $(ps -T -p {pid} | grep disk1_q3 | xargs | cut -f 2 -d \" \") | cut -f 6 -d \" \"").as_str());
2628             assert_eq!(String::from_utf8_lossy(&taskset_q3.stdout).trim(), "3");
2629         });
2630 
2631         kill_child(&mut child);
2632         let output = child.wait_with_output().unwrap();
2633         handle_child_output(r, &output);
2634     }
2635 
2636     #[test]
2637     #[cfg(not(feature = "mshv"))]
2638     fn test_large_vm() {
2639         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2640         let guest = Guest::new(Box::new(focal));
2641         let mut cmd = GuestCommand::new(&guest);
2642         cmd.args(["--cpus", "boot=48"])
2643             .args(["--memory", "size=5120M"])
2644             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2645             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2646             .args(["--serial", "tty"])
2647             .args(["--console", "off"])
2648             .capture_output()
2649             .default_disks()
2650             .default_net();
2651 
2652         let mut child = cmd.spawn().unwrap();
2653 
2654         guest.wait_vm_boot(None).unwrap();
2655 
2656         let r = std::panic::catch_unwind(|| {
2657             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 48);
2658             assert_eq!(
2659                 guest
2660                     .ssh_command("lscpu | grep \"On-line\" | cut -f 2 -d \":\" | sed \"s# *##\"")
2661                     .unwrap()
2662                     .trim(),
2663                 "0-47"
2664             );
2665 
2666             assert!(guest.get_total_memory().unwrap_or_default() > 5_000_000);
2667         });
2668 
2669         kill_child(&mut child);
2670         let output = child.wait_with_output().unwrap();
2671 
2672         handle_child_output(r, &output);
2673     }
2674 
2675     #[test]
2676     #[cfg(not(feature = "mshv"))]
2677     fn test_huge_memory() {
2678         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2679         let guest = Guest::new(Box::new(focal));
2680         let mut cmd = GuestCommand::new(&guest);
2681         cmd.args(["--cpus", "boot=1"])
2682             .args(["--memory", "size=128G"])
2683             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2684             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2685             .capture_output()
2686             .default_disks()
2687             .default_net();
2688 
2689         let mut child = cmd.spawn().unwrap();
2690 
2691         guest.wait_vm_boot(Some(120)).unwrap();
2692 
2693         let r = std::panic::catch_unwind(|| {
2694             assert!(guest.get_total_memory().unwrap_or_default() > 128_000_000);
2695         });
2696 
2697         kill_child(&mut child);
2698         let output = child.wait_with_output().unwrap();
2699 
2700         handle_child_output(r, &output);
2701     }
2702 
2703     #[test]
2704     fn test_power_button() {
2705         _test_power_button(false);
2706     }
2707 
2708     #[test]
2709     #[cfg(not(feature = "mshv"))]
2710     fn test_user_defined_memory_regions() {
2711         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2712         let guest = Guest::new(Box::new(focal));
2713         let api_socket = temp_api_path(&guest.tmp_dir);
2714 
2715         let kernel_path = direct_kernel_boot_path();
2716 
2717         let mut child = GuestCommand::new(&guest)
2718             .args(["--cpus", "boot=1"])
2719             .args(["--memory", "size=0,hotplug_method=virtio-mem"])
2720             .args([
2721                 "--memory-zone",
2722                 "id=mem0,size=1G,hotplug_size=2G",
2723                 "id=mem1,size=1G,shared=on",
2724                 "id=mem2,size=1G,host_numa_node=0,hotplug_size=2G",
2725             ])
2726             .args(["--kernel", kernel_path.to_str().unwrap()])
2727             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2728             .args(["--api-socket", &api_socket])
2729             .capture_output()
2730             .default_disks()
2731             .default_net()
2732             .spawn()
2733             .unwrap();
2734 
2735         let r = std::panic::catch_unwind(|| {
2736             guest.wait_vm_boot(None).unwrap();
2737 
2738             assert!(guest.get_total_memory().unwrap_or_default() > 2_880_000);
2739 
2740             guest.enable_memory_hotplug();
2741 
2742             resize_zone_command(&api_socket, "mem0", "3G");
2743             thread::sleep(std::time::Duration::new(5, 0));
2744             assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000);
2745             resize_zone_command(&api_socket, "mem2", "3G");
2746             thread::sleep(std::time::Duration::new(5, 0));
2747             assert!(guest.get_total_memory().unwrap_or_default() > 6_720_000);
2748             resize_zone_command(&api_socket, "mem0", "2G");
2749             thread::sleep(std::time::Duration::new(5, 0));
2750             assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000);
2751             resize_zone_command(&api_socket, "mem2", "2G");
2752             thread::sleep(std::time::Duration::new(5, 0));
2753             assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000);
2754 
2755             guest.reboot_linux(0, None);
2756 
2757             // Check the amount of RAM after reboot
2758             assert!(guest.get_total_memory().unwrap_or_default() > 4_800_000);
2759             assert!(guest.get_total_memory().unwrap_or_default() < 5_760_000);
2760 
2761             // Check if we can still resize down to the initial 'boot'size
2762             resize_zone_command(&api_socket, "mem0", "1G");
2763             thread::sleep(std::time::Duration::new(5, 0));
2764             assert!(guest.get_total_memory().unwrap_or_default() < 4_800_000);
2765             resize_zone_command(&api_socket, "mem2", "1G");
2766             thread::sleep(std::time::Duration::new(5, 0));
2767             assert!(guest.get_total_memory().unwrap_or_default() < 3_840_000);
2768         });
2769 
2770         kill_child(&mut child);
2771         let output = child.wait_with_output().unwrap();
2772 
2773         handle_child_output(r, &output);
2774     }
2775 
2776     #[test]
2777     #[cfg(not(feature = "mshv"))]
2778     fn test_guest_numa_nodes() {
2779         _test_guest_numa_nodes(false);
2780     }
2781 
2782     #[test]
2783     #[cfg(target_arch = "x86_64")]
2784     fn test_iommu_segments() {
2785         let focal_image = FOCAL_IMAGE_NAME.to_string();
2786         let focal = UbuntuDiskConfig::new(focal_image);
2787         let guest = Guest::new(Box::new(focal));
2788 
2789         // Prepare another disk file for the virtio-disk device
2790         let test_disk_path = String::from(
2791             guest
2792                 .tmp_dir
2793                 .as_path()
2794                 .join("test-disk.raw")
2795                 .to_str()
2796                 .unwrap(),
2797         );
2798         assert!(
2799             exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success()
2800         );
2801         assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success());
2802 
2803         let api_socket = temp_api_path(&guest.tmp_dir);
2804         let mut cmd = GuestCommand::new(&guest);
2805 
2806         cmd.args(["--cpus", "boot=1"])
2807             .args(["--api-socket", &api_socket])
2808             .args(["--memory", "size=512M"])
2809             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2810             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2811             .args([
2812                 "--platform",
2813                 &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS},iommu_segments=[1]"),
2814             ])
2815             .default_disks()
2816             .capture_output()
2817             .default_net();
2818 
2819         let mut child = cmd.spawn().unwrap();
2820 
2821         guest.wait_vm_boot(None).unwrap();
2822 
2823         let r = std::panic::catch_unwind(|| {
2824             let (cmd_success, cmd_output) = remote_command_w_output(
2825                 &api_socket,
2826                 "add-disk",
2827                 Some(
2828                     format!(
2829                         "path={},id=test0,pci_segment=1,iommu=on",
2830                         test_disk_path.as_str()
2831                     )
2832                     .as_str(),
2833                 ),
2834             );
2835             assert!(cmd_success);
2836             assert!(String::from_utf8_lossy(&cmd_output)
2837                 .contains("{\"id\":\"test0\",\"bdf\":\"0001:00:01.0\"}"));
2838 
2839             // Check IOMMU setup
2840             assert!(guest
2841                 .does_device_vendor_pair_match("0x1057", "0x1af4")
2842                 .unwrap_or_default());
2843             assert_eq!(
2844                 guest
2845                     .ssh_command("ls /sys/kernel/iommu_groups/0/devices")
2846                     .unwrap()
2847                     .trim(),
2848                 "0001:00:01.0"
2849             );
2850         });
2851 
2852         kill_child(&mut child);
2853         let output = child.wait_with_output().unwrap();
2854 
2855         handle_child_output(r, &output);
2856     }
2857 
2858     #[test]
2859     fn test_pci_msi() {
2860         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2861         let guest = Guest::new(Box::new(focal));
2862         let mut cmd = GuestCommand::new(&guest);
2863         cmd.args(["--cpus", "boot=1"])
2864             .args(["--memory", "size=512M"])
2865             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2866             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2867             .capture_output()
2868             .default_disks()
2869             .default_net();
2870 
2871         let mut child = cmd.spawn().unwrap();
2872 
2873         guest.wait_vm_boot(None).unwrap();
2874 
2875         #[cfg(target_arch = "x86_64")]
2876         let grep_cmd = "grep -c PCI-MSI /proc/interrupts";
2877         #[cfg(target_arch = "aarch64")]
2878         let grep_cmd = "grep -c ITS-PCI-MSIX /proc/interrupts";
2879 
2880         let r = std::panic::catch_unwind(|| {
2881             assert_eq!(
2882                 guest
2883                     .ssh_command(grep_cmd)
2884                     .unwrap()
2885                     .trim()
2886                     .parse::<u32>()
2887                     .unwrap_or_default(),
2888                 12
2889             );
2890         });
2891 
2892         kill_child(&mut child);
2893         let output = child.wait_with_output().unwrap();
2894 
2895         handle_child_output(r, &output);
2896     }
2897 
2898     #[test]
2899     fn test_virtio_net_ctrl_queue() {
2900         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2901         let guest = Guest::new(Box::new(focal));
2902         let mut cmd = GuestCommand::new(&guest);
2903         cmd.args(["--cpus", "boot=1"])
2904             .args(["--memory", "size=512M"])
2905             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2906             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2907             .args(["--net", guest.default_net_string_w_mtu(3000).as_str()])
2908             .capture_output()
2909             .default_disks();
2910 
2911         let mut child = cmd.spawn().unwrap();
2912 
2913         guest.wait_vm_boot(None).unwrap();
2914 
2915         #[cfg(target_arch = "aarch64")]
2916         let iface = "enp0s4";
2917         #[cfg(target_arch = "x86_64")]
2918         let iface = "ens4";
2919 
2920         let r = std::panic::catch_unwind(|| {
2921             assert_eq!(
2922                 guest
2923                     .ssh_command(
2924                         format!("sudo ethtool -K {iface} rx-gro-hw off && echo success").as_str()
2925                     )
2926                     .unwrap()
2927                     .trim(),
2928                 "success"
2929             );
2930             assert_eq!(
2931                 guest
2932                     .ssh_command(format!("cat /sys/class/net/{iface}/mtu").as_str())
2933                     .unwrap()
2934                     .trim(),
2935                 "3000"
2936             );
2937         });
2938 
2939         kill_child(&mut child);
2940         let output = child.wait_with_output().unwrap();
2941 
2942         handle_child_output(r, &output);
2943     }
2944 
2945     #[test]
2946     fn test_pci_multiple_segments() {
2947         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
2948         let guest = Guest::new(Box::new(focal));
2949 
2950         // Prepare another disk file for the virtio-disk device
2951         let test_disk_path = String::from(
2952             guest
2953                 .tmp_dir
2954                 .as_path()
2955                 .join("test-disk.raw")
2956                 .to_str()
2957                 .unwrap(),
2958         );
2959         assert!(
2960             exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success()
2961         );
2962         assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success());
2963 
2964         let mut cmd = GuestCommand::new(&guest);
2965         cmd.args(["--cpus", "boot=1"])
2966             .args(["--memory", "size=512M"])
2967             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
2968             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
2969             .args([
2970                 "--platform",
2971                 &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"),
2972             ])
2973             .args([
2974                 "--disk",
2975                 format!(
2976                     "path={}",
2977                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
2978                 )
2979                 .as_str(),
2980                 format!(
2981                     "path={}",
2982                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
2983                 )
2984                 .as_str(),
2985                 format!("path={test_disk_path},pci_segment=15").as_str(),
2986             ])
2987             .capture_output()
2988             .default_net();
2989 
2990         let mut child = cmd.spawn().unwrap();
2991 
2992         guest.wait_vm_boot(None).unwrap();
2993 
2994         let grep_cmd = "lspci | grep \"Host bridge\" | wc -l";
2995 
2996         let r = std::panic::catch_unwind(|| {
2997             // There should be MAX_NUM_PCI_SEGMENTS PCI host bridges in the guest.
2998             assert_eq!(
2999                 guest
3000                     .ssh_command(grep_cmd)
3001                     .unwrap()
3002                     .trim()
3003                     .parse::<u16>()
3004                     .unwrap_or_default(),
3005                 MAX_NUM_PCI_SEGMENTS
3006             );
3007 
3008             // Check both if /dev/vdc exists and if the block size is 4M.
3009             assert_eq!(
3010                 guest
3011                     .ssh_command("lsblk | grep vdc | grep -c 4M")
3012                     .unwrap()
3013                     .trim()
3014                     .parse::<u32>()
3015                     .unwrap_or_default(),
3016                 1
3017             );
3018 
3019             // Mount the device.
3020             guest.ssh_command("mkdir mount_image").unwrap();
3021             guest
3022                 .ssh_command("sudo mount -o rw -t ext4 /dev/vdc mount_image/")
3023                 .unwrap();
3024             // Grant all users with write permission.
3025             guest.ssh_command("sudo chmod a+w mount_image/").unwrap();
3026 
3027             // Write something to the device.
3028             guest
3029                 .ssh_command("sudo echo \"bar\" >> mount_image/foo")
3030                 .unwrap();
3031 
3032             // Check the content of the block device. The file "foo" should
3033             // contain "bar".
3034             assert_eq!(
3035                 guest
3036                     .ssh_command("sudo cat mount_image/foo")
3037                     .unwrap()
3038                     .trim(),
3039                 "bar"
3040             );
3041         });
3042 
3043         kill_child(&mut child);
3044         let output = child.wait_with_output().unwrap();
3045 
3046         handle_child_output(r, &output);
3047     }
3048 
3049     #[test]
3050     fn test_pci_multiple_segments_numa_node() {
3051         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3052         let guest = Guest::new(Box::new(focal));
3053         let api_socket = temp_api_path(&guest.tmp_dir);
3054         #[cfg(target_arch = "x86_64")]
3055         let kernel_path = direct_kernel_boot_path();
3056         #[cfg(target_arch = "aarch64")]
3057         let kernel_path = edk2_path();
3058 
3059         // Prepare another disk file for the virtio-disk device
3060         let test_disk_path = String::from(
3061             guest
3062                 .tmp_dir
3063                 .as_path()
3064                 .join("test-disk.raw")
3065                 .to_str()
3066                 .unwrap(),
3067         );
3068         assert!(
3069             exec_host_command_status(format!("truncate {test_disk_path} -s 4M").as_str()).success()
3070         );
3071         assert!(exec_host_command_status(format!("mkfs.ext4 {test_disk_path}").as_str()).success());
3072         const TEST_DISK_NODE: u16 = 1;
3073 
3074         let mut child = GuestCommand::new(&guest)
3075             .args(["--platform", "num_pci_segments=2"])
3076             .args(["--cpus", "boot=2"])
3077             .args(["--memory", "size=0"])
3078             .args(["--memory-zone", "id=mem0,size=256M", "id=mem1,size=256M"])
3079             .args([
3080                 "--numa",
3081                 "guest_numa_id=0,cpus=[0],distances=[1@20],memory_zones=mem0,pci_segments=[0]",
3082                 "guest_numa_id=1,cpus=[1],distances=[0@20],memory_zones=mem1,pci_segments=[1]",
3083             ])
3084             .args(["--kernel", kernel_path.to_str().unwrap()])
3085             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3086             .args(["--api-socket", &api_socket])
3087             .capture_output()
3088             .args([
3089                 "--disk",
3090                 format!(
3091                     "path={}",
3092                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
3093                 )
3094                 .as_str(),
3095                 format!(
3096                     "path={}",
3097                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
3098                 )
3099                 .as_str(),
3100                 format!("path={test_disk_path},pci_segment={TEST_DISK_NODE}").as_str(),
3101             ])
3102             .default_net()
3103             .spawn()
3104             .unwrap();
3105 
3106         let cmd = "cat /sys/block/vdc/device/../numa_node";
3107 
3108         let r = std::panic::catch_unwind(|| {
3109             guest.wait_vm_boot(None).unwrap();
3110 
3111             assert_eq!(
3112                 guest
3113                     .ssh_command(cmd)
3114                     .unwrap()
3115                     .trim()
3116                     .parse::<u16>()
3117                     .unwrap_or_default(),
3118                 TEST_DISK_NODE
3119             );
3120         });
3121 
3122         kill_child(&mut child);
3123         let output = child.wait_with_output().unwrap();
3124 
3125         handle_child_output(r, &output);
3126     }
3127 
3128     #[test]
3129     fn test_direct_kernel_boot() {
3130         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3131         let guest = Guest::new(Box::new(focal));
3132 
3133         let kernel_path = direct_kernel_boot_path();
3134 
3135         let mut child = GuestCommand::new(&guest)
3136             .args(["--cpus", "boot=1"])
3137             .args(["--memory", "size=512M"])
3138             .args(["--kernel", kernel_path.to_str().unwrap()])
3139             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3140             .default_disks()
3141             .default_net()
3142             .capture_output()
3143             .spawn()
3144             .unwrap();
3145 
3146         let r = std::panic::catch_unwind(|| {
3147             guest.wait_vm_boot(None).unwrap();
3148 
3149             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
3150             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
3151 
3152             let grep_cmd = if cfg!(target_arch = "x86_64") {
3153                 "grep -c PCI-MSI /proc/interrupts"
3154             } else {
3155                 "grep -c ITS-PCI-MSIX /proc/interrupts"
3156             };
3157             assert_eq!(
3158                 guest
3159                     .ssh_command(grep_cmd)
3160                     .unwrap()
3161                     .trim()
3162                     .parse::<u32>()
3163                     .unwrap_or_default(),
3164                 12
3165             );
3166         });
3167 
3168         kill_child(&mut child);
3169         let output = child.wait_with_output().unwrap();
3170 
3171         handle_child_output(r, &output);
3172     }
3173 
3174     #[test]
3175     #[cfg(target_arch = "x86_64")]
3176     fn test_direct_kernel_boot_bzimage() {
3177         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3178         let guest = Guest::new(Box::new(focal));
3179 
3180         let mut kernel_path = direct_kernel_boot_path();
3181         // Replace the default kernel with the bzImage.
3182         kernel_path.pop();
3183         kernel_path.push("bzImage");
3184 
3185         let mut child = GuestCommand::new(&guest)
3186             .args(["--cpus", "boot=1"])
3187             .args(["--memory", "size=512M"])
3188             .args(["--kernel", kernel_path.to_str().unwrap()])
3189             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3190             .default_disks()
3191             .default_net()
3192             .capture_output()
3193             .spawn()
3194             .unwrap();
3195 
3196         let r = std::panic::catch_unwind(|| {
3197             guest.wait_vm_boot(None).unwrap();
3198 
3199             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
3200             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
3201 
3202             let grep_cmd = if cfg!(target_arch = "x86_64") {
3203                 "grep -c PCI-MSI /proc/interrupts"
3204             } else {
3205                 "grep -c ITS-PCI-MSIX /proc/interrupts"
3206             };
3207             assert_eq!(
3208                 guest
3209                     .ssh_command(grep_cmd)
3210                     .unwrap()
3211                     .trim()
3212                     .parse::<u32>()
3213                     .unwrap_or_default(),
3214                 12
3215             );
3216         });
3217 
3218         kill_child(&mut child);
3219         let output = child.wait_with_output().unwrap();
3220 
3221         handle_child_output(r, &output);
3222     }
3223 
3224     fn _test_virtio_block(image_name: &str, disable_io_uring: bool, disable_aio: bool) {
3225         let focal = UbuntuDiskConfig::new(image_name.to_string());
3226         let guest = Guest::new(Box::new(focal));
3227 
3228         let mut workload_path = dirs::home_dir().unwrap();
3229         workload_path.push("workloads");
3230 
3231         let mut blk_file_path = workload_path;
3232         blk_file_path.push("blk.img");
3233 
3234         let kernel_path = direct_kernel_boot_path();
3235 
3236         let mut cloud_child = GuestCommand::new(&guest)
3237             .args(["--cpus", "boot=4"])
3238             .args(["--memory", "size=512M,shared=on"])
3239             .args(["--kernel", kernel_path.to_str().unwrap()])
3240             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3241             .args([
3242                 "--disk",
3243                 format!(
3244                     "path={}",
3245                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
3246                 )
3247                 .as_str(),
3248                 format!(
3249                     "path={}",
3250                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
3251                 )
3252                 .as_str(),
3253                 format!(
3254                     "path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={},_disable_aio={}",
3255                     blk_file_path.to_str().unwrap(),
3256                     disable_io_uring,
3257                     disable_aio,
3258                 )
3259                 .as_str(),
3260             ])
3261             .default_net()
3262             .capture_output()
3263             .spawn()
3264             .unwrap();
3265 
3266         let r = std::panic::catch_unwind(|| {
3267             guest.wait_vm_boot(None).unwrap();
3268 
3269             // Check both if /dev/vdc exists and if the block size is 16M.
3270             assert_eq!(
3271                 guest
3272                     .ssh_command("lsblk | grep vdc | grep -c 16M")
3273                     .unwrap()
3274                     .trim()
3275                     .parse::<u32>()
3276                     .unwrap_or_default(),
3277                 1
3278             );
3279 
3280             // Check both if /dev/vdc exists and if this block is RO.
3281             assert_eq!(
3282                 guest
3283                     .ssh_command("lsblk | grep vdc | awk '{print $5}'")
3284                     .unwrap()
3285                     .trim()
3286                     .parse::<u32>()
3287                     .unwrap_or_default(),
3288                 1
3289             );
3290 
3291             // Check if the number of queues is 4.
3292             assert_eq!(
3293                 guest
3294                     .ssh_command("ls -ll /sys/block/vdc/mq | grep ^d | wc -l")
3295                     .unwrap()
3296                     .trim()
3297                     .parse::<u32>()
3298                     .unwrap_or_default(),
3299                 4
3300             );
3301         });
3302 
3303         let _ = cloud_child.kill();
3304         let output = cloud_child.wait_with_output().unwrap();
3305 
3306         handle_child_output(r, &output);
3307     }
3308 
3309     #[test]
3310     fn test_virtio_block_io_uring() {
3311         _test_virtio_block(FOCAL_IMAGE_NAME, false, true)
3312     }
3313 
3314     #[test]
3315     fn test_virtio_block_aio() {
3316         _test_virtio_block(FOCAL_IMAGE_NAME, true, false)
3317     }
3318 
3319     #[test]
3320     fn test_virtio_block_sync() {
3321         _test_virtio_block(FOCAL_IMAGE_NAME, true, true)
3322     }
3323 
3324     #[test]
3325     fn test_virtio_block_qcow2() {
3326         _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2, false, false)
3327     }
3328 
3329     #[test]
3330     fn test_virtio_block_qcow2_backing_file() {
3331         _test_virtio_block(FOCAL_IMAGE_NAME_QCOW2_BACKING_FILE, false, false)
3332     }
3333 
3334     #[test]
3335     fn test_virtio_block_vhd() {
3336         let mut workload_path = dirs::home_dir().unwrap();
3337         workload_path.push("workloads");
3338 
3339         let mut raw_file_path = workload_path.clone();
3340         let mut vhd_file_path = workload_path;
3341         raw_file_path.push(FOCAL_IMAGE_NAME);
3342         vhd_file_path.push(FOCAL_IMAGE_NAME_VHD);
3343 
3344         // Generate VHD file from RAW file
3345         std::process::Command::new("qemu-img")
3346             .arg("convert")
3347             .arg("-p")
3348             .args(["-f", "raw"])
3349             .args(["-O", "vpc"])
3350             .args(["-o", "subformat=fixed"])
3351             .arg(raw_file_path.to_str().unwrap())
3352             .arg(vhd_file_path.to_str().unwrap())
3353             .output()
3354             .expect("Expect generating VHD image from RAW image");
3355 
3356         _test_virtio_block(FOCAL_IMAGE_NAME_VHD, false, false)
3357     }
3358 
3359     #[test]
3360     fn test_virtio_block_vhdx() {
3361         let mut workload_path = dirs::home_dir().unwrap();
3362         workload_path.push("workloads");
3363 
3364         let mut raw_file_path = workload_path.clone();
3365         let mut vhdx_file_path = workload_path;
3366         raw_file_path.push(FOCAL_IMAGE_NAME);
3367         vhdx_file_path.push(FOCAL_IMAGE_NAME_VHDX);
3368 
3369         // Generate dynamic VHDX file from RAW file
3370         std::process::Command::new("qemu-img")
3371             .arg("convert")
3372             .arg("-p")
3373             .args(["-f", "raw"])
3374             .args(["-O", "vhdx"])
3375             .arg(raw_file_path.to_str().unwrap())
3376             .arg(vhdx_file_path.to_str().unwrap())
3377             .output()
3378             .expect("Expect generating dynamic VHDx image from RAW image");
3379 
3380         _test_virtio_block(FOCAL_IMAGE_NAME_VHDX, false, false)
3381     }
3382 
3383     #[test]
3384     fn test_virtio_block_dynamic_vhdx_expand() {
3385         const VIRTUAL_DISK_SIZE: u64 = 100 << 20;
3386         const EMPTY_VHDX_FILE_SIZE: u64 = 8 << 20;
3387         const FULL_VHDX_FILE_SIZE: u64 = 112 << 20;
3388         const DYNAMIC_VHDX_NAME: &str = "dynamic.vhdx";
3389 
3390         let mut workload_path = dirs::home_dir().unwrap();
3391         workload_path.push("workloads");
3392 
3393         let mut vhdx_file_path = workload_path;
3394         vhdx_file_path.push(DYNAMIC_VHDX_NAME);
3395         let vhdx_path = vhdx_file_path.to_str().unwrap();
3396 
3397         // Generate a 100 MiB dynamic VHDX file
3398         std::process::Command::new("qemu-img")
3399             .arg("create")
3400             .args(["-f", "vhdx"])
3401             .arg(vhdx_path)
3402             .arg(VIRTUAL_DISK_SIZE.to_string())
3403             .output()
3404             .expect("Expect generating dynamic VHDx image from RAW image");
3405 
3406         // Check if the size matches with empty VHDx file size
3407         assert_eq!(vhdx_image_size(vhdx_path), EMPTY_VHDX_FILE_SIZE);
3408 
3409         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3410         let guest = Guest::new(Box::new(focal));
3411         let kernel_path = direct_kernel_boot_path();
3412 
3413         let mut cloud_child = GuestCommand::new(&guest)
3414             .args(["--cpus", "boot=1"])
3415             .args(["--memory", "size=512M"])
3416             .args(["--kernel", kernel_path.to_str().unwrap()])
3417             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3418             .args([
3419                 "--disk",
3420                 format!(
3421                     "path={}",
3422                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
3423                 )
3424                 .as_str(),
3425                 format!(
3426                     "path={}",
3427                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
3428                 )
3429                 .as_str(),
3430                 format!("path={vhdx_path}").as_str(),
3431             ])
3432             .default_net()
3433             .capture_output()
3434             .spawn()
3435             .unwrap();
3436 
3437         let r = std::panic::catch_unwind(|| {
3438             guest.wait_vm_boot(None).unwrap();
3439 
3440             // Check both if /dev/vdc exists and if the block size is 100 MiB.
3441             assert_eq!(
3442                 guest
3443                     .ssh_command("lsblk | grep vdc | grep -c 100M")
3444                     .unwrap()
3445                     .trim()
3446                     .parse::<u32>()
3447                     .unwrap_or_default(),
3448                 1
3449             );
3450 
3451             // Write 100 MB of data to the VHDx disk
3452             guest
3453                 .ssh_command("sudo dd if=/dev/urandom of=/dev/vdc bs=1M count=100")
3454                 .unwrap();
3455         });
3456 
3457         // Check if the size matches with expected expanded VHDx file size
3458         assert_eq!(vhdx_image_size(vhdx_path), FULL_VHDX_FILE_SIZE);
3459 
3460         kill_child(&mut cloud_child);
3461         let output = cloud_child.wait_with_output().unwrap();
3462 
3463         handle_child_output(r, &output);
3464     }
3465 
3466     fn vhdx_image_size(disk_name: &str) -> u64 {
3467         std::fs::File::open(disk_name)
3468             .unwrap()
3469             .seek(SeekFrom::End(0))
3470             .unwrap()
3471     }
3472 
3473     #[test]
3474     fn test_virtio_block_direct_and_firmware() {
3475         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3476         let guest = Guest::new(Box::new(focal));
3477 
3478         // The OS disk must be copied to a location that is not backed by
3479         // tmpfs, otherwise the syscall openat(2) with O_DIRECT simply fails
3480         // with EINVAL because tmpfs doesn't support this flag.
3481         let mut workloads_path = dirs::home_dir().unwrap();
3482         workloads_path.push("workloads");
3483         let os_dir = TempDir::new_in(workloads_path.as_path()).unwrap();
3484         let mut os_path = os_dir.as_path().to_path_buf();
3485         os_path.push("osdisk.img");
3486         rate_limited_copy(
3487             guest.disk_config.disk(DiskType::OperatingSystem).unwrap(),
3488             os_path.as_path(),
3489         )
3490         .expect("copying of OS disk failed");
3491 
3492         let mut child = GuestCommand::new(&guest)
3493             .args(["--cpus", "boot=1"])
3494             .args(["--memory", "size=512M"])
3495             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
3496             .args([
3497                 "--disk",
3498                 format!("path={},direct=on", os_path.as_path().to_str().unwrap()).as_str(),
3499                 format!(
3500                     "path={}",
3501                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
3502                 )
3503                 .as_str(),
3504             ])
3505             .default_net()
3506             .capture_output()
3507             .spawn()
3508             .unwrap();
3509 
3510         let r = std::panic::catch_unwind(|| {
3511             guest.wait_vm_boot(Some(120)).unwrap();
3512         });
3513 
3514         kill_child(&mut child);
3515         let output = child.wait_with_output().unwrap();
3516 
3517         handle_child_output(r, &output);
3518     }
3519 
3520     #[test]
3521     fn test_vhost_user_net_default() {
3522         test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, false, false)
3523     }
3524 
3525     #[test]
3526     fn test_vhost_user_net_named_tap() {
3527         test_vhost_user_net(
3528             Some("mytap0"),
3529             2,
3530             &prepare_vhost_user_net_daemon,
3531             false,
3532             false,
3533         )
3534     }
3535 
3536     #[test]
3537     fn test_vhost_user_net_existing_tap() {
3538         test_vhost_user_net(
3539             Some("vunet-tap0"),
3540             2,
3541             &prepare_vhost_user_net_daemon,
3542             false,
3543             false,
3544         )
3545     }
3546 
3547     #[test]
3548     fn test_vhost_user_net_multiple_queues() {
3549         test_vhost_user_net(None, 4, &prepare_vhost_user_net_daemon, false, false)
3550     }
3551 
3552     #[test]
3553     fn test_vhost_user_net_tap_multiple_queues() {
3554         test_vhost_user_net(
3555             Some("vunet-tap1"),
3556             4,
3557             &prepare_vhost_user_net_daemon,
3558             false,
3559             false,
3560         )
3561     }
3562 
3563     #[test]
3564     fn test_vhost_user_net_host_mac() {
3565         test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, true, false)
3566     }
3567 
3568     #[test]
3569     fn test_vhost_user_net_client_mode() {
3570         test_vhost_user_net(None, 2, &prepare_vhost_user_net_daemon, false, true)
3571     }
3572 
3573     #[test]
3574     #[cfg(not(target_arch = "aarch64"))]
3575     fn test_vhost_user_blk_default() {
3576         test_vhost_user_blk(2, false, false, Some(&prepare_vubd))
3577     }
3578 
3579     #[test]
3580     #[cfg(not(target_arch = "aarch64"))]
3581     fn test_vhost_user_blk_readonly() {
3582         test_vhost_user_blk(1, true, false, Some(&prepare_vubd))
3583     }
3584 
3585     #[test]
3586     #[cfg(not(target_arch = "aarch64"))]
3587     fn test_vhost_user_blk_direct() {
3588         test_vhost_user_blk(1, false, true, Some(&prepare_vubd))
3589     }
3590 
3591     #[test]
3592     fn test_boot_from_vhost_user_blk_default() {
3593         test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd))
3594     }
3595 
3596     #[test]
3597     #[cfg(target_arch = "x86_64")]
3598     fn test_split_irqchip() {
3599         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3600         let guest = Guest::new(Box::new(focal));
3601 
3602         let mut child = GuestCommand::new(&guest)
3603             .args(["--cpus", "boot=1"])
3604             .args(["--memory", "size=512M"])
3605             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3606             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3607             .default_disks()
3608             .default_net()
3609             .capture_output()
3610             .spawn()
3611             .unwrap();
3612 
3613         let r = std::panic::catch_unwind(|| {
3614             guest.wait_vm_boot(None).unwrap();
3615 
3616             assert_eq!(
3617                 guest
3618                     .ssh_command("grep -c IO-APIC.*timer /proc/interrupts || true")
3619                     .unwrap()
3620                     .trim()
3621                     .parse::<u32>()
3622                     .unwrap_or(1),
3623                 0
3624             );
3625             assert_eq!(
3626                 guest
3627                     .ssh_command("grep -c IO-APIC.*cascade /proc/interrupts || true")
3628                     .unwrap()
3629                     .trim()
3630                     .parse::<u32>()
3631                     .unwrap_or(1),
3632                 0
3633             );
3634         });
3635 
3636         kill_child(&mut child);
3637         let output = child.wait_with_output().unwrap();
3638 
3639         handle_child_output(r, &output);
3640     }
3641 
3642     #[test]
3643     #[cfg(target_arch = "x86_64")]
3644     fn test_dmi_serial_number() {
3645         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3646         let guest = Guest::new(Box::new(focal));
3647 
3648         let mut child = GuestCommand::new(&guest)
3649             .args(["--cpus", "boot=1"])
3650             .args(["--memory", "size=512M"])
3651             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3652             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3653             .args(["--platform", "serial_number=a=b;c=d"])
3654             .default_disks()
3655             .default_net()
3656             .capture_output()
3657             .spawn()
3658             .unwrap();
3659 
3660         let r = std::panic::catch_unwind(|| {
3661             guest.wait_vm_boot(None).unwrap();
3662 
3663             assert_eq!(
3664                 guest
3665                     .ssh_command("sudo cat /sys/class/dmi/id/product_serial")
3666                     .unwrap()
3667                     .trim(),
3668                 "a=b;c=d"
3669             );
3670         });
3671 
3672         kill_child(&mut child);
3673         let output = child.wait_with_output().unwrap();
3674 
3675         handle_child_output(r, &output);
3676     }
3677 
3678     #[test]
3679     #[cfg(target_arch = "x86_64")]
3680     fn test_dmi_uuid() {
3681         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3682         let guest = Guest::new(Box::new(focal));
3683 
3684         let mut child = GuestCommand::new(&guest)
3685             .args(["--cpus", "boot=1"])
3686             .args(["--memory", "size=512M"])
3687             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3688             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3689             .args(["--platform", "uuid=1e8aa28a-435d-4027-87f4-40dceff1fa0a"])
3690             .default_disks()
3691             .default_net()
3692             .capture_output()
3693             .spawn()
3694             .unwrap();
3695 
3696         let r = std::panic::catch_unwind(|| {
3697             guest.wait_vm_boot(None).unwrap();
3698 
3699             assert_eq!(
3700                 guest
3701                     .ssh_command("sudo cat /sys/class/dmi/id/product_uuid")
3702                     .unwrap()
3703                     .trim(),
3704                 "1e8aa28a-435d-4027-87f4-40dceff1fa0a"
3705             );
3706         });
3707 
3708         kill_child(&mut child);
3709         let output = child.wait_with_output().unwrap();
3710 
3711         handle_child_output(r, &output);
3712     }
3713 
3714     #[test]
3715     #[cfg(target_arch = "x86_64")]
3716     fn test_dmi_oem_strings() {
3717         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3718         let guest = Guest::new(Box::new(focal));
3719 
3720         let s1 = "io.systemd.credential:xx=yy";
3721         let s2 = "This is a test string";
3722 
3723         let oem_strings = format!("oem_strings=[{s1},{s2}]");
3724 
3725         let mut child = GuestCommand::new(&guest)
3726             .args(["--cpus", "boot=1"])
3727             .args(["--memory", "size=512M"])
3728             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3729             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3730             .args(["--platform", &oem_strings])
3731             .default_disks()
3732             .default_net()
3733             .capture_output()
3734             .spawn()
3735             .unwrap();
3736 
3737         let r = std::panic::catch_unwind(|| {
3738             guest.wait_vm_boot(None).unwrap();
3739 
3740             assert_eq!(
3741                 guest
3742                     .ssh_command("sudo dmidecode --oem-string count")
3743                     .unwrap()
3744                     .trim(),
3745                 "2"
3746             );
3747 
3748             assert_eq!(
3749                 guest
3750                     .ssh_command("sudo dmidecode --oem-string 1")
3751                     .unwrap()
3752                     .trim(),
3753                 s1
3754             );
3755 
3756             assert_eq!(
3757                 guest
3758                     .ssh_command("sudo dmidecode --oem-string 2")
3759                     .unwrap()
3760                     .trim(),
3761                 s2
3762             );
3763         });
3764 
3765         kill_child(&mut child);
3766         let output = child.wait_with_output().unwrap();
3767 
3768         handle_child_output(r, &output);
3769     }
3770 
3771     #[test]
3772     fn test_virtio_fs() {
3773         _test_virtio_fs(&prepare_virtiofsd, false, None)
3774     }
3775 
3776     #[test]
3777     fn test_virtio_fs_hotplug() {
3778         _test_virtio_fs(&prepare_virtiofsd, true, None)
3779     }
3780 
3781     #[test]
3782     #[cfg(not(feature = "mshv"))]
3783     fn test_virtio_fs_multi_segment_hotplug() {
3784         _test_virtio_fs(&prepare_virtiofsd, true, Some(15))
3785     }
3786 
3787     #[test]
3788     #[cfg(not(feature = "mshv"))]
3789     fn test_virtio_fs_multi_segment() {
3790         _test_virtio_fs(&prepare_virtiofsd, false, Some(15))
3791     }
3792 
3793     #[test]
3794     fn test_virtio_pmem_discard_writes() {
3795         test_virtio_pmem(true, false)
3796     }
3797 
3798     #[test]
3799     fn test_virtio_pmem_with_size() {
3800         test_virtio_pmem(true, true)
3801     }
3802 
3803     #[test]
3804     fn test_boot_from_virtio_pmem() {
3805         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3806         let guest = Guest::new(Box::new(focal));
3807 
3808         let kernel_path = direct_kernel_boot_path();
3809 
3810         let mut child = GuestCommand::new(&guest)
3811             .args(["--cpus", "boot=1"])
3812             .args(["--memory", "size=512M"])
3813             .args(["--kernel", kernel_path.to_str().unwrap()])
3814             .args([
3815                 "--disk",
3816                 format!(
3817                     "path={}",
3818                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
3819                 )
3820                 .as_str(),
3821             ])
3822             .default_net()
3823             .args([
3824                 "--pmem",
3825                 format!(
3826                     "file={},size={}",
3827                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap(),
3828                     fs::metadata(guest.disk_config.disk(DiskType::OperatingSystem).unwrap())
3829                         .unwrap()
3830                         .len()
3831                 )
3832                 .as_str(),
3833             ])
3834             .args([
3835                 "--cmdline",
3836                 DIRECT_KERNEL_BOOT_CMDLINE
3837                     .replace("vda1", "pmem0p1")
3838                     .as_str(),
3839             ])
3840             .capture_output()
3841             .spawn()
3842             .unwrap();
3843 
3844         let r = std::panic::catch_unwind(|| {
3845             guest.wait_vm_boot(None).unwrap();
3846 
3847             // Simple checks to validate the VM booted properly
3848             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
3849             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
3850         });
3851 
3852         kill_child(&mut child);
3853         let output = child.wait_with_output().unwrap();
3854 
3855         handle_child_output(r, &output);
3856     }
3857 
3858     #[test]
3859     fn test_multiple_network_interfaces() {
3860         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3861         let guest = Guest::new(Box::new(focal));
3862 
3863         let kernel_path = direct_kernel_boot_path();
3864 
3865         let mut child = GuestCommand::new(&guest)
3866             .args(["--cpus", "boot=1"])
3867             .args(["--memory", "size=512M"])
3868             .args(["--kernel", kernel_path.to_str().unwrap()])
3869             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3870             .default_disks()
3871             .args([
3872                 "--net",
3873                 guest.default_net_string().as_str(),
3874                 "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0",
3875                 "tap=mytap1,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.0",
3876             ])
3877             .capture_output()
3878             .spawn()
3879             .unwrap();
3880 
3881         let r = std::panic::catch_unwind(|| {
3882             guest.wait_vm_boot(None).unwrap();
3883 
3884             let tap_count = exec_host_command_output("ip link | grep -c mytap1");
3885             assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1");
3886 
3887             // 3 network interfaces + default localhost ==> 4 interfaces
3888             assert_eq!(
3889                 guest
3890                     .ssh_command("ip -o link | wc -l")
3891                     .unwrap()
3892                     .trim()
3893                     .parse::<u32>()
3894                     .unwrap_or_default(),
3895                 4
3896             );
3897         });
3898 
3899         kill_child(&mut child);
3900         let output = child.wait_with_output().unwrap();
3901 
3902         handle_child_output(r, &output);
3903     }
3904 
3905     #[test]
3906     #[cfg(target_arch = "aarch64")]
3907     fn test_pmu_on() {
3908         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3909         let guest = Guest::new(Box::new(focal));
3910         let mut child = GuestCommand::new(&guest)
3911             .args(["--cpus", "boot=1"])
3912             .args(["--memory", "size=512M"])
3913             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3914             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3915             .default_disks()
3916             .default_net()
3917             .capture_output()
3918             .spawn()
3919             .unwrap();
3920 
3921         let r = std::panic::catch_unwind(|| {
3922             guest.wait_vm_boot(None).unwrap();
3923 
3924             // Test that PMU exists.
3925             assert_eq!(
3926                 guest
3927                     .ssh_command(GREP_PMU_IRQ_CMD)
3928                     .unwrap()
3929                     .trim()
3930                     .parse::<u32>()
3931                     .unwrap_or_default(),
3932                 1
3933             );
3934         });
3935 
3936         kill_child(&mut child);
3937         let output = child.wait_with_output().unwrap();
3938 
3939         handle_child_output(r, &output);
3940     }
3941 
3942     #[test]
3943     fn test_serial_off() {
3944         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3945         let guest = Guest::new(Box::new(focal));
3946         let mut child = GuestCommand::new(&guest)
3947             .args(["--cpus", "boot=1"])
3948             .args(["--memory", "size=512M"])
3949             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3950             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
3951             .default_disks()
3952             .default_net()
3953             .args(["--serial", "off"])
3954             .capture_output()
3955             .spawn()
3956             .unwrap();
3957 
3958         let r = std::panic::catch_unwind(|| {
3959             guest.wait_vm_boot(None).unwrap();
3960 
3961             // Test that there is no ttyS0
3962             assert_eq!(
3963                 guest
3964                     .ssh_command(GREP_SERIAL_IRQ_CMD)
3965                     .unwrap()
3966                     .trim()
3967                     .parse::<u32>()
3968                     .unwrap_or(1),
3969                 0
3970             );
3971         });
3972 
3973         kill_child(&mut child);
3974         let output = child.wait_with_output().unwrap();
3975 
3976         handle_child_output(r, &output);
3977     }
3978 
3979     #[test]
3980     fn test_serial_null() {
3981         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
3982         let guest = Guest::new(Box::new(focal));
3983         let mut cmd = GuestCommand::new(&guest);
3984         #[cfg(target_arch = "x86_64")]
3985         let console_str: &str = "console=ttyS0";
3986         #[cfg(target_arch = "aarch64")]
3987         let console_str: &str = "console=ttyAMA0";
3988 
3989         cmd.args(["--cpus", "boot=1"])
3990             .args(["--memory", "size=512M"])
3991             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
3992             .args([
3993                 "--cmdline",
3994                 DIRECT_KERNEL_BOOT_CMDLINE
3995                     .replace("console=hvc0 ", console_str)
3996                     .as_str(),
3997             ])
3998             .default_disks()
3999             .default_net()
4000             .args(["--serial", "null"])
4001             .args(["--console", "off"])
4002             .capture_output();
4003 
4004         let mut child = cmd.spawn().unwrap();
4005 
4006         let r = std::panic::catch_unwind(|| {
4007             guest.wait_vm_boot(None).unwrap();
4008 
4009             // Test that there is a ttyS0
4010             assert_eq!(
4011                 guest
4012                     .ssh_command(GREP_SERIAL_IRQ_CMD)
4013                     .unwrap()
4014                     .trim()
4015                     .parse::<u32>()
4016                     .unwrap_or_default(),
4017                 1
4018             );
4019         });
4020 
4021         kill_child(&mut child);
4022         let output = child.wait_with_output().unwrap();
4023         handle_child_output(r, &output);
4024 
4025         let r = std::panic::catch_unwind(|| {
4026             assert!(!String::from_utf8_lossy(&output.stdout).contains(CONSOLE_TEST_STRING));
4027         });
4028 
4029         handle_child_output(r, &output);
4030     }
4031 
4032     #[test]
4033     fn test_serial_tty() {
4034         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4035         let guest = Guest::new(Box::new(focal));
4036 
4037         let kernel_path = direct_kernel_boot_path();
4038 
4039         #[cfg(target_arch = "x86_64")]
4040         let console_str: &str = "console=ttyS0";
4041         #[cfg(target_arch = "aarch64")]
4042         let console_str: &str = "console=ttyAMA0";
4043 
4044         let mut child = GuestCommand::new(&guest)
4045             .args(["--cpus", "boot=1"])
4046             .args(["--memory", "size=512M"])
4047             .args(["--kernel", kernel_path.to_str().unwrap()])
4048             .args([
4049                 "--cmdline",
4050                 DIRECT_KERNEL_BOOT_CMDLINE
4051                     .replace("console=hvc0 ", console_str)
4052                     .as_str(),
4053             ])
4054             .default_disks()
4055             .default_net()
4056             .args(["--serial", "tty"])
4057             .args(["--console", "off"])
4058             .capture_output()
4059             .spawn()
4060             .unwrap();
4061 
4062         let r = std::panic::catch_unwind(|| {
4063             guest.wait_vm_boot(None).unwrap();
4064 
4065             // Test that there is a ttyS0
4066             assert_eq!(
4067                 guest
4068                     .ssh_command(GREP_SERIAL_IRQ_CMD)
4069                     .unwrap()
4070                     .trim()
4071                     .parse::<u32>()
4072                     .unwrap_or_default(),
4073                 1
4074             );
4075         });
4076 
4077         // This sleep is needed to wait for the login prompt
4078         thread::sleep(std::time::Duration::new(2, 0));
4079 
4080         kill_child(&mut child);
4081         let output = child.wait_with_output().unwrap();
4082         handle_child_output(r, &output);
4083 
4084         let r = std::panic::catch_unwind(|| {
4085             assert!(String::from_utf8_lossy(&output.stdout).contains(CONSOLE_TEST_STRING));
4086         });
4087 
4088         handle_child_output(r, &output);
4089     }
4090 
4091     #[test]
4092     fn test_serial_file() {
4093         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4094         let guest = Guest::new(Box::new(focal));
4095 
4096         let serial_path = guest.tmp_dir.as_path().join("serial-output");
4097         #[cfg(target_arch = "x86_64")]
4098         let console_str: &str = "console=ttyS0";
4099         #[cfg(target_arch = "aarch64")]
4100         let console_str: &str = "console=ttyAMA0";
4101 
4102         let mut child = GuestCommand::new(&guest)
4103             .args(["--cpus", "boot=1"])
4104             .args(["--memory", "size=512M"])
4105             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
4106             .args([
4107                 "--cmdline",
4108                 DIRECT_KERNEL_BOOT_CMDLINE
4109                     .replace("console=hvc0 ", console_str)
4110                     .as_str(),
4111             ])
4112             .default_disks()
4113             .default_net()
4114             .args([
4115                 "--serial",
4116                 format!("file={}", serial_path.to_str().unwrap()).as_str(),
4117             ])
4118             .capture_output()
4119             .spawn()
4120             .unwrap();
4121 
4122         let r = std::panic::catch_unwind(|| {
4123             guest.wait_vm_boot(None).unwrap();
4124 
4125             // Test that there is a ttyS0
4126             assert_eq!(
4127                 guest
4128                     .ssh_command(GREP_SERIAL_IRQ_CMD)
4129                     .unwrap()
4130                     .trim()
4131                     .parse::<u32>()
4132                     .unwrap_or_default(),
4133                 1
4134             );
4135 
4136             guest.ssh_command("sudo shutdown -h now").unwrap();
4137         });
4138 
4139         let _ = child.wait_timeout(std::time::Duration::from_secs(20));
4140         kill_child(&mut child);
4141         let output = child.wait_with_output().unwrap();
4142         handle_child_output(r, &output);
4143 
4144         let r = std::panic::catch_unwind(|| {
4145             // Check that the cloud-hypervisor binary actually terminated
4146             assert!(output.status.success());
4147 
4148             // Do this check after shutdown of the VM as an easy way to ensure
4149             // all writes are flushed to disk
4150             let mut f = std::fs::File::open(serial_path).unwrap();
4151             let mut buf = String::new();
4152             f.read_to_string(&mut buf).unwrap();
4153             assert!(buf.contains(CONSOLE_TEST_STRING));
4154         });
4155 
4156         handle_child_output(r, &output);
4157     }
4158 
4159     #[test]
4160     fn test_pty_interaction() {
4161         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4162         let guest = Guest::new(Box::new(focal));
4163         let api_socket = temp_api_path(&guest.tmp_dir);
4164         let serial_option = if cfg!(target_arch = "x86_64") {
4165             " console=ttyS0"
4166         } else {
4167             " console=ttyAMA0"
4168         };
4169         let cmdline = DIRECT_KERNEL_BOOT_CMDLINE.to_owned() + serial_option;
4170 
4171         let mut child = GuestCommand::new(&guest)
4172             .args(["--cpus", "boot=1"])
4173             .args(["--memory", "size=512M"])
4174             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
4175             .args(["--cmdline", &cmdline])
4176             .default_disks()
4177             .default_net()
4178             .args(["--serial", "null"])
4179             .args(["--console", "pty"])
4180             .args(["--api-socket", &api_socket])
4181             .spawn()
4182             .unwrap();
4183 
4184         let r = std::panic::catch_unwind(|| {
4185             guest.wait_vm_boot(None).unwrap();
4186             // Get pty fd for console
4187             let console_path = get_pty_path(&api_socket, "console");
4188             _test_pty_interaction(console_path);
4189 
4190             guest.ssh_command("sudo shutdown -h now").unwrap();
4191         });
4192 
4193         let _ = child.wait_timeout(std::time::Duration::from_secs(20));
4194         let _ = child.kill();
4195         let output = child.wait_with_output().unwrap();
4196         handle_child_output(r, &output);
4197 
4198         let r = std::panic::catch_unwind(|| {
4199             // Check that the cloud-hypervisor binary actually terminated
4200             assert!(output.status.success())
4201         });
4202         handle_child_output(r, &output);
4203     }
4204 
4205     #[test]
4206     fn test_serial_socket_interaction() {
4207         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4208         let guest = Guest::new(Box::new(focal));
4209         let serial_socket = guest.tmp_dir.as_path().join("serial.socket");
4210         let serial_socket_pty = guest.tmp_dir.as_path().join("serial.pty");
4211         let serial_option = if cfg!(target_arch = "x86_64") {
4212             " console=ttyS0"
4213         } else {
4214             " console=ttyAMA0"
4215         };
4216         let cmdline = DIRECT_KERNEL_BOOT_CMDLINE.to_owned() + serial_option;
4217 
4218         let mut child = GuestCommand::new(&guest)
4219             .args(["--cpus", "boot=1"])
4220             .args(["--memory", "size=512M"])
4221             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
4222             .args(["--cmdline", &cmdline])
4223             .default_disks()
4224             .default_net()
4225             .args(["--console", "null"])
4226             .args([
4227                 "--serial",
4228                 format!("socket={}", serial_socket.to_str().unwrap()).as_str(),
4229             ])
4230             .spawn()
4231             .unwrap();
4232 
4233         let _ = std::panic::catch_unwind(|| {
4234             guest.wait_vm_boot(None).unwrap();
4235         });
4236 
4237         let mut socat_command = Command::new("socat");
4238         let socat_args = [
4239             &format!("pty,link={},raw", serial_socket_pty.display()),
4240             &format!("UNIX-CONNECT:{}", serial_socket.display()),
4241         ];
4242         socat_command.args(socat_args);
4243 
4244         let mut socat_child = socat_command.spawn().unwrap();
4245         thread::sleep(std::time::Duration::new(1, 0));
4246 
4247         let _ = std::panic::catch_unwind(|| {
4248             _test_pty_interaction(serial_socket_pty);
4249         });
4250 
4251         let _ = socat_child.kill();
4252         let _ = socat_child.wait();
4253 
4254         let r = std::panic::catch_unwind(|| {
4255             guest.ssh_command("sudo shutdown -h now").unwrap();
4256         });
4257 
4258         let _ = child.wait_timeout(std::time::Duration::from_secs(20));
4259         kill_child(&mut child);
4260         let output = child.wait_with_output().unwrap();
4261         handle_child_output(r, &output);
4262 
4263         let r = std::panic::catch_unwind(|| {
4264             // Check that the cloud-hypervisor binary actually terminated
4265             if !output.status.success() {
4266                 panic!(
4267                     "Cloud Hypervisor process failed to terminate gracefully: {:?}",
4268                     output.status
4269                 );
4270             }
4271         });
4272         handle_child_output(r, &output);
4273     }
4274 
4275     #[test]
4276     fn test_virtio_console() {
4277         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4278         let guest = Guest::new(Box::new(focal));
4279 
4280         let kernel_path = direct_kernel_boot_path();
4281 
4282         let mut child = GuestCommand::new(&guest)
4283             .args(["--cpus", "boot=1"])
4284             .args(["--memory", "size=512M"])
4285             .args(["--kernel", kernel_path.to_str().unwrap()])
4286             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
4287             .default_disks()
4288             .default_net()
4289             .args(["--console", "tty"])
4290             .args(["--serial", "null"])
4291             .capture_output()
4292             .spawn()
4293             .unwrap();
4294 
4295         let text = String::from("On a branch floating down river a cricket, singing.");
4296         let cmd = format!("echo {text} | sudo tee /dev/hvc0");
4297 
4298         let r = std::panic::catch_unwind(|| {
4299             guest.wait_vm_boot(None).unwrap();
4300 
4301             assert!(guest
4302                 .does_device_vendor_pair_match("0x1043", "0x1af4")
4303                 .unwrap_or_default());
4304 
4305             guest.ssh_command(&cmd).unwrap();
4306         });
4307 
4308         kill_child(&mut child);
4309         let output = child.wait_with_output().unwrap();
4310         handle_child_output(r, &output);
4311 
4312         let r = std::panic::catch_unwind(|| {
4313             assert!(String::from_utf8_lossy(&output.stdout).contains(&text));
4314         });
4315 
4316         handle_child_output(r, &output);
4317     }
4318 
4319     #[test]
4320     fn test_console_file() {
4321         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4322         let guest = Guest::new(Box::new(focal));
4323 
4324         let console_path = guest.tmp_dir.as_path().join("console-output");
4325         let mut child = GuestCommand::new(&guest)
4326             .args(["--cpus", "boot=1"])
4327             .args(["--memory", "size=512M"])
4328             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
4329             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
4330             .default_disks()
4331             .default_net()
4332             .args([
4333                 "--console",
4334                 format!("file={}", console_path.to_str().unwrap()).as_str(),
4335             ])
4336             .capture_output()
4337             .spawn()
4338             .unwrap();
4339 
4340         guest.wait_vm_boot(None).unwrap();
4341 
4342         guest.ssh_command("sudo shutdown -h now").unwrap();
4343 
4344         let _ = child.wait_timeout(std::time::Duration::from_secs(20));
4345         kill_child(&mut child);
4346         let output = child.wait_with_output().unwrap();
4347 
4348         let r = std::panic::catch_unwind(|| {
4349             // Check that the cloud-hypervisor binary actually terminated
4350             assert!(output.status.success());
4351 
4352             // Do this check after shutdown of the VM as an easy way to ensure
4353             // all writes are flushed to disk
4354             let mut f = std::fs::File::open(console_path).unwrap();
4355             let mut buf = String::new();
4356             f.read_to_string(&mut buf).unwrap();
4357 
4358             if !buf.contains(CONSOLE_TEST_STRING) {
4359                 eprintln!(
4360                     "\n\n==== Console file output ====\n\n{buf}\n\n==== End console file output ===="
4361                 );
4362             }
4363             assert!(buf.contains(CONSOLE_TEST_STRING));
4364         });
4365 
4366         handle_child_output(r, &output);
4367     }
4368 
4369     #[test]
4370     #[cfg(target_arch = "x86_64")]
4371     #[cfg(not(feature = "mshv"))]
4372     // The VFIO integration test starts cloud-hypervisor guest with 3 TAP
4373     // backed networking interfaces, bound through a simple bridge on the host.
4374     // So if the nested cloud-hypervisor succeeds in getting a directly
4375     // assigned interface from its cloud-hypervisor host, we should be able to
4376     // ssh into it, and verify that it's running with the right kernel command
4377     // line (We tag the command line from cloud-hypervisor for that purpose).
4378     // The third device is added to validate that hotplug works correctly since
4379     // it is being added to the L2 VM through hotplugging mechanism.
4380     // Also, we pass-through a virtio-blk device to the L2 VM to test the 32-bit
4381     // vfio device support
4382     fn test_vfio() {
4383         setup_vfio_network_interfaces();
4384 
4385         let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string());
4386         let guest = Guest::new_from_ip_range(Box::new(jammy), "172.18", 0);
4387 
4388         let mut workload_path = dirs::home_dir().unwrap();
4389         workload_path.push("workloads");
4390 
4391         let kernel_path = direct_kernel_boot_path();
4392 
4393         let mut vfio_path = workload_path.clone();
4394         vfio_path.push("vfio");
4395 
4396         let mut cloud_init_vfio_base_path = vfio_path.clone();
4397         cloud_init_vfio_base_path.push("cloudinit.img");
4398 
4399         // We copy our cloudinit into the vfio mount point, for the nested
4400         // cloud-hypervisor guest to use.
4401         rate_limited_copy(
4402             guest.disk_config.disk(DiskType::CloudInit).unwrap(),
4403             &cloud_init_vfio_base_path,
4404         )
4405         .expect("copying of cloud-init disk failed");
4406 
4407         let mut vfio_disk_path = workload_path.clone();
4408         vfio_disk_path.push("vfio.img");
4409 
4410         // Create the vfio disk image
4411         let output = Command::new("mkfs.ext4")
4412             .arg("-d")
4413             .arg(vfio_path.to_str().unwrap())
4414             .arg(vfio_disk_path.to_str().unwrap())
4415             .arg("2g")
4416             .output()
4417             .unwrap();
4418         if !output.status.success() {
4419             eprintln!("{}", String::from_utf8_lossy(&output.stderr));
4420             panic!("mkfs.ext4 command generated an error");
4421         }
4422 
4423         let mut blk_file_path = workload_path;
4424         blk_file_path.push("blk.img");
4425 
4426         let vfio_tap0 = "vfio-tap0";
4427         let vfio_tap1 = "vfio-tap1";
4428         let vfio_tap2 = "vfio-tap2";
4429         let vfio_tap3 = "vfio-tap3";
4430 
4431         let mut child = GuestCommand::new(&guest)
4432             .args(["--cpus", "boot=4"])
4433             .args(["--memory", "size=2G,hugepages=on,shared=on"])
4434             .args(["--kernel", kernel_path.to_str().unwrap()])
4435             .args([
4436                 "--disk",
4437                 format!(
4438                     "path={}",
4439                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
4440                 )
4441                 .as_str(),
4442                 format!(
4443                     "path={}",
4444                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
4445                 )
4446                 .as_str(),
4447                 format!("path={}", vfio_disk_path.to_str().unwrap()).as_str(),
4448                 format!("path={},iommu=on,readonly=true", blk_file_path.to_str().unwrap()).as_str(),
4449             ])
4450             .args([
4451                 "--cmdline",
4452                 format!(
4453                     "{DIRECT_KERNEL_BOOT_CMDLINE} kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts"
4454                 )
4455                 .as_str(),
4456             ])
4457             .args([
4458                 "--net",
4459                 format!("tap={},mac={}", vfio_tap0, guest.network.guest_mac).as_str(),
4460                 format!(
4461                     "tap={},mac={},iommu=on",
4462                     vfio_tap1, guest.network.l2_guest_mac1
4463                 )
4464                 .as_str(),
4465                 format!(
4466                     "tap={},mac={},iommu=on",
4467                     vfio_tap2, guest.network.l2_guest_mac2
4468                 )
4469                 .as_str(),
4470                 format!(
4471                     "tap={},mac={},iommu=on",
4472                     vfio_tap3, guest.network.l2_guest_mac3
4473                 )
4474                 .as_str(),
4475             ])
4476             .capture_output()
4477             .spawn()
4478             .unwrap();
4479 
4480         thread::sleep(std::time::Duration::new(30, 0));
4481 
4482         let r = std::panic::catch_unwind(|| {
4483             guest.ssh_command_l1("sudo systemctl start vfio").unwrap();
4484             thread::sleep(std::time::Duration::new(120, 0));
4485 
4486             // We booted our cloud hypervisor L2 guest with a "VFIOTAG" tag
4487             // added to its kernel command line.
4488             // Let's ssh into it and verify that it's there. If it is it means
4489             // we're in the right guest (The L2 one) because the QEMU L1 guest
4490             // does not have this command line tag.
4491             assert!(check_matched_lines_count(
4492                 guest.ssh_command_l2_1("cat /proc/cmdline").unwrap().trim(),
4493                 vec!["VFIOTAG"],
4494                 1
4495             ));
4496 
4497             // Let's also verify from the second virtio-net device passed to
4498             // the L2 VM.
4499             assert!(check_matched_lines_count(
4500                 guest.ssh_command_l2_2("cat /proc/cmdline").unwrap().trim(),
4501                 vec!["VFIOTAG"],
4502                 1
4503             ));
4504 
4505             // Check the amount of PCI devices appearing in L2 VM.
4506             assert!(check_lines_count(
4507                 guest
4508                     .ssh_command_l2_1("ls /sys/bus/pci/devices")
4509                     .unwrap()
4510                     .trim(),
4511                 8
4512             ));
4513 
4514             // Check both if /dev/vdc exists and if the block size is 16M in L2 VM
4515             assert!(check_matched_lines_count(
4516                 guest.ssh_command_l2_1("lsblk").unwrap().trim(),
4517                 vec!["vdc", "16M"],
4518                 1
4519             ));
4520 
4521             // Hotplug an extra virtio-net device through L2 VM.
4522             guest
4523                 .ssh_command_l1(
4524                     "echo 0000:00:09.0 | sudo tee /sys/bus/pci/devices/0000:00:09.0/driver/unbind",
4525                 )
4526                 .unwrap();
4527             guest
4528                 .ssh_command_l1("echo 0000:00:09.0 | sudo tee /sys/bus/pci/drivers/vfio-pci/bind")
4529                 .unwrap();
4530             let vfio_hotplug_output = guest
4531                 .ssh_command_l1(
4532                     "sudo /mnt/ch-remote \
4533                  --api-socket=/tmp/ch_api.sock \
4534                  add-device path=/sys/bus/pci/devices/0000:00:09.0,id=vfio123",
4535                 )
4536                 .unwrap();
4537             assert!(check_matched_lines_count(
4538                 vfio_hotplug_output.trim(),
4539                 vec!["{\"id\":\"vfio123\",\"bdf\":\"0000:00:08.0\"}"],
4540                 1
4541             ));
4542 
4543             thread::sleep(std::time::Duration::new(10, 0));
4544 
4545             // Let's also verify from the third virtio-net device passed to
4546             // the L2 VM. This third device has been hotplugged through the L2
4547             // VM, so this is our way to validate hotplug works for VFIO PCI.
4548             assert!(check_matched_lines_count(
4549                 guest.ssh_command_l2_3("cat /proc/cmdline").unwrap().trim(),
4550                 vec!["VFIOTAG"],
4551                 1
4552             ));
4553 
4554             // Check the amount of PCI devices appearing in L2 VM.
4555             // There should be one more device than before, raising the count
4556             // up to 9 PCI devices.
4557             assert!(check_lines_count(
4558                 guest
4559                     .ssh_command_l2_1("ls /sys/bus/pci/devices")
4560                     .unwrap()
4561                     .trim(),
4562                 9
4563             ));
4564 
4565             // Let's now verify that we can correctly remove the virtio-net
4566             // device through the "remove-device" command responsible for
4567             // unplugging VFIO devices.
4568             guest
4569                 .ssh_command_l1(
4570                     "sudo /mnt/ch-remote \
4571                  --api-socket=/tmp/ch_api.sock \
4572                  remove-device vfio123",
4573                 )
4574                 .unwrap();
4575             thread::sleep(std::time::Duration::new(10, 0));
4576 
4577             // Check the amount of PCI devices appearing in L2 VM is back down
4578             // to 8 devices.
4579             assert!(check_lines_count(
4580                 guest
4581                     .ssh_command_l2_1("ls /sys/bus/pci/devices")
4582                     .unwrap()
4583                     .trim(),
4584                 8
4585             ));
4586 
4587             // Perform memory hotplug in L2 and validate the memory is showing
4588             // up as expected. In order to check, we will use the virtio-net
4589             // device already passed through L2 as a VFIO device, this will
4590             // verify that VFIO devices are functional with memory hotplug.
4591             assert!(guest.get_total_memory_l2().unwrap_or_default() > 480_000);
4592             guest
4593                 .ssh_command_l2_1(
4594                     "sudo bash -c 'echo online > /sys/devices/system/memory/auto_online_blocks'",
4595                 )
4596                 .unwrap();
4597             guest
4598                 .ssh_command_l1(
4599                     "sudo /mnt/ch-remote \
4600                  --api-socket=/tmp/ch_api.sock \
4601                  resize --memory=1073741824",
4602                 )
4603                 .unwrap();
4604             assert!(guest.get_total_memory_l2().unwrap_or_default() > 960_000);
4605         });
4606 
4607         kill_child(&mut child);
4608         let output = child.wait_with_output().unwrap();
4609 
4610         cleanup_vfio_network_interfaces();
4611 
4612         handle_child_output(r, &output);
4613     }
4614 
4615     #[test]
4616     fn test_direct_kernel_boot_noacpi() {
4617         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4618         let guest = Guest::new(Box::new(focal));
4619 
4620         let kernel_path = direct_kernel_boot_path();
4621 
4622         let mut child = GuestCommand::new(&guest)
4623             .args(["--cpus", "boot=1"])
4624             .args(["--memory", "size=512M"])
4625             .args(["--kernel", kernel_path.to_str().unwrap()])
4626             .args([
4627                 "--cmdline",
4628                 format!("{DIRECT_KERNEL_BOOT_CMDLINE} acpi=off").as_str(),
4629             ])
4630             .default_disks()
4631             .default_net()
4632             .capture_output()
4633             .spawn()
4634             .unwrap();
4635 
4636         let r = std::panic::catch_unwind(|| {
4637             guest.wait_vm_boot(None).unwrap();
4638 
4639             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
4640             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
4641         });
4642 
4643         kill_child(&mut child);
4644         let output = child.wait_with_output().unwrap();
4645 
4646         handle_child_output(r, &output);
4647     }
4648 
4649     #[test]
4650     fn test_virtio_vsock() {
4651         _test_virtio_vsock(false)
4652     }
4653 
4654     #[test]
4655     fn test_virtio_vsock_hotplug() {
4656         _test_virtio_vsock(true);
4657     }
4658 
4659     #[test]
4660     fn test_api_http_shutdown() {
4661         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4662         let guest = Guest::new(Box::new(focal));
4663 
4664         _test_api_shutdown(TargetApi::new_http_api(&guest.tmp_dir), guest)
4665     }
4666 
4667     #[test]
4668     fn test_api_http_delete() {
4669         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4670         let guest = Guest::new(Box::new(focal));
4671 
4672         _test_api_delete(TargetApi::new_http_api(&guest.tmp_dir), guest);
4673     }
4674 
4675     #[test]
4676     fn test_api_http_pause_resume() {
4677         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4678         let guest = Guest::new(Box::new(focal));
4679 
4680         _test_api_pause_resume(TargetApi::new_http_api(&guest.tmp_dir), guest)
4681     }
4682 
4683     #[test]
4684     fn test_api_http_create_boot() {
4685         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4686         let guest = Guest::new(Box::new(focal));
4687 
4688         _test_api_create_boot(TargetApi::new_http_api(&guest.tmp_dir), guest)
4689     }
4690 
4691     #[test]
4692     fn test_virtio_iommu() {
4693         _test_virtio_iommu(cfg!(target_arch = "x86_64"))
4694     }
4695 
4696     #[test]
4697     // We cannot force the software running in the guest to reprogram the BAR
4698     // with some different addresses, but we have a reliable way of testing it
4699     // with a standard Linux kernel.
4700     // By removing a device from the PCI tree, and then rescanning the tree,
4701     // Linux consistently chooses to reorganize the PCI device BARs to other
4702     // locations in the guest address space.
4703     // This test creates a dedicated PCI network device to be checked as being
4704     // properly probed first, then removing it, and adding it again by doing a
4705     // rescan.
4706     fn test_pci_bar_reprogramming() {
4707         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4708         let guest = Guest::new(Box::new(focal));
4709 
4710         #[cfg(target_arch = "x86_64")]
4711         let kernel_path = direct_kernel_boot_path();
4712         #[cfg(target_arch = "aarch64")]
4713         let kernel_path = edk2_path();
4714 
4715         let mut child = GuestCommand::new(&guest)
4716             .args(["--cpus", "boot=1"])
4717             .args(["--memory", "size=512M"])
4718             .args(["--kernel", kernel_path.to_str().unwrap()])
4719             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
4720             .default_disks()
4721             .args([
4722                 "--net",
4723                 guest.default_net_string().as_str(),
4724                 "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0",
4725             ])
4726             .capture_output()
4727             .spawn()
4728             .unwrap();
4729 
4730         let r = std::panic::catch_unwind(|| {
4731             guest.wait_vm_boot(None).unwrap();
4732 
4733             // 2 network interfaces + default localhost ==> 3 interfaces
4734             assert_eq!(
4735                 guest
4736                     .ssh_command("ip -o link | wc -l")
4737                     .unwrap()
4738                     .trim()
4739                     .parse::<u32>()
4740                     .unwrap_or_default(),
4741                 3
4742             );
4743 
4744             let init_bar_addr = guest
4745                 .ssh_command(
4746                     "sudo awk '{print $1; exit}' /sys/bus/pci/devices/0000:00:05.0/resource",
4747                 )
4748                 .unwrap();
4749 
4750             // Remove the PCI device
4751             guest
4752                 .ssh_command("echo 1 | sudo tee /sys/bus/pci/devices/0000:00:05.0/remove")
4753                 .unwrap();
4754 
4755             // Only 1 network interface left + default localhost ==> 2 interfaces
4756             assert_eq!(
4757                 guest
4758                     .ssh_command("ip -o link | wc -l")
4759                     .unwrap()
4760                     .trim()
4761                     .parse::<u32>()
4762                     .unwrap_or_default(),
4763                 2
4764             );
4765 
4766             // Remove the PCI device
4767             guest
4768                 .ssh_command("echo 1 | sudo tee /sys/bus/pci/rescan")
4769                 .unwrap();
4770 
4771             // Back to 2 network interface + default localhost ==> 3 interfaces
4772             assert_eq!(
4773                 guest
4774                     .ssh_command("ip -o link | wc -l")
4775                     .unwrap()
4776                     .trim()
4777                     .parse::<u32>()
4778                     .unwrap_or_default(),
4779                 3
4780             );
4781 
4782             let new_bar_addr = guest
4783                 .ssh_command(
4784                     "sudo awk '{print $1; exit}' /sys/bus/pci/devices/0000:00:05.0/resource",
4785                 )
4786                 .unwrap();
4787 
4788             // Let's compare the BAR addresses for our virtio-net device.
4789             // They should be different as we expect the BAR reprogramming
4790             // to have happened.
4791             assert_ne!(init_bar_addr, new_bar_addr);
4792         });
4793 
4794         kill_child(&mut child);
4795         let output = child.wait_with_output().unwrap();
4796 
4797         handle_child_output(r, &output);
4798     }
4799 
4800     #[test]
4801     fn test_memory_mergeable_off() {
4802         test_memory_mergeable(false)
4803     }
4804 
4805     #[test]
4806     #[cfg(target_arch = "x86_64")]
4807     fn test_cpu_hotplug() {
4808         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4809         let guest = Guest::new(Box::new(focal));
4810         let api_socket = temp_api_path(&guest.tmp_dir);
4811 
4812         let kernel_path = direct_kernel_boot_path();
4813 
4814         let mut child = GuestCommand::new(&guest)
4815             .args(["--cpus", "boot=2,max=4"])
4816             .args(["--memory", "size=512M"])
4817             .args(["--kernel", kernel_path.to_str().unwrap()])
4818             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
4819             .default_disks()
4820             .default_net()
4821             .args(["--api-socket", &api_socket])
4822             .capture_output()
4823             .spawn()
4824             .unwrap();
4825 
4826         let r = std::panic::catch_unwind(|| {
4827             guest.wait_vm_boot(None).unwrap();
4828 
4829             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2);
4830 
4831             // Resize the VM
4832             let desired_vcpus = 4;
4833             resize_command(&api_socket, Some(desired_vcpus), None, None, None);
4834 
4835             guest
4836                 .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu2/online")
4837                 .unwrap();
4838             guest
4839                 .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu3/online")
4840                 .unwrap();
4841             thread::sleep(std::time::Duration::new(10, 0));
4842             assert_eq!(
4843                 guest.get_cpu_count().unwrap_or_default(),
4844                 u32::from(desired_vcpus)
4845             );
4846 
4847             guest.reboot_linux(0, None);
4848 
4849             assert_eq!(
4850                 guest.get_cpu_count().unwrap_or_default(),
4851                 u32::from(desired_vcpus)
4852             );
4853 
4854             // Resize the VM
4855             let desired_vcpus = 2;
4856             resize_command(&api_socket, Some(desired_vcpus), None, None, None);
4857 
4858             thread::sleep(std::time::Duration::new(10, 0));
4859             assert_eq!(
4860                 guest.get_cpu_count().unwrap_or_default(),
4861                 u32::from(desired_vcpus)
4862             );
4863 
4864             // Resize the VM back up to 4
4865             let desired_vcpus = 4;
4866             resize_command(&api_socket, Some(desired_vcpus), None, None, None);
4867 
4868             guest
4869                 .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu2/online")
4870                 .unwrap();
4871             guest
4872                 .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu3/online")
4873                 .unwrap();
4874             thread::sleep(std::time::Duration::new(10, 0));
4875             assert_eq!(
4876                 guest.get_cpu_count().unwrap_or_default(),
4877                 u32::from(desired_vcpus)
4878             );
4879         });
4880 
4881         kill_child(&mut child);
4882         let output = child.wait_with_output().unwrap();
4883 
4884         handle_child_output(r, &output);
4885     }
4886 
4887     #[test]
4888     fn test_memory_hotplug() {
4889         #[cfg(target_arch = "aarch64")]
4890         let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string();
4891         #[cfg(target_arch = "x86_64")]
4892         let focal_image = FOCAL_IMAGE_NAME.to_string();
4893         let focal = UbuntuDiskConfig::new(focal_image);
4894         let guest = Guest::new(Box::new(focal));
4895         let api_socket = temp_api_path(&guest.tmp_dir);
4896 
4897         #[cfg(target_arch = "aarch64")]
4898         let kernel_path = edk2_path();
4899         #[cfg(target_arch = "x86_64")]
4900         let kernel_path = direct_kernel_boot_path();
4901 
4902         let mut child = GuestCommand::new(&guest)
4903             .args(["--cpus", "boot=2,max=4"])
4904             .args(["--memory", "size=512M,hotplug_size=8192M"])
4905             .args(["--kernel", kernel_path.to_str().unwrap()])
4906             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
4907             .default_disks()
4908             .default_net()
4909             .args(["--balloon", "size=0"])
4910             .args(["--api-socket", &api_socket])
4911             .capture_output()
4912             .spawn()
4913             .unwrap();
4914 
4915         let r = std::panic::catch_unwind(|| {
4916             guest.wait_vm_boot(None).unwrap();
4917 
4918             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
4919 
4920             guest.enable_memory_hotplug();
4921 
4922             // Add RAM to the VM
4923             let desired_ram = 1024 << 20;
4924             resize_command(&api_socket, None, Some(desired_ram), None, None);
4925 
4926             thread::sleep(std::time::Duration::new(10, 0));
4927             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
4928 
4929             // Use balloon to remove RAM from the VM
4930             let desired_balloon = 512 << 20;
4931             resize_command(&api_socket, None, None, Some(desired_balloon), None);
4932 
4933             thread::sleep(std::time::Duration::new(10, 0));
4934             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
4935             assert!(guest.get_total_memory().unwrap_or_default() < 960_000);
4936 
4937             guest.reboot_linux(0, None);
4938 
4939             assert!(guest.get_total_memory().unwrap_or_default() < 960_000);
4940 
4941             // Use balloon add RAM to the VM
4942             let desired_balloon = 0;
4943             resize_command(&api_socket, None, None, Some(desired_balloon), None);
4944 
4945             thread::sleep(std::time::Duration::new(10, 0));
4946 
4947             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
4948 
4949             guest.enable_memory_hotplug();
4950 
4951             // Add RAM to the VM
4952             let desired_ram = 2048 << 20;
4953             resize_command(&api_socket, None, Some(desired_ram), None, None);
4954 
4955             thread::sleep(std::time::Duration::new(10, 0));
4956             assert!(guest.get_total_memory().unwrap_or_default() > 1_920_000);
4957 
4958             // Remove RAM to the VM (only applies after reboot)
4959             let desired_ram = 1024 << 20;
4960             resize_command(&api_socket, None, Some(desired_ram), None, None);
4961 
4962             guest.reboot_linux(1, None);
4963 
4964             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
4965             assert!(guest.get_total_memory().unwrap_or_default() < 1_920_000);
4966         });
4967 
4968         kill_child(&mut child);
4969         let output = child.wait_with_output().unwrap();
4970 
4971         handle_child_output(r, &output);
4972     }
4973 
4974     #[test]
4975     #[cfg(not(feature = "mshv"))]
4976     fn test_virtio_mem() {
4977         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
4978         let guest = Guest::new(Box::new(focal));
4979         let api_socket = temp_api_path(&guest.tmp_dir);
4980 
4981         let kernel_path = direct_kernel_boot_path();
4982 
4983         let mut child = GuestCommand::new(&guest)
4984             .args(["--cpus", "boot=2,max=4"])
4985             .args([
4986                 "--memory",
4987                 "size=512M,hotplug_method=virtio-mem,hotplug_size=8192M",
4988             ])
4989             .args(["--kernel", kernel_path.to_str().unwrap()])
4990             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
4991             .default_disks()
4992             .default_net()
4993             .args(["--api-socket", &api_socket])
4994             .capture_output()
4995             .spawn()
4996             .unwrap();
4997 
4998         let r = std::panic::catch_unwind(|| {
4999             guest.wait_vm_boot(None).unwrap();
5000 
5001             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
5002 
5003             guest.enable_memory_hotplug();
5004 
5005             // Add RAM to the VM
5006             let desired_ram = 1024 << 20;
5007             resize_command(&api_socket, None, Some(desired_ram), None, None);
5008 
5009             thread::sleep(std::time::Duration::new(10, 0));
5010             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
5011 
5012             // Add RAM to the VM
5013             let desired_ram = 2048 << 20;
5014             resize_command(&api_socket, None, Some(desired_ram), None, None);
5015 
5016             thread::sleep(std::time::Duration::new(10, 0));
5017             assert!(guest.get_total_memory().unwrap_or_default() > 1_920_000);
5018 
5019             // Remove RAM from the VM
5020             let desired_ram = 1024 << 20;
5021             resize_command(&api_socket, None, Some(desired_ram), None, None);
5022 
5023             thread::sleep(std::time::Duration::new(10, 0));
5024             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
5025             assert!(guest.get_total_memory().unwrap_or_default() < 1_920_000);
5026 
5027             guest.reboot_linux(0, None);
5028 
5029             // Check the amount of memory after reboot is 1GiB
5030             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
5031             assert!(guest.get_total_memory().unwrap_or_default() < 1_920_000);
5032 
5033             // Check we can still resize to 512MiB
5034             let desired_ram = 512 << 20;
5035             resize_command(&api_socket, None, Some(desired_ram), None, None);
5036             thread::sleep(std::time::Duration::new(10, 0));
5037             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
5038             assert!(guest.get_total_memory().unwrap_or_default() < 960_000);
5039         });
5040 
5041         kill_child(&mut child);
5042         let output = child.wait_with_output().unwrap();
5043 
5044         handle_child_output(r, &output);
5045     }
5046 
5047     #[test]
5048     #[cfg(target_arch = "x86_64")]
5049     #[cfg(not(feature = "mshv"))]
5050     // Test both vCPU and memory resizing together
5051     fn test_resize() {
5052         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5053         let guest = Guest::new(Box::new(focal));
5054         let api_socket = temp_api_path(&guest.tmp_dir);
5055 
5056         let kernel_path = direct_kernel_boot_path();
5057 
5058         let mut child = GuestCommand::new(&guest)
5059             .args(["--cpus", "boot=2,max=4"])
5060             .args(["--memory", "size=512M,hotplug_size=8192M"])
5061             .args(["--kernel", kernel_path.to_str().unwrap()])
5062             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5063             .default_disks()
5064             .default_net()
5065             .args(["--api-socket", &api_socket])
5066             .capture_output()
5067             .spawn()
5068             .unwrap();
5069 
5070         let r = std::panic::catch_unwind(|| {
5071             guest.wait_vm_boot(None).unwrap();
5072 
5073             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2);
5074             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
5075 
5076             guest.enable_memory_hotplug();
5077 
5078             // Resize the VM
5079             let desired_vcpus = 4;
5080             let desired_ram = 1024 << 20;
5081             resize_command(
5082                 &api_socket,
5083                 Some(desired_vcpus),
5084                 Some(desired_ram),
5085                 None,
5086                 None,
5087             );
5088 
5089             guest
5090                 .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu2/online")
5091                 .unwrap();
5092             guest
5093                 .ssh_command("echo 1 | sudo tee /sys/bus/cpu/devices/cpu3/online")
5094                 .unwrap();
5095             thread::sleep(std::time::Duration::new(10, 0));
5096             assert_eq!(
5097                 guest.get_cpu_count().unwrap_or_default(),
5098                 u32::from(desired_vcpus)
5099             );
5100 
5101             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
5102         });
5103 
5104         kill_child(&mut child);
5105         let output = child.wait_with_output().unwrap();
5106 
5107         handle_child_output(r, &output);
5108     }
5109 
5110     #[test]
5111     fn test_memory_overhead() {
5112         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5113         let guest = Guest::new(Box::new(focal));
5114 
5115         let kernel_path = direct_kernel_boot_path();
5116 
5117         let guest_memory_size_kb = 512 * 1024;
5118 
5119         let mut child = GuestCommand::new(&guest)
5120             .args(["--cpus", "boot=1"])
5121             .args(["--memory", format!("size={guest_memory_size_kb}K").as_str()])
5122             .args(["--kernel", kernel_path.to_str().unwrap()])
5123             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5124             .default_net()
5125             .default_disks()
5126             .capture_output()
5127             .spawn()
5128             .unwrap();
5129 
5130         guest.wait_vm_boot(None).unwrap();
5131 
5132         let r = std::panic::catch_unwind(|| {
5133             let overhead = get_vmm_overhead(child.id(), guest_memory_size_kb);
5134             eprintln!("Guest memory overhead: {overhead} vs {MAXIMUM_VMM_OVERHEAD_KB}");
5135             assert!(overhead <= MAXIMUM_VMM_OVERHEAD_KB);
5136         });
5137 
5138         kill_child(&mut child);
5139         let output = child.wait_with_output().unwrap();
5140 
5141         handle_child_output(r, &output);
5142     }
5143 
5144     #[test]
5145     #[cfg(target_arch = "x86_64")]
5146     // This test runs a guest with Landlock enabled and hotplugs a new disk. As
5147     // the path for the hotplug disk is not pre-added to Landlock rules, this
5148     // the test will result in a failure.
5149     fn test_landlock() {
5150         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5151         let guest = Guest::new(Box::new(focal));
5152 
5153         #[cfg(target_arch = "x86_64")]
5154         let kernel_path = direct_kernel_boot_path();
5155         #[cfg(target_arch = "aarch64")]
5156         let kernel_path = edk2_path();
5157 
5158         let api_socket = temp_api_path(&guest.tmp_dir);
5159 
5160         let mut child = GuestCommand::new(&guest)
5161             .args(["--api-socket", &api_socket])
5162             .args(["--cpus", "boot=1"])
5163             .args(["--memory", "size=512M"])
5164             .args(["--kernel", kernel_path.to_str().unwrap()])
5165             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5166             .args(["--landlock"])
5167             .default_disks()
5168             .default_net()
5169             .capture_output()
5170             .spawn()
5171             .unwrap();
5172 
5173         let r = std::panic::catch_unwind(|| {
5174             guest.wait_vm_boot(None).unwrap();
5175 
5176             // Check /dev/vdc is not there
5177             assert_eq!(
5178                 guest
5179                     .ssh_command("lsblk | grep -c vdc.*16M || true")
5180                     .unwrap()
5181                     .trim()
5182                     .parse::<u32>()
5183                     .unwrap_or(1),
5184                 0
5185             );
5186 
5187             // Now let's add the extra disk.
5188             let mut blk_file_path = dirs::home_dir().unwrap();
5189             blk_file_path.push("workloads");
5190             blk_file_path.push("blk.img");
5191             // As the path to the hotplug disk is not pre-added, this remote
5192             // command will fail.
5193             assert!(!remote_command(
5194                 &api_socket,
5195                 "add-disk",
5196                 Some(
5197                     format!(
5198                         "path={},id=test0,readonly=true",
5199                         blk_file_path.to_str().unwrap()
5200                     )
5201                     .as_str()
5202                 ),
5203             ));
5204         });
5205 
5206         let _ = child.kill();
5207         let output = child.wait_with_output().unwrap();
5208 
5209         handle_child_output(r, &output);
5210     }
5211 
5212     fn _test_disk_hotplug(landlock_enabled: bool) {
5213         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5214         let guest = Guest::new(Box::new(focal));
5215 
5216         #[cfg(target_arch = "x86_64")]
5217         let kernel_path = direct_kernel_boot_path();
5218         #[cfg(target_arch = "aarch64")]
5219         let kernel_path = edk2_path();
5220 
5221         let api_socket = temp_api_path(&guest.tmp_dir);
5222 
5223         let mut blk_file_path = dirs::home_dir().unwrap();
5224         blk_file_path.push("workloads");
5225         blk_file_path.push("blk.img");
5226 
5227         let mut cmd = GuestCommand::new(&guest);
5228         if landlock_enabled {
5229             cmd.args(["--landlock"]).args([
5230                 "--landlock-rules",
5231                 format!("path={blk_file_path:?},access=rw").as_str(),
5232             ]);
5233         }
5234 
5235         cmd.args(["--api-socket", &api_socket])
5236             .args(["--cpus", "boot=1"])
5237             .args(["--memory", "size=512M"])
5238             .args(["--kernel", kernel_path.to_str().unwrap()])
5239             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5240             .default_disks()
5241             .default_net()
5242             .capture_output();
5243 
5244         let mut child = cmd.spawn().unwrap();
5245 
5246         let r = std::panic::catch_unwind(|| {
5247             guest.wait_vm_boot(None).unwrap();
5248 
5249             // Check /dev/vdc is not there
5250             assert_eq!(
5251                 guest
5252                     .ssh_command("lsblk | grep -c vdc.*16M || true")
5253                     .unwrap()
5254                     .trim()
5255                     .parse::<u32>()
5256                     .unwrap_or(1),
5257                 0
5258             );
5259 
5260             // Now let's add the extra disk.
5261             let (cmd_success, cmd_output) = remote_command_w_output(
5262                 &api_socket,
5263                 "add-disk",
5264                 Some(
5265                     format!(
5266                         "path={},id=test0,readonly=true",
5267                         blk_file_path.to_str().unwrap()
5268                     )
5269                     .as_str(),
5270                 ),
5271             );
5272             assert!(cmd_success);
5273             assert!(String::from_utf8_lossy(&cmd_output)
5274                 .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}"));
5275 
5276             thread::sleep(std::time::Duration::new(10, 0));
5277 
5278             // Check that /dev/vdc exists and the block size is 16M.
5279             assert_eq!(
5280                 guest
5281                     .ssh_command("lsblk | grep vdc | grep -c 16M")
5282                     .unwrap()
5283                     .trim()
5284                     .parse::<u32>()
5285                     .unwrap_or_default(),
5286                 1
5287             );
5288             // And check the block device can be read.
5289             guest
5290                 .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16")
5291                 .unwrap();
5292 
5293             // Let's remove it the extra disk.
5294             assert!(remote_command(&api_socket, "remove-device", Some("test0")));
5295             thread::sleep(std::time::Duration::new(5, 0));
5296             // And check /dev/vdc is not there
5297             assert_eq!(
5298                 guest
5299                     .ssh_command("lsblk | grep -c vdc.*16M || true")
5300                     .unwrap()
5301                     .trim()
5302                     .parse::<u32>()
5303                     .unwrap_or(1),
5304                 0
5305             );
5306 
5307             // And add it back to validate unplug did work correctly.
5308             let (cmd_success, cmd_output) = remote_command_w_output(
5309                 &api_socket,
5310                 "add-disk",
5311                 Some(
5312                     format!(
5313                         "path={},id=test0,readonly=true",
5314                         blk_file_path.to_str().unwrap()
5315                     )
5316                     .as_str(),
5317                 ),
5318             );
5319             assert!(cmd_success);
5320             assert!(String::from_utf8_lossy(&cmd_output)
5321                 .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}"));
5322 
5323             thread::sleep(std::time::Duration::new(10, 0));
5324 
5325             // Check that /dev/vdc exists and the block size is 16M.
5326             assert_eq!(
5327                 guest
5328                     .ssh_command("lsblk | grep vdc | grep -c 16M")
5329                     .unwrap()
5330                     .trim()
5331                     .parse::<u32>()
5332                     .unwrap_or_default(),
5333                 1
5334             );
5335             // And check the block device can be read.
5336             guest
5337                 .ssh_command("sudo dd if=/dev/vdc of=/dev/null bs=1M iflag=direct count=16")
5338                 .unwrap();
5339 
5340             // Reboot the VM.
5341             guest.reboot_linux(0, None);
5342 
5343             // Check still there after reboot
5344             assert_eq!(
5345                 guest
5346                     .ssh_command("lsblk | grep vdc | grep -c 16M")
5347                     .unwrap()
5348                     .trim()
5349                     .parse::<u32>()
5350                     .unwrap_or_default(),
5351                 1
5352             );
5353 
5354             assert!(remote_command(&api_socket, "remove-device", Some("test0")));
5355 
5356             thread::sleep(std::time::Duration::new(20, 0));
5357 
5358             // Check device has gone away
5359             assert_eq!(
5360                 guest
5361                     .ssh_command("lsblk | grep -c vdc.*16M || true")
5362                     .unwrap()
5363                     .trim()
5364                     .parse::<u32>()
5365                     .unwrap_or(1),
5366                 0
5367             );
5368 
5369             guest.reboot_linux(1, None);
5370 
5371             // Check device still absent
5372             assert_eq!(
5373                 guest
5374                     .ssh_command("lsblk | grep -c vdc.*16M || true")
5375                     .unwrap()
5376                     .trim()
5377                     .parse::<u32>()
5378                     .unwrap_or(1),
5379                 0
5380             );
5381         });
5382 
5383         kill_child(&mut child);
5384         let output = child.wait_with_output().unwrap();
5385 
5386         handle_child_output(r, &output);
5387     }
5388 
5389     #[test]
5390     fn test_disk_hotplug() {
5391         _test_disk_hotplug(false)
5392     }
5393 
5394     #[test]
5395     #[cfg(target_arch = "x86_64")]
5396     fn test_disk_hotplug_with_landlock() {
5397         _test_disk_hotplug(true)
5398     }
5399 
5400     fn create_loop_device(backing_file_path: &str, block_size: u32, num_retries: usize) -> String {
5401         const LOOP_CONFIGURE: u64 = 0x4c0a;
5402         const LOOP_CTL_GET_FREE: u64 = 0x4c82;
5403         const LOOP_CTL_PATH: &str = "/dev/loop-control";
5404         const LOOP_DEVICE_PREFIX: &str = "/dev/loop";
5405 
5406         #[repr(C)]
5407         struct LoopInfo64 {
5408             lo_device: u64,
5409             lo_inode: u64,
5410             lo_rdevice: u64,
5411             lo_offset: u64,
5412             lo_sizelimit: u64,
5413             lo_number: u32,
5414             lo_encrypt_type: u32,
5415             lo_encrypt_key_size: u32,
5416             lo_flags: u32,
5417             lo_file_name: [u8; 64],
5418             lo_crypt_name: [u8; 64],
5419             lo_encrypt_key: [u8; 32],
5420             lo_init: [u64; 2],
5421         }
5422 
5423         impl Default for LoopInfo64 {
5424             fn default() -> Self {
5425                 LoopInfo64 {
5426                     lo_device: 0,
5427                     lo_inode: 0,
5428                     lo_rdevice: 0,
5429                     lo_offset: 0,
5430                     lo_sizelimit: 0,
5431                     lo_number: 0,
5432                     lo_encrypt_type: 0,
5433                     lo_encrypt_key_size: 0,
5434                     lo_flags: 0,
5435                     lo_file_name: [0; 64],
5436                     lo_crypt_name: [0; 64],
5437                     lo_encrypt_key: [0; 32],
5438                     lo_init: [0; 2],
5439                 }
5440             }
5441         }
5442 
5443         #[derive(Default)]
5444         #[repr(C)]
5445         struct LoopConfig {
5446             fd: u32,
5447             block_size: u32,
5448             info: LoopInfo64,
5449             _reserved: [u64; 8],
5450         }
5451 
5452         // Open loop-control device
5453         let loop_ctl_file = OpenOptions::new()
5454             .read(true)
5455             .write(true)
5456             .open(LOOP_CTL_PATH)
5457             .unwrap();
5458 
5459         // Request a free loop device
5460         let loop_device_number =
5461             unsafe { libc::ioctl(loop_ctl_file.as_raw_fd(), LOOP_CTL_GET_FREE as _) };
5462 
5463         if loop_device_number < 0 {
5464             panic!("Couldn't find a free loop device");
5465         }
5466 
5467         // Create loop device path
5468         let loop_device_path = format!("{LOOP_DEVICE_PREFIX}{loop_device_number}");
5469 
5470         // Open loop device
5471         let loop_device_file = OpenOptions::new()
5472             .read(true)
5473             .write(true)
5474             .open(&loop_device_path)
5475             .unwrap();
5476 
5477         // Open backing file
5478         let backing_file = OpenOptions::new()
5479             .read(true)
5480             .write(true)
5481             .open(backing_file_path)
5482             .unwrap();
5483 
5484         let loop_config = LoopConfig {
5485             fd: backing_file.as_raw_fd() as u32,
5486             block_size,
5487             ..Default::default()
5488         };
5489 
5490         for i in 0..num_retries {
5491             let ret = unsafe {
5492                 libc::ioctl(
5493                     loop_device_file.as_raw_fd(),
5494                     LOOP_CONFIGURE as _,
5495                     &loop_config,
5496                 )
5497             };
5498             if ret != 0 {
5499                 if i < num_retries - 1 {
5500                     println!(
5501                         "Iteration {}: Failed to configure the loop device {}: {}",
5502                         i,
5503                         loop_device_path,
5504                         std::io::Error::last_os_error()
5505                     );
5506                 } else {
5507                     panic!(
5508                         "Failed {} times trying to configure the loop device {}: {}",
5509                         num_retries,
5510                         loop_device_path,
5511                         std::io::Error::last_os_error()
5512                     );
5513                 }
5514             } else {
5515                 break;
5516             }
5517 
5518             // Wait for a bit before retrying
5519             thread::sleep(std::time::Duration::new(5, 0));
5520         }
5521 
5522         loop_device_path
5523     }
5524 
5525     #[test]
5526     fn test_virtio_block_topology() {
5527         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5528         let guest = Guest::new(Box::new(focal));
5529 
5530         let kernel_path = direct_kernel_boot_path();
5531         let test_disk_path = guest.tmp_dir.as_path().join("test.img");
5532 
5533         let output = exec_host_command_output(
5534             format!(
5535                 "qemu-img create -f raw {} 16M",
5536                 test_disk_path.to_str().unwrap()
5537             )
5538             .as_str(),
5539         );
5540         if !output.status.success() {
5541             let stdout = String::from_utf8_lossy(&output.stdout);
5542             let stderr = String::from_utf8_lossy(&output.stderr);
5543             panic!("qemu-img command failed\nstdout\n{stdout}\nstderr\n{stderr}");
5544         }
5545 
5546         let loop_dev = create_loop_device(test_disk_path.to_str().unwrap(), 4096, 5);
5547 
5548         let mut child = GuestCommand::new(&guest)
5549             .args(["--cpus", "boot=1"])
5550             .args(["--memory", "size=512M"])
5551             .args(["--kernel", kernel_path.to_str().unwrap()])
5552             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5553             .args([
5554                 "--disk",
5555                 format!(
5556                     "path={}",
5557                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
5558                 )
5559                 .as_str(),
5560                 format!(
5561                     "path={}",
5562                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
5563                 )
5564                 .as_str(),
5565                 format!("path={}", &loop_dev).as_str(),
5566             ])
5567             .default_net()
5568             .capture_output()
5569             .spawn()
5570             .unwrap();
5571 
5572         let r = std::panic::catch_unwind(|| {
5573             guest.wait_vm_boot(None).unwrap();
5574 
5575             // MIN-IO column
5576             assert_eq!(
5577                 guest
5578                     .ssh_command("lsblk -t| grep vdc | awk '{print $3}'")
5579                     .unwrap()
5580                     .trim()
5581                     .parse::<u32>()
5582                     .unwrap_or_default(),
5583                 4096
5584             );
5585             // PHY-SEC column
5586             assert_eq!(
5587                 guest
5588                     .ssh_command("lsblk -t| grep vdc | awk '{print $5}'")
5589                     .unwrap()
5590                     .trim()
5591                     .parse::<u32>()
5592                     .unwrap_or_default(),
5593                 4096
5594             );
5595             // LOG-SEC column
5596             assert_eq!(
5597                 guest
5598                     .ssh_command("lsblk -t| grep vdc | awk '{print $6}'")
5599                     .unwrap()
5600                     .trim()
5601                     .parse::<u32>()
5602                     .unwrap_or_default(),
5603                 4096
5604             );
5605         });
5606 
5607         kill_child(&mut child);
5608         let output = child.wait_with_output().unwrap();
5609 
5610         handle_child_output(r, &output);
5611 
5612         Command::new("losetup")
5613             .args(["-d", &loop_dev])
5614             .output()
5615             .expect("loop device not found");
5616     }
5617 
5618     #[test]
5619     fn test_virtio_balloon_deflate_on_oom() {
5620         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5621         let guest = Guest::new(Box::new(focal));
5622 
5623         let kernel_path = direct_kernel_boot_path();
5624 
5625         let api_socket = temp_api_path(&guest.tmp_dir);
5626 
5627         //Let's start a 4G guest with balloon occupied 2G memory
5628         let mut child = GuestCommand::new(&guest)
5629             .args(["--api-socket", &api_socket])
5630             .args(["--cpus", "boot=1"])
5631             .args(["--memory", "size=4G"])
5632             .args(["--kernel", kernel_path.to_str().unwrap()])
5633             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5634             .args(["--balloon", "size=2G,deflate_on_oom=on"])
5635             .default_disks()
5636             .default_net()
5637             .capture_output()
5638             .spawn()
5639             .unwrap();
5640 
5641         let r = std::panic::catch_unwind(|| {
5642             guest.wait_vm_boot(None).unwrap();
5643 
5644             // Wait for balloon memory's initialization and check its size.
5645             // The virtio-balloon driver might take a few seconds to report the
5646             // balloon effective size back to the VMM.
5647             thread::sleep(std::time::Duration::new(20, 0));
5648 
5649             let orig_balloon = balloon_size(&api_socket);
5650             println!("The original balloon memory size is {orig_balloon} bytes");
5651             assert!(orig_balloon == 2147483648);
5652 
5653             // Two steps to verify if the 'deflate_on_oom' parameter works.
5654             // 1st: run a command to trigger an OOM in the guest.
5655             guest
5656                 .ssh_command("echo f | sudo tee /proc/sysrq-trigger")
5657                 .unwrap();
5658 
5659             // Give some time for the OOM to happen in the guest and be reported
5660             // back to the host.
5661             thread::sleep(std::time::Duration::new(20, 0));
5662 
5663             // 2nd: check balloon_mem's value to verify balloon has been automatically deflated
5664             let deflated_balloon = balloon_size(&api_socket);
5665             println!("After deflating, balloon memory size is {deflated_balloon} bytes");
5666             // Verify the balloon size deflated
5667             assert!(deflated_balloon < 2147483648);
5668         });
5669 
5670         kill_child(&mut child);
5671         let output = child.wait_with_output().unwrap();
5672 
5673         handle_child_output(r, &output);
5674     }
5675 
5676     #[test]
5677     #[cfg(not(feature = "mshv"))]
5678     fn test_virtio_balloon_free_page_reporting() {
5679         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5680         let guest = Guest::new(Box::new(focal));
5681 
5682         //Let's start a 4G guest with balloon occupied 2G memory
5683         let mut child = GuestCommand::new(&guest)
5684             .args(["--cpus", "boot=1"])
5685             .args(["--memory", "size=4G"])
5686             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
5687             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5688             .args(["--balloon", "size=0,free_page_reporting=on"])
5689             .default_disks()
5690             .default_net()
5691             .capture_output()
5692             .spawn()
5693             .unwrap();
5694 
5695         let pid = child.id();
5696         let r = std::panic::catch_unwind(|| {
5697             guest.wait_vm_boot(None).unwrap();
5698 
5699             // Check the initial RSS is less than 1GiB
5700             let rss = process_rss_kib(pid);
5701             println!("RSS {rss} < 1048576");
5702             assert!(rss < 1048576);
5703 
5704             // Spawn a command inside the guest to consume 2GiB of RAM for 60
5705             // seconds
5706             let guest_ip = guest.network.guest_ip.clone();
5707             thread::spawn(move || {
5708                 ssh_command_ip(
5709                     "stress --vm 1 --vm-bytes 2G --vm-keep --timeout 60",
5710                     &guest_ip,
5711                     DEFAULT_SSH_RETRIES,
5712                     DEFAULT_SSH_TIMEOUT,
5713                 )
5714                 .unwrap();
5715             });
5716 
5717             // Wait for 50 seconds to make sure the stress command is consuming
5718             // the expected amount of memory.
5719             thread::sleep(std::time::Duration::new(50, 0));
5720             let rss = process_rss_kib(pid);
5721             println!("RSS {rss} >= 2097152");
5722             assert!(rss >= 2097152);
5723 
5724             // Wait for an extra minute to make sure the stress command has
5725             // completed and that the guest reported the free pages to the VMM
5726             // through the virtio-balloon device. We expect the RSS to be under
5727             // 2GiB.
5728             thread::sleep(std::time::Duration::new(60, 0));
5729             let rss = process_rss_kib(pid);
5730             println!("RSS {rss} < 2097152");
5731             assert!(rss < 2097152);
5732         });
5733 
5734         kill_child(&mut child);
5735         let output = child.wait_with_output().unwrap();
5736 
5737         handle_child_output(r, &output);
5738     }
5739 
5740     #[test]
5741     fn test_pmem_hotplug() {
5742         _test_pmem_hotplug(None)
5743     }
5744 
5745     #[test]
5746     fn test_pmem_multi_segment_hotplug() {
5747         _test_pmem_hotplug(Some(15))
5748     }
5749 
5750     fn _test_pmem_hotplug(pci_segment: Option<u16>) {
5751         #[cfg(target_arch = "aarch64")]
5752         let focal_image = FOCAL_IMAGE_UPDATE_KERNEL_NAME.to_string();
5753         #[cfg(target_arch = "x86_64")]
5754         let focal_image = FOCAL_IMAGE_NAME.to_string();
5755         let focal = UbuntuDiskConfig::new(focal_image);
5756         let guest = Guest::new(Box::new(focal));
5757 
5758         #[cfg(target_arch = "x86_64")]
5759         let kernel_path = direct_kernel_boot_path();
5760         #[cfg(target_arch = "aarch64")]
5761         let kernel_path = edk2_path();
5762 
5763         let api_socket = temp_api_path(&guest.tmp_dir);
5764 
5765         let mut cmd = GuestCommand::new(&guest);
5766 
5767         cmd.args(["--api-socket", &api_socket])
5768             .args(["--cpus", "boot=1"])
5769             .args(["--memory", "size=512M"])
5770             .args(["--kernel", kernel_path.to_str().unwrap()])
5771             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5772             .default_disks()
5773             .default_net()
5774             .capture_output();
5775 
5776         if pci_segment.is_some() {
5777             cmd.args([
5778                 "--platform",
5779                 &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"),
5780             ]);
5781         }
5782 
5783         let mut child = cmd.spawn().unwrap();
5784 
5785         let r = std::panic::catch_unwind(|| {
5786             guest.wait_vm_boot(None).unwrap();
5787 
5788             // Check /dev/pmem0 is not there
5789             assert_eq!(
5790                 guest
5791                     .ssh_command("lsblk | grep -c pmem0 || true")
5792                     .unwrap()
5793                     .trim()
5794                     .parse::<u32>()
5795                     .unwrap_or(1),
5796                 0
5797             );
5798 
5799             let pmem_temp_file = TempFile::new().unwrap();
5800             pmem_temp_file.as_file().set_len(128 << 20).unwrap();
5801             let (cmd_success, cmd_output) = remote_command_w_output(
5802                 &api_socket,
5803                 "add-pmem",
5804                 Some(&format!(
5805                     "file={},id=test0{}",
5806                     pmem_temp_file.as_path().to_str().unwrap(),
5807                     if let Some(pci_segment) = pci_segment {
5808                         format!(",pci_segment={pci_segment}")
5809                     } else {
5810                         "".to_owned()
5811                     }
5812                 )),
5813             );
5814             assert!(cmd_success);
5815             if let Some(pci_segment) = pci_segment {
5816                 assert!(String::from_utf8_lossy(&cmd_output).contains(&format!(
5817                     "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}"
5818                 )));
5819             } else {
5820                 assert!(String::from_utf8_lossy(&cmd_output)
5821                     .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}"));
5822             }
5823 
5824             // Check that /dev/pmem0 exists and the block size is 128M
5825             assert_eq!(
5826                 guest
5827                     .ssh_command("lsblk | grep pmem0 | grep -c 128M")
5828                     .unwrap()
5829                     .trim()
5830                     .parse::<u32>()
5831                     .unwrap_or_default(),
5832                 1
5833             );
5834 
5835             guest.reboot_linux(0, None);
5836 
5837             // Check still there after reboot
5838             assert_eq!(
5839                 guest
5840                     .ssh_command("lsblk | grep pmem0 | grep -c 128M")
5841                     .unwrap()
5842                     .trim()
5843                     .parse::<u32>()
5844                     .unwrap_or_default(),
5845                 1
5846             );
5847 
5848             assert!(remote_command(&api_socket, "remove-device", Some("test0")));
5849 
5850             thread::sleep(std::time::Duration::new(20, 0));
5851 
5852             // Check device has gone away
5853             assert_eq!(
5854                 guest
5855                     .ssh_command("lsblk | grep -c pmem0.*128M || true")
5856                     .unwrap()
5857                     .trim()
5858                     .parse::<u32>()
5859                     .unwrap_or(1),
5860                 0
5861             );
5862 
5863             guest.reboot_linux(1, None);
5864 
5865             // Check still absent after reboot
5866             assert_eq!(
5867                 guest
5868                     .ssh_command("lsblk | grep -c pmem0.*128M || true")
5869                     .unwrap()
5870                     .trim()
5871                     .parse::<u32>()
5872                     .unwrap_or(1),
5873                 0
5874             );
5875         });
5876 
5877         kill_child(&mut child);
5878         let output = child.wait_with_output().unwrap();
5879 
5880         handle_child_output(r, &output);
5881     }
5882 
5883     #[test]
5884     fn test_net_hotplug() {
5885         _test_net_hotplug(None)
5886     }
5887 
5888     #[test]
5889     fn test_net_multi_segment_hotplug() {
5890         _test_net_hotplug(Some(15))
5891     }
5892 
5893     fn _test_net_hotplug(pci_segment: Option<u16>) {
5894         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
5895         let guest = Guest::new(Box::new(focal));
5896 
5897         #[cfg(target_arch = "x86_64")]
5898         let kernel_path = direct_kernel_boot_path();
5899         #[cfg(target_arch = "aarch64")]
5900         let kernel_path = edk2_path();
5901 
5902         let api_socket = temp_api_path(&guest.tmp_dir);
5903 
5904         // Boot without network
5905         let mut cmd = GuestCommand::new(&guest);
5906 
5907         cmd.args(["--api-socket", &api_socket])
5908             .args(["--cpus", "boot=1"])
5909             .args(["--memory", "size=512M"])
5910             .args(["--kernel", kernel_path.to_str().unwrap()])
5911             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
5912             .default_disks()
5913             .capture_output();
5914 
5915         if pci_segment.is_some() {
5916             cmd.args([
5917                 "--platform",
5918                 &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"),
5919             ]);
5920         }
5921 
5922         let mut child = cmd.spawn().unwrap();
5923 
5924         thread::sleep(std::time::Duration::new(20, 0));
5925 
5926         let r = std::panic::catch_unwind(|| {
5927             // Add network
5928             let (cmd_success, cmd_output) = remote_command_w_output(
5929                 &api_socket,
5930                 "add-net",
5931                 Some(
5932                     format!(
5933                         "{}{},id=test0",
5934                         guest.default_net_string(),
5935                         if let Some(pci_segment) = pci_segment {
5936                             format!(",pci_segment={pci_segment}")
5937                         } else {
5938                             "".to_owned()
5939                         }
5940                     )
5941                     .as_str(),
5942                 ),
5943             );
5944             assert!(cmd_success);
5945 
5946             if let Some(pci_segment) = pci_segment {
5947                 assert!(String::from_utf8_lossy(&cmd_output).contains(&format!(
5948                     "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}"
5949                 )));
5950             } else {
5951                 assert!(String::from_utf8_lossy(&cmd_output)
5952                     .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:05.0\"}"));
5953             }
5954 
5955             thread::sleep(std::time::Duration::new(5, 0));
5956 
5957             // 1 network interfaces + default localhost ==> 2 interfaces
5958             assert_eq!(
5959                 guest
5960                     .ssh_command("ip -o link | wc -l")
5961                     .unwrap()
5962                     .trim()
5963                     .parse::<u32>()
5964                     .unwrap_or_default(),
5965                 2
5966             );
5967 
5968             // Remove network
5969             assert!(remote_command(&api_socket, "remove-device", Some("test0"),));
5970             thread::sleep(std::time::Duration::new(5, 0));
5971 
5972             let (cmd_success, cmd_output) = remote_command_w_output(
5973                 &api_socket,
5974                 "add-net",
5975                 Some(
5976                     format!(
5977                         "{}{},id=test1",
5978                         guest.default_net_string(),
5979                         if let Some(pci_segment) = pci_segment {
5980                             format!(",pci_segment={pci_segment}")
5981                         } else {
5982                             "".to_owned()
5983                         }
5984                     )
5985                     .as_str(),
5986                 ),
5987             );
5988             assert!(cmd_success);
5989 
5990             if let Some(pci_segment) = pci_segment {
5991                 assert!(String::from_utf8_lossy(&cmd_output).contains(&format!(
5992                     "{{\"id\":\"test1\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}"
5993                 )));
5994             } else {
5995                 assert!(String::from_utf8_lossy(&cmd_output)
5996                     .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:05.0\"}"));
5997             }
5998 
5999             thread::sleep(std::time::Duration::new(5, 0));
6000 
6001             // 1 network interfaces + default localhost ==> 2 interfaces
6002             assert_eq!(
6003                 guest
6004                     .ssh_command("ip -o link | wc -l")
6005                     .unwrap()
6006                     .trim()
6007                     .parse::<u32>()
6008                     .unwrap_or_default(),
6009                 2
6010             );
6011 
6012             guest.reboot_linux(0, None);
6013 
6014             // Check still there after reboot
6015             // 1 network interfaces + default localhost ==> 2 interfaces
6016             assert_eq!(
6017                 guest
6018                     .ssh_command("ip -o link | wc -l")
6019                     .unwrap()
6020                     .trim()
6021                     .parse::<u32>()
6022                     .unwrap_or_default(),
6023                 2
6024             );
6025         });
6026 
6027         kill_child(&mut child);
6028         let output = child.wait_with_output().unwrap();
6029 
6030         handle_child_output(r, &output);
6031     }
6032 
6033     #[test]
6034     fn test_initramfs() {
6035         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6036         let guest = Guest::new(Box::new(focal));
6037         let mut workload_path = dirs::home_dir().unwrap();
6038         workload_path.push("workloads");
6039 
6040         #[cfg(target_arch = "x86_64")]
6041         let mut kernels = vec![direct_kernel_boot_path()];
6042         #[cfg(target_arch = "aarch64")]
6043         let kernels = [direct_kernel_boot_path()];
6044 
6045         #[cfg(target_arch = "x86_64")]
6046         {
6047             let mut pvh_kernel_path = workload_path.clone();
6048             pvh_kernel_path.push("vmlinux");
6049             kernels.push(pvh_kernel_path);
6050         }
6051 
6052         let mut initramfs_path = workload_path;
6053         initramfs_path.push("alpine_initramfs.img");
6054 
6055         let test_string = String::from("axz34i9rylotd8n50wbv6kcj7f2qushme1pg");
6056         let cmdline = format!("console=hvc0 quiet TEST_STRING={test_string}");
6057 
6058         kernels.iter().for_each(|k_path| {
6059             let mut child = GuestCommand::new(&guest)
6060                 .args(["--kernel", k_path.to_str().unwrap()])
6061                 .args(["--initramfs", initramfs_path.to_str().unwrap()])
6062                 .args(["--cmdline", &cmdline])
6063                 .capture_output()
6064                 .spawn()
6065                 .unwrap();
6066 
6067             thread::sleep(std::time::Duration::new(20, 0));
6068 
6069             kill_child(&mut child);
6070             let output = child.wait_with_output().unwrap();
6071 
6072             let r = std::panic::catch_unwind(|| {
6073                 let s = String::from_utf8_lossy(&output.stdout);
6074 
6075                 assert_ne!(s.lines().position(|line| line == test_string), None);
6076             });
6077 
6078             handle_child_output(r, &output);
6079         });
6080     }
6081 
6082     #[test]
6083     fn test_counters() {
6084         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6085         let guest = Guest::new(Box::new(focal));
6086         let api_socket = temp_api_path(&guest.tmp_dir);
6087 
6088         let mut cmd = GuestCommand::new(&guest);
6089         cmd.args(["--cpus", "boot=1"])
6090             .args(["--memory", "size=512M"])
6091             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
6092             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6093             .default_disks()
6094             .args(["--net", guest.default_net_string().as_str()])
6095             .args(["--api-socket", &api_socket])
6096             .capture_output();
6097 
6098         let mut child = cmd.spawn().unwrap();
6099 
6100         let r = std::panic::catch_unwind(|| {
6101             guest.wait_vm_boot(None).unwrap();
6102 
6103             let orig_counters = get_counters(&api_socket);
6104             guest
6105                 .ssh_command("dd if=/dev/zero of=test count=8 bs=1M")
6106                 .unwrap();
6107 
6108             let new_counters = get_counters(&api_socket);
6109 
6110             // Check that all the counters have increased
6111             assert!(new_counters > orig_counters);
6112         });
6113 
6114         kill_child(&mut child);
6115         let output = child.wait_with_output().unwrap();
6116 
6117         handle_child_output(r, &output);
6118     }
6119 
6120     #[test]
6121     #[cfg(feature = "guest_debug")]
6122     fn test_coredump() {
6123         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6124         let guest = Guest::new(Box::new(focal));
6125         let api_socket = temp_api_path(&guest.tmp_dir);
6126 
6127         let mut cmd = GuestCommand::new(&guest);
6128         cmd.args(["--cpus", "boot=4"])
6129             .args(["--memory", "size=4G"])
6130             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
6131             .default_disks()
6132             .args(["--net", guest.default_net_string().as_str()])
6133             .args(["--api-socket", &api_socket])
6134             .capture_output();
6135 
6136         let mut child = cmd.spawn().unwrap();
6137         let vmcore_file = temp_vmcore_file_path(&guest.tmp_dir);
6138 
6139         let r = std::panic::catch_unwind(|| {
6140             guest.wait_vm_boot(None).unwrap();
6141 
6142             assert!(remote_command(&api_socket, "pause", None));
6143 
6144             assert!(remote_command(
6145                 &api_socket,
6146                 "coredump",
6147                 Some(format!("file://{vmcore_file}").as_str()),
6148             ));
6149 
6150             // the num of CORE notes should equals to vcpu
6151             let readelf_core_num_cmd =
6152                 format!("readelf --all {vmcore_file} |grep CORE |grep -v Type |wc -l");
6153             let core_num_in_elf = exec_host_command_output(&readelf_core_num_cmd);
6154             assert_eq!(String::from_utf8_lossy(&core_num_in_elf.stdout).trim(), "4");
6155 
6156             // the num of QEMU notes should equals to vcpu
6157             let readelf_vmm_num_cmd = format!("readelf --all {vmcore_file} |grep QEMU |wc -l");
6158             let vmm_num_in_elf = exec_host_command_output(&readelf_vmm_num_cmd);
6159             assert_eq!(String::from_utf8_lossy(&vmm_num_in_elf.stdout).trim(), "4");
6160         });
6161 
6162         kill_child(&mut child);
6163         let output = child.wait_with_output().unwrap();
6164 
6165         handle_child_output(r, &output);
6166     }
6167 
6168     #[test]
6169     #[cfg(feature = "guest_debug")]
6170     fn test_coredump_no_pause() {
6171         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6172         let guest = Guest::new(Box::new(focal));
6173         let api_socket = temp_api_path(&guest.tmp_dir);
6174 
6175         let mut cmd = GuestCommand::new(&guest);
6176         cmd.args(["--cpus", "boot=4"])
6177             .args(["--memory", "size=4G"])
6178             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
6179             .default_disks()
6180             .args(["--net", guest.default_net_string().as_str()])
6181             .args(["--api-socket", &api_socket])
6182             .capture_output();
6183 
6184         let mut child = cmd.spawn().unwrap();
6185         let vmcore_file = temp_vmcore_file_path(&guest.tmp_dir);
6186 
6187         let r = std::panic::catch_unwind(|| {
6188             guest.wait_vm_boot(None).unwrap();
6189 
6190             assert!(remote_command(
6191                 &api_socket,
6192                 "coredump",
6193                 Some(format!("file://{vmcore_file}").as_str()),
6194             ));
6195 
6196             assert_eq!(vm_state(&api_socket), "Running");
6197         });
6198 
6199         kill_child(&mut child);
6200         let output = child.wait_with_output().unwrap();
6201 
6202         handle_child_output(r, &output);
6203     }
6204 
6205     #[test]
6206     fn test_watchdog() {
6207         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6208         let guest = Guest::new(Box::new(focal));
6209         let api_socket = temp_api_path(&guest.tmp_dir);
6210 
6211         let kernel_path = direct_kernel_boot_path();
6212         let event_path = temp_event_monitor_path(&guest.tmp_dir);
6213 
6214         let mut cmd = GuestCommand::new(&guest);
6215         cmd.args(["--cpus", "boot=1"])
6216             .args(["--memory", "size=512M"])
6217             .args(["--kernel", kernel_path.to_str().unwrap()])
6218             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6219             .default_disks()
6220             .args(["--net", guest.default_net_string().as_str()])
6221             .args(["--watchdog"])
6222             .args(["--api-socket", &api_socket])
6223             .args(["--event-monitor", format!("path={event_path}").as_str()])
6224             .capture_output();
6225 
6226         let mut child = cmd.spawn().unwrap();
6227 
6228         let r = std::panic::catch_unwind(|| {
6229             guest.wait_vm_boot(None).unwrap();
6230 
6231             let mut expected_reboot_count = 1;
6232 
6233             // Enable the watchdog with a 15s timeout
6234             enable_guest_watchdog(&guest, 15);
6235 
6236             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
6237             assert_eq!(
6238                 guest
6239                     .ssh_command("sudo journalctl | grep -c -- \"Watchdog started\"")
6240                     .unwrap()
6241                     .trim()
6242                     .parse::<u32>()
6243                     .unwrap_or_default(),
6244                 1
6245             );
6246 
6247             // Allow some normal time to elapse to check we don't get spurious reboots
6248             thread::sleep(std::time::Duration::new(40, 0));
6249             // Check no reboot
6250             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
6251 
6252             // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns.
6253             guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap();
6254             // Allow some time for the watchdog to trigger (max 30s) and reboot to happen
6255             guest.wait_vm_boot(Some(50)).unwrap();
6256             // Check a reboot is triggered by the watchdog
6257             expected_reboot_count += 1;
6258             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
6259 
6260             #[cfg(target_arch = "x86_64")]
6261             {
6262                 // Now pause the VM and remain offline for 30s
6263                 assert!(remote_command(&api_socket, "pause", None));
6264                 let latest_events = [
6265                     &MetaEvent {
6266                         event: "pausing".to_string(),
6267                         device_id: None,
6268                     },
6269                     &MetaEvent {
6270                         event: "paused".to_string(),
6271                         device_id: None,
6272                     },
6273                 ];
6274                 assert!(check_latest_events_exact(&latest_events, &event_path));
6275                 assert!(remote_command(&api_socket, "resume", None));
6276 
6277                 // Check no reboot
6278                 assert_eq!(get_reboot_count(&guest), expected_reboot_count);
6279             }
6280         });
6281 
6282         kill_child(&mut child);
6283         let output = child.wait_with_output().unwrap();
6284 
6285         handle_child_output(r, &output);
6286     }
6287 
6288     #[test]
6289     fn test_pvpanic() {
6290         let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string());
6291         let guest = Guest::new(Box::new(jammy));
6292         let api_socket = temp_api_path(&guest.tmp_dir);
6293         let event_path = temp_event_monitor_path(&guest.tmp_dir);
6294 
6295         let kernel_path = direct_kernel_boot_path();
6296 
6297         let mut cmd = GuestCommand::new(&guest);
6298         cmd.args(["--cpus", "boot=1"])
6299             .args(["--memory", "size=512M"])
6300             .args(["--kernel", kernel_path.to_str().unwrap()])
6301             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6302             .default_disks()
6303             .args(["--net", guest.default_net_string().as_str()])
6304             .args(["--pvpanic"])
6305             .args(["--api-socket", &api_socket])
6306             .args(["--event-monitor", format!("path={event_path}").as_str()])
6307             .capture_output();
6308 
6309         let mut child = cmd.spawn().unwrap();
6310 
6311         let r = std::panic::catch_unwind(|| {
6312             guest.wait_vm_boot(None).unwrap();
6313 
6314             // Trigger guest a panic
6315             make_guest_panic(&guest);
6316 
6317             // Wait a while for guest
6318             thread::sleep(std::time::Duration::new(10, 0));
6319 
6320             let expected_sequential_events = [&MetaEvent {
6321                 event: "panic".to_string(),
6322                 device_id: None,
6323             }];
6324             assert!(check_latest_events_exact(
6325                 &expected_sequential_events,
6326                 &event_path
6327             ));
6328         });
6329 
6330         kill_child(&mut child);
6331         let output = child.wait_with_output().unwrap();
6332 
6333         handle_child_output(r, &output);
6334     }
6335 
6336     #[test]
6337     fn test_tap_from_fd() {
6338         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6339         let guest = Guest::new(Box::new(focal));
6340         let kernel_path = direct_kernel_boot_path();
6341 
6342         // Create a TAP interface with multi-queue enabled
6343         let num_queue_pairs: usize = 2;
6344 
6345         use std::str::FromStr;
6346         let taps = net_util::open_tap(
6347             Some("chtap0"),
6348             Some(std::net::IpAddr::V4(
6349                 std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap(),
6350             )),
6351             None,
6352             &mut None,
6353             None,
6354             num_queue_pairs,
6355             Some(libc::O_RDWR | libc::O_NONBLOCK),
6356         )
6357         .unwrap();
6358 
6359         let mut child = GuestCommand::new(&guest)
6360             .args(["--cpus", &format!("boot={num_queue_pairs}")])
6361             .args(["--memory", "size=512M"])
6362             .args(["--kernel", kernel_path.to_str().unwrap()])
6363             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6364             .default_disks()
6365             .args([
6366                 "--net",
6367                 &format!(
6368                     "fd=[{},{}],mac={},num_queues={}",
6369                     taps[0].as_raw_fd(),
6370                     taps[1].as_raw_fd(),
6371                     guest.network.guest_mac,
6372                     num_queue_pairs * 2
6373                 ),
6374             ])
6375             .capture_output()
6376             .spawn()
6377             .unwrap();
6378 
6379         let r = std::panic::catch_unwind(|| {
6380             guest.wait_vm_boot(None).unwrap();
6381 
6382             assert_eq!(
6383                 guest
6384                     .ssh_command("ip -o link | wc -l")
6385                     .unwrap()
6386                     .trim()
6387                     .parse::<u32>()
6388                     .unwrap_or_default(),
6389                 2
6390             );
6391 
6392             guest.reboot_linux(0, None);
6393 
6394             assert_eq!(
6395                 guest
6396                     .ssh_command("ip -o link | wc -l")
6397                     .unwrap()
6398                     .trim()
6399                     .parse::<u32>()
6400                     .unwrap_or_default(),
6401                 2
6402             );
6403         });
6404 
6405         kill_child(&mut child);
6406         let output = child.wait_with_output().unwrap();
6407 
6408         handle_child_output(r, &output);
6409     }
6410 
6411     // By design, a guest VM won't be able to connect to the host
6412     // machine when using a macvtap network interface (while it can
6413     // communicate externally). As a workaround, this integration
6414     // test creates two macvtap interfaces in 'bridge' mode on the
6415     // same physical net interface, one for the guest and one for
6416     // the host. With additional setup on the IP address and the
6417     // routing table, it enables the communications between the
6418     // guest VM and the host machine.
6419     // Details: https://wiki.libvirt.org/page/TroubleshootMacvtapHostFail
6420     fn _test_macvtap(hotplug: bool, guest_macvtap_name: &str, host_macvtap_name: &str) {
6421         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6422         let guest = Guest::new(Box::new(focal));
6423         let api_socket = temp_api_path(&guest.tmp_dir);
6424 
6425         #[cfg(target_arch = "x86_64")]
6426         let kernel_path = direct_kernel_boot_path();
6427         #[cfg(target_arch = "aarch64")]
6428         let kernel_path = edk2_path();
6429 
6430         let phy_net = "eth0";
6431 
6432         // Create a macvtap interface for the guest VM to use
6433         assert!(exec_host_command_status(&format!(
6434             "sudo ip link add link {phy_net} name {guest_macvtap_name} type macvtap mod bridge"
6435         ))
6436         .success());
6437         assert!(exec_host_command_status(&format!(
6438             "sudo ip link set {} address {} up",
6439             guest_macvtap_name, guest.network.guest_mac
6440         ))
6441         .success());
6442         assert!(
6443             exec_host_command_status(&format!("sudo ip link show {guest_macvtap_name}")).success()
6444         );
6445 
6446         let tap_index =
6447             fs::read_to_string(format!("/sys/class/net/{guest_macvtap_name}/ifindex")).unwrap();
6448         let tap_device = format!("/dev/tap{}", tap_index.trim());
6449 
6450         assert!(exec_host_command_status(&format!("sudo chown $UID.$UID {tap_device}")).success());
6451 
6452         let cstr_tap_device = std::ffi::CString::new(tap_device).unwrap();
6453         let tap_fd1 = unsafe { libc::open(cstr_tap_device.as_ptr(), libc::O_RDWR) };
6454         assert!(tap_fd1 > 0);
6455         let tap_fd2 = unsafe { libc::open(cstr_tap_device.as_ptr(), libc::O_RDWR) };
6456         assert!(tap_fd2 > 0);
6457 
6458         // Create a macvtap on the same physical net interface for
6459         // the host machine to use
6460         assert!(exec_host_command_status(&format!(
6461             "sudo ip link add link {phy_net} name {host_macvtap_name} type macvtap mod bridge"
6462         ))
6463         .success());
6464         // Use default mask "255.255.255.0"
6465         assert!(exec_host_command_status(&format!(
6466             "sudo ip address add {}/24 dev {}",
6467             guest.network.host_ip, host_macvtap_name
6468         ))
6469         .success());
6470         assert!(
6471             exec_host_command_status(&format!("sudo ip link set dev {host_macvtap_name} up"))
6472                 .success()
6473         );
6474 
6475         let mut guest_command = GuestCommand::new(&guest);
6476         guest_command
6477             .args(["--cpus", "boot=2"])
6478             .args(["--memory", "size=512M"])
6479             .args(["--kernel", kernel_path.to_str().unwrap()])
6480             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6481             .default_disks()
6482             .args(["--api-socket", &api_socket]);
6483 
6484         let net_params = format!(
6485             "fd=[{},{}],mac={},num_queues=4",
6486             tap_fd1, tap_fd2, guest.network.guest_mac
6487         );
6488 
6489         if !hotplug {
6490             guest_command.args(["--net", &net_params]);
6491         }
6492 
6493         let mut child = guest_command.capture_output().spawn().unwrap();
6494 
6495         if hotplug {
6496             // Give some time to the VMM process to listen to the API
6497             // socket. This is the only requirement to avoid the following
6498             // call to ch-remote from failing.
6499             thread::sleep(std::time::Duration::new(10, 0));
6500             // Hotplug the virtio-net device
6501             let (cmd_success, cmd_output) =
6502                 remote_command_w_output(&api_socket, "add-net", Some(&net_params));
6503             assert!(cmd_success);
6504             #[cfg(target_arch = "x86_64")]
6505             assert!(String::from_utf8_lossy(&cmd_output)
6506                 .contains("{\"id\":\"_net2\",\"bdf\":\"0000:00:05.0\"}"));
6507             #[cfg(target_arch = "aarch64")]
6508             assert!(String::from_utf8_lossy(&cmd_output)
6509                 .contains("{\"id\":\"_net0\",\"bdf\":\"0000:00:05.0\"}"));
6510         }
6511 
6512         // The functional connectivity provided by the virtio-net device
6513         // gets tested through wait_vm_boot() as it expects to receive a
6514         // HTTP request, and through the SSH command as well.
6515         let r = std::panic::catch_unwind(|| {
6516             guest.wait_vm_boot(None).unwrap();
6517 
6518             assert_eq!(
6519                 guest
6520                     .ssh_command("ip -o link | wc -l")
6521                     .unwrap()
6522                     .trim()
6523                     .parse::<u32>()
6524                     .unwrap_or_default(),
6525                 2
6526             );
6527 
6528             guest.reboot_linux(0, None);
6529 
6530             assert_eq!(
6531                 guest
6532                     .ssh_command("ip -o link | wc -l")
6533                     .unwrap()
6534                     .trim()
6535                     .parse::<u32>()
6536                     .unwrap_or_default(),
6537                 2
6538             );
6539         });
6540 
6541         kill_child(&mut child);
6542 
6543         exec_host_command_status(&format!("sudo ip link del {guest_macvtap_name}"));
6544         exec_host_command_status(&format!("sudo ip link del {host_macvtap_name}"));
6545 
6546         let output = child.wait_with_output().unwrap();
6547 
6548         handle_child_output(r, &output);
6549     }
6550 
6551     #[test]
6552     #[cfg_attr(target_arch = "aarch64", ignore = "See #5443")]
6553     fn test_macvtap() {
6554         _test_macvtap(false, "guestmacvtap0", "hostmacvtap0")
6555     }
6556 
6557     #[test]
6558     #[cfg_attr(target_arch = "aarch64", ignore = "See #5443")]
6559     fn test_macvtap_hotplug() {
6560         _test_macvtap(true, "guestmacvtap1", "hostmacvtap1")
6561     }
6562 
6563     #[test]
6564     #[cfg(not(feature = "mshv"))]
6565     fn test_ovs_dpdk() {
6566         let focal1 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6567         let guest1 = Guest::new(Box::new(focal1));
6568 
6569         let focal2 = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6570         let guest2 = Guest::new(Box::new(focal2));
6571         let api_socket_source = format!("{}.1", temp_api_path(&guest2.tmp_dir));
6572 
6573         let (mut child1, mut child2) =
6574             setup_ovs_dpdk_guests(&guest1, &guest2, &api_socket_source, false);
6575 
6576         // Create the snapshot directory
6577         let snapshot_dir = temp_snapshot_dir_path(&guest2.tmp_dir);
6578 
6579         let r = std::panic::catch_unwind(|| {
6580             // Remove one of the two ports from the OVS bridge
6581             assert!(exec_host_command_status("ovs-vsctl del-port vhost-user1").success());
6582 
6583             // Spawn a new netcat listener in the first VM
6584             let guest_ip = guest1.network.guest_ip.clone();
6585             thread::spawn(move || {
6586                 ssh_command_ip(
6587                     "nc -l 12345",
6588                     &guest_ip,
6589                     DEFAULT_SSH_RETRIES,
6590                     DEFAULT_SSH_TIMEOUT,
6591                 )
6592                 .unwrap();
6593             });
6594 
6595             // Wait for the server to be listening
6596             thread::sleep(std::time::Duration::new(5, 0));
6597 
6598             // Check the connection fails this time
6599             guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap_err();
6600 
6601             // Add the OVS port back
6602             assert!(exec_host_command_status("ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuserclient options:vhost-server-path=/tmp/dpdkvhostclient1").success());
6603 
6604             // And finally check the connection is functional again
6605             guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap();
6606 
6607             // Pause the VM
6608             assert!(remote_command(&api_socket_source, "pause", None));
6609 
6610             // Take a snapshot from the VM
6611             assert!(remote_command(
6612                 &api_socket_source,
6613                 "snapshot",
6614                 Some(format!("file://{snapshot_dir}").as_str()),
6615             ));
6616 
6617             // Wait to make sure the snapshot is completed
6618             thread::sleep(std::time::Duration::new(10, 0));
6619         });
6620 
6621         // Shutdown the source VM
6622         kill_child(&mut child2);
6623         let output = child2.wait_with_output().unwrap();
6624         handle_child_output(r, &output);
6625 
6626         // Remove the vhost-user socket file.
6627         Command::new("rm")
6628             .arg("-f")
6629             .arg("/tmp/dpdkvhostclient2")
6630             .output()
6631             .unwrap();
6632 
6633         let api_socket_restored = format!("{}.2", temp_api_path(&guest2.tmp_dir));
6634         // Restore the VM from the snapshot
6635         let mut child2 = GuestCommand::new(&guest2)
6636             .args(["--api-socket", &api_socket_restored])
6637             .args([
6638                 "--restore",
6639                 format!("source_url=file://{snapshot_dir}").as_str(),
6640             ])
6641             .capture_output()
6642             .spawn()
6643             .unwrap();
6644 
6645         // Wait for the VM to be restored
6646         thread::sleep(std::time::Duration::new(10, 0));
6647 
6648         let r = std::panic::catch_unwind(|| {
6649             // Resume the VM
6650             assert!(remote_command(&api_socket_restored, "resume", None));
6651 
6652             // Spawn a new netcat listener in the first VM
6653             let guest_ip = guest1.network.guest_ip.clone();
6654             thread::spawn(move || {
6655                 ssh_command_ip(
6656                     "nc -l 12345",
6657                     &guest_ip,
6658                     DEFAULT_SSH_RETRIES,
6659                     DEFAULT_SSH_TIMEOUT,
6660                 )
6661                 .unwrap();
6662             });
6663 
6664             // Wait for the server to be listening
6665             thread::sleep(std::time::Duration::new(5, 0));
6666 
6667             // And check the connection is still functional after restore
6668             guest2.ssh_command("nc -vz 172.100.0.1 12345").unwrap();
6669         });
6670 
6671         kill_child(&mut child1);
6672         kill_child(&mut child2);
6673 
6674         let output = child1.wait_with_output().unwrap();
6675         child2.wait().unwrap();
6676 
6677         cleanup_ovs_dpdk();
6678 
6679         handle_child_output(r, &output);
6680     }
6681 
6682     fn setup_spdk_nvme(nvme_dir: &std::path::Path) -> Child {
6683         cleanup_spdk_nvme();
6684 
6685         assert!(exec_host_command_status(&format!(
6686             "mkdir -p {}",
6687             nvme_dir.join("nvme-vfio-user").to_str().unwrap()
6688         ))
6689         .success());
6690         assert!(exec_host_command_status(&format!(
6691             "truncate {} -s 128M",
6692             nvme_dir.join("test-disk.raw").to_str().unwrap()
6693         ))
6694         .success());
6695         assert!(exec_host_command_status(&format!(
6696             "mkfs.ext4 {}",
6697             nvme_dir.join("test-disk.raw").to_str().unwrap()
6698         ))
6699         .success());
6700 
6701         // Start the SPDK nvmf_tgt daemon to present NVMe device as a VFIO user device
6702         let child = Command::new("/usr/local/bin/spdk-nvme/nvmf_tgt")
6703             .args(["-i", "0", "-m", "0x1"])
6704             .spawn()
6705             .unwrap();
6706         thread::sleep(std::time::Duration::new(2, 0));
6707 
6708         assert!(exec_host_command_with_retries(
6709             "/usr/local/bin/spdk-nvme/rpc.py nvmf_create_transport -t VFIOUSER",
6710             3,
6711             std::time::Duration::new(5, 0),
6712         ));
6713         assert!(exec_host_command_status(&format!(
6714             "/usr/local/bin/spdk-nvme/rpc.py bdev_aio_create {} test 512",
6715             nvme_dir.join("test-disk.raw").to_str().unwrap()
6716         ))
6717         .success());
6718         assert!(exec_host_command_status(
6719                 "/usr/local/bin/spdk-nvme/rpc.py nvmf_create_subsystem nqn.2019-07.io.spdk:cnode -a -s test"
6720             )
6721             .success());
6722         assert!(exec_host_command_status(
6723             "/usr/local/bin/spdk-nvme/rpc.py nvmf_subsystem_add_ns nqn.2019-07.io.spdk:cnode test"
6724         )
6725         .success());
6726         assert!(exec_host_command_status(&format!(
6727                 "/usr/local/bin/spdk-nvme/rpc.py nvmf_subsystem_add_listener nqn.2019-07.io.spdk:cnode -t VFIOUSER -a {} -s 0",
6728                 nvme_dir.join("nvme-vfio-user").to_str().unwrap()
6729             ))
6730             .success());
6731 
6732         child
6733     }
6734 
6735     fn cleanup_spdk_nvme() {
6736         exec_host_command_status("pkill -f nvmf_tgt");
6737     }
6738 
6739     #[test]
6740     fn test_vfio_user() {
6741         let jammy_image = JAMMY_IMAGE_NAME.to_string();
6742         let jammy = UbuntuDiskConfig::new(jammy_image);
6743         let guest = Guest::new(Box::new(jammy));
6744 
6745         let spdk_nvme_dir = guest.tmp_dir.as_path().join("test-vfio-user");
6746         let mut spdk_child = setup_spdk_nvme(spdk_nvme_dir.as_path());
6747 
6748         let api_socket = temp_api_path(&guest.tmp_dir);
6749         let mut child = GuestCommand::new(&guest)
6750             .args(["--api-socket", &api_socket])
6751             .args(["--cpus", "boot=1"])
6752             .args(["--memory", "size=1G,shared=on,hugepages=on"])
6753             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
6754             .args(["--serial", "tty", "--console", "off"])
6755             .default_disks()
6756             .default_net()
6757             .capture_output()
6758             .spawn()
6759             .unwrap();
6760 
6761         let r = std::panic::catch_unwind(|| {
6762             guest.wait_vm_boot(None).unwrap();
6763 
6764             // Hotplug the SPDK-NVMe device to the VM
6765             let (cmd_success, cmd_output) = remote_command_w_output(
6766                 &api_socket,
6767                 "add-user-device",
6768                 Some(&format!(
6769                     "socket={},id=vfio_user0",
6770                     spdk_nvme_dir
6771                         .as_path()
6772                         .join("nvme-vfio-user/cntrl")
6773                         .to_str()
6774                         .unwrap(),
6775                 )),
6776             );
6777             assert!(cmd_success);
6778             assert!(String::from_utf8_lossy(&cmd_output)
6779                 .contains("{\"id\":\"vfio_user0\",\"bdf\":\"0000:00:05.0\"}"));
6780 
6781             thread::sleep(std::time::Duration::new(10, 0));
6782 
6783             // Check both if /dev/nvme exists and if the block size is 128M.
6784             assert_eq!(
6785                 guest
6786                     .ssh_command("lsblk | grep nvme0n1 | grep -c 128M")
6787                     .unwrap()
6788                     .trim()
6789                     .parse::<u32>()
6790                     .unwrap_or_default(),
6791                 1
6792             );
6793 
6794             // Check changes persist after reboot
6795             assert_eq!(
6796                 guest.ssh_command("sudo mount /dev/nvme0n1 /mnt").unwrap(),
6797                 ""
6798             );
6799             assert_eq!(guest.ssh_command("ls /mnt").unwrap(), "lost+found\n");
6800             guest
6801                 .ssh_command("echo test123 | sudo tee /mnt/test")
6802                 .unwrap();
6803             assert_eq!(guest.ssh_command("sudo umount /mnt").unwrap(), "");
6804             assert_eq!(guest.ssh_command("ls /mnt").unwrap(), "");
6805 
6806             guest.reboot_linux(0, None);
6807             assert_eq!(
6808                 guest.ssh_command("sudo mount /dev/nvme0n1 /mnt").unwrap(),
6809                 ""
6810             );
6811             assert_eq!(
6812                 guest.ssh_command("sudo cat /mnt/test").unwrap().trim(),
6813                 "test123"
6814             );
6815         });
6816 
6817         let _ = spdk_child.kill();
6818         let _ = spdk_child.wait();
6819 
6820         kill_child(&mut child);
6821         let output = child.wait_with_output().unwrap();
6822 
6823         handle_child_output(r, &output);
6824     }
6825 
6826     #[test]
6827     #[cfg(target_arch = "x86_64")]
6828     fn test_vdpa_block() {
6829         // Before trying to run the test, verify the vdpa_sim_blk module is correctly loaded.
6830         assert!(exec_host_command_status("lsmod | grep vdpa_sim_blk").success());
6831 
6832         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6833         let guest = Guest::new(Box::new(focal));
6834         let api_socket = temp_api_path(&guest.tmp_dir);
6835 
6836         let kernel_path = direct_kernel_boot_path();
6837 
6838         let mut child = GuestCommand::new(&guest)
6839             .args(["--cpus", "boot=2"])
6840             .args(["--memory", "size=512M,hugepages=on"])
6841             .args(["--kernel", kernel_path.to_str().unwrap()])
6842             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6843             .default_disks()
6844             .default_net()
6845             .args(["--vdpa", "path=/dev/vhost-vdpa-0,num_queues=1"])
6846             .args(["--platform", "num_pci_segments=2,iommu_segments=1"])
6847             .args(["--api-socket", &api_socket])
6848             .capture_output()
6849             .spawn()
6850             .unwrap();
6851 
6852         let r = std::panic::catch_unwind(|| {
6853             guest.wait_vm_boot(None).unwrap();
6854 
6855             // Check both if /dev/vdc exists and if the block size is 128M.
6856             assert_eq!(
6857                 guest
6858                     .ssh_command("lsblk | grep vdc | grep -c 128M")
6859                     .unwrap()
6860                     .trim()
6861                     .parse::<u32>()
6862                     .unwrap_or_default(),
6863                 1
6864             );
6865 
6866             // Check the content of the block device after we wrote to it.
6867             // The vpda-sim-blk should let us read what we previously wrote.
6868             guest
6869                 .ssh_command("sudo bash -c 'echo foobar > /dev/vdc'")
6870                 .unwrap();
6871             assert_eq!(
6872                 guest.ssh_command("sudo head -1 /dev/vdc").unwrap().trim(),
6873                 "foobar"
6874             );
6875 
6876             // Hotplug an extra vDPA block device behind the vIOMMU
6877             // Add a new vDPA device to the VM
6878             let (cmd_success, cmd_output) = remote_command_w_output(
6879                 &api_socket,
6880                 "add-vdpa",
6881                 Some("id=myvdpa0,path=/dev/vhost-vdpa-1,num_queues=1,pci_segment=1,iommu=on"),
6882             );
6883             assert!(cmd_success);
6884             assert!(String::from_utf8_lossy(&cmd_output)
6885                 .contains("{\"id\":\"myvdpa0\",\"bdf\":\"0001:00:01.0\"}"));
6886 
6887             thread::sleep(std::time::Duration::new(10, 0));
6888 
6889             // Check IOMMU setup
6890             assert!(guest
6891                 .does_device_vendor_pair_match("0x1057", "0x1af4")
6892                 .unwrap_or_default());
6893             assert_eq!(
6894                 guest
6895                     .ssh_command("ls /sys/kernel/iommu_groups/0/devices")
6896                     .unwrap()
6897                     .trim(),
6898                 "0001:00:01.0"
6899             );
6900 
6901             // Check both if /dev/vdd exists and if the block size is 128M.
6902             assert_eq!(
6903                 guest
6904                     .ssh_command("lsblk | grep vdd | grep -c 128M")
6905                     .unwrap()
6906                     .trim()
6907                     .parse::<u32>()
6908                     .unwrap_or_default(),
6909                 1
6910             );
6911 
6912             // Write some content to the block device we've just plugged.
6913             guest
6914                 .ssh_command("sudo bash -c 'echo foobar > /dev/vdd'")
6915                 .unwrap();
6916 
6917             // Check we can read the content back.
6918             assert_eq!(
6919                 guest.ssh_command("sudo head -1 /dev/vdd").unwrap().trim(),
6920                 "foobar"
6921             );
6922 
6923             // Unplug the device
6924             let cmd_success = remote_command(&api_socket, "remove-device", Some("myvdpa0"));
6925             assert!(cmd_success);
6926             thread::sleep(std::time::Duration::new(10, 0));
6927 
6928             // Check /dev/vdd doesn't exist anymore
6929             assert_eq!(
6930                 guest
6931                     .ssh_command("lsblk | grep -c vdd || true")
6932                     .unwrap()
6933                     .trim()
6934                     .parse::<u32>()
6935                     .unwrap_or(1),
6936                 0
6937             );
6938         });
6939 
6940         kill_child(&mut child);
6941         let output = child.wait_with_output().unwrap();
6942 
6943         handle_child_output(r, &output);
6944     }
6945 
6946     #[test]
6947     #[cfg(target_arch = "x86_64")]
6948     #[ignore = "See #5756"]
6949     fn test_vdpa_net() {
6950         // Before trying to run the test, verify the vdpa_sim_net module is correctly loaded.
6951         if !exec_host_command_status("lsmod | grep vdpa_sim_net").success() {
6952             return;
6953         }
6954 
6955         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
6956         let guest = Guest::new(Box::new(focal));
6957 
6958         let kernel_path = direct_kernel_boot_path();
6959 
6960         let mut child = GuestCommand::new(&guest)
6961             .args(["--cpus", "boot=2"])
6962             .args(["--memory", "size=512M,hugepages=on"])
6963             .args(["--kernel", kernel_path.to_str().unwrap()])
6964             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
6965             .default_disks()
6966             .default_net()
6967             .args(["--vdpa", "path=/dev/vhost-vdpa-2,num_queues=2"])
6968             .capture_output()
6969             .spawn()
6970             .unwrap();
6971 
6972         let r = std::panic::catch_unwind(|| {
6973             guest.wait_vm_boot(None).unwrap();
6974 
6975             // Check we can find network interface related to vDPA device
6976             assert_eq!(
6977                 guest
6978                     .ssh_command("ip -o link | grep -c ens6")
6979                     .unwrap()
6980                     .trim()
6981                     .parse::<u32>()
6982                     .unwrap_or(0),
6983                 1
6984             );
6985 
6986             guest
6987                 .ssh_command("sudo ip addr add 172.16.1.2/24 dev ens6")
6988                 .unwrap();
6989             guest.ssh_command("sudo ip link set up dev ens6").unwrap();
6990 
6991             // Check there is no packet yet on both TX/RX of the network interface
6992             assert_eq!(
6993                 guest
6994                     .ssh_command("ip -j -p -s link show ens6 | grep -c '\"packets\": 0'")
6995                     .unwrap()
6996                     .trim()
6997                     .parse::<u32>()
6998                     .unwrap_or(0),
6999                 2
7000             );
7001 
7002             // Send 6 packets with ping command
7003             guest.ssh_command("ping 172.16.1.10 -c 6 || true").unwrap();
7004 
7005             // Check we can find 6 packets on both TX/RX of the network interface
7006             assert_eq!(
7007                 guest
7008                     .ssh_command("ip -j -p -s link show ens6 | grep -c '\"packets\": 6'")
7009                     .unwrap()
7010                     .trim()
7011                     .parse::<u32>()
7012                     .unwrap_or(0),
7013                 2
7014             );
7015 
7016             // No need to check for hotplug as we already tested it through
7017             // test_vdpa_block()
7018         });
7019 
7020         kill_child(&mut child);
7021         let output = child.wait_with_output().unwrap();
7022 
7023         handle_child_output(r, &output);
7024     }
7025 
7026     #[test]
7027     #[cfg(target_arch = "x86_64")]
7028     fn test_tpm() {
7029         let focal = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string());
7030         let guest = Guest::new(Box::new(focal));
7031 
7032         let (mut swtpm_command, swtpm_socket_path) = prepare_swtpm_daemon(&guest.tmp_dir);
7033 
7034         let mut guest_cmd = GuestCommand::new(&guest);
7035         guest_cmd
7036             .args(["--cpus", "boot=1"])
7037             .args(["--memory", "size=1G"])
7038             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
7039             .args(["--tpm", &format!("socket={swtpm_socket_path}")])
7040             .capture_output()
7041             .default_disks()
7042             .default_net();
7043 
7044         // Start swtpm daemon
7045         let mut swtpm_child = swtpm_command.spawn().unwrap();
7046         thread::sleep(std::time::Duration::new(10, 0));
7047         let mut child = guest_cmd.spawn().unwrap();
7048         let r = std::panic::catch_unwind(|| {
7049             guest.wait_vm_boot(None).unwrap();
7050             assert_eq!(
7051                 guest.ssh_command("ls /dev/tpm0").unwrap().trim(),
7052                 "/dev/tpm0"
7053             );
7054             guest.ssh_command("sudo tpm2_selftest -f").unwrap();
7055             guest
7056                 .ssh_command("echo 'hello' > /tmp/checksum_test;  ")
7057                 .unwrap();
7058             guest.ssh_command("cmp <(sudo tpm2_pcrevent  /tmp/checksum_test | grep sha256 | awk '{print $2}') <(sha256sum /tmp/checksum_test| awk '{print $1}')").unwrap();
7059         });
7060 
7061         let _ = swtpm_child.kill();
7062         let _d_out = swtpm_child.wait_with_output().unwrap();
7063 
7064         kill_child(&mut child);
7065         let output = child.wait_with_output().unwrap();
7066 
7067         handle_child_output(r, &output);
7068     }
7069 
7070     #[test]
7071     #[cfg(target_arch = "x86_64")]
7072     fn test_double_tty() {
7073         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7074         let guest = Guest::new(Box::new(focal));
7075         let mut cmd = GuestCommand::new(&guest);
7076         let api_socket = temp_api_path(&guest.tmp_dir);
7077         let tty_str: &str = "console=hvc0 earlyprintk=ttyS0 ";
7078         // linux printk module enable console log.
7079         let con_dis_str: &str = "console [hvc0] enabled";
7080         // linux printk module disable console log.
7081         let con_enb_str: &str = "bootconsole [earlyser0] disabled";
7082 
7083         let kernel_path = direct_kernel_boot_path();
7084 
7085         cmd.args(["--cpus", "boot=1"])
7086             .args(["--memory", "size=512M"])
7087             .args(["--kernel", kernel_path.to_str().unwrap()])
7088             .args([
7089                 "--cmdline",
7090                 DIRECT_KERNEL_BOOT_CMDLINE
7091                     .replace("console=hvc0 ", tty_str)
7092                     .as_str(),
7093             ])
7094             .capture_output()
7095             .default_disks()
7096             .default_net()
7097             .args(["--serial", "tty"])
7098             .args(["--console", "tty"])
7099             .args(["--api-socket", &api_socket]);
7100 
7101         let mut child = cmd.spawn().unwrap();
7102 
7103         let mut r = std::panic::catch_unwind(|| {
7104             guest.wait_vm_boot(None).unwrap();
7105         });
7106 
7107         kill_child(&mut child);
7108         let output = child.wait_with_output().unwrap();
7109 
7110         if r.is_ok() {
7111             r = std::panic::catch_unwind(|| {
7112                 let s = String::from_utf8_lossy(&output.stdout);
7113                 assert!(s.contains(tty_str));
7114                 assert!(s.contains(con_dis_str));
7115                 assert!(s.contains(con_enb_str));
7116             });
7117         }
7118 
7119         handle_child_output(r, &output);
7120     }
7121 
7122     #[test]
7123     #[cfg(target_arch = "x86_64")]
7124     fn test_nmi() {
7125         let jammy = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string());
7126         let guest = Guest::new(Box::new(jammy));
7127         let api_socket = temp_api_path(&guest.tmp_dir);
7128         let event_path = temp_event_monitor_path(&guest.tmp_dir);
7129 
7130         let kernel_path = direct_kernel_boot_path();
7131         let cmd_line = format!("{} {}", DIRECT_KERNEL_BOOT_CMDLINE, "unknown_nmi_panic=1");
7132 
7133         let mut cmd = GuestCommand::new(&guest);
7134         cmd.args(["--cpus", "boot=4"])
7135             .args(["--memory", "size=512M"])
7136             .args(["--kernel", kernel_path.to_str().unwrap()])
7137             .args(["--cmdline", cmd_line.as_str()])
7138             .default_disks()
7139             .args(["--net", guest.default_net_string().as_str()])
7140             .args(["--pvpanic"])
7141             .args(["--api-socket", &api_socket])
7142             .args(["--event-monitor", format!("path={event_path}").as_str()])
7143             .capture_output();
7144 
7145         let mut child = cmd.spawn().unwrap();
7146 
7147         let r = std::panic::catch_unwind(|| {
7148             guest.wait_vm_boot(None).unwrap();
7149 
7150             assert!(remote_command(&api_socket, "nmi", None));
7151 
7152             // Wait a while for guest
7153             thread::sleep(std::time::Duration::new(3, 0));
7154 
7155             let expected_sequential_events = [&MetaEvent {
7156                 event: "panic".to_string(),
7157                 device_id: None,
7158             }];
7159             assert!(check_latest_events_exact(
7160                 &expected_sequential_events,
7161                 &event_path
7162             ));
7163         });
7164 
7165         kill_child(&mut child);
7166         let output = child.wait_with_output().unwrap();
7167 
7168         handle_child_output(r, &output);
7169     }
7170 }
7171 
7172 mod dbus_api {
7173     use crate::*;
7174 
7175     // Start cloud-hypervisor with no VM parameters, running both the HTTP
7176     // and DBus APIs. Alternate calls to the external APIs (HTTP and DBus)
7177     // to create a VM, boot it, and verify that it can be shut down and then
7178     // booted again.
7179     #[test]
7180     fn test_api_dbus_and_http_interleaved() {
7181         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7182         let guest = Guest::new(Box::new(focal));
7183         let dbus_api = TargetApi::new_dbus_api(&guest.tmp_dir);
7184         let http_api = TargetApi::new_http_api(&guest.tmp_dir);
7185 
7186         let mut child = GuestCommand::new(&guest)
7187             .args(dbus_api.guest_args())
7188             .args(http_api.guest_args())
7189             .capture_output()
7190             .spawn()
7191             .unwrap();
7192 
7193         thread::sleep(std::time::Duration::new(1, 0));
7194 
7195         // Verify API servers are running
7196         assert!(dbus_api.remote_command("ping", None));
7197         assert!(http_api.remote_command("ping", None));
7198 
7199         // Create the VM first
7200         let cpu_count: u8 = 4;
7201         let request_body = guest.api_create_body(
7202             cpu_count,
7203             direct_kernel_boot_path().to_str().unwrap(),
7204             DIRECT_KERNEL_BOOT_CMDLINE,
7205         );
7206 
7207         let temp_config_path = guest.tmp_dir.as_path().join("config");
7208         std::fs::write(&temp_config_path, request_body).unwrap();
7209         let create_config = temp_config_path.as_os_str().to_str().unwrap();
7210 
7211         let r = std::panic::catch_unwind(|| {
7212             // Create the VM
7213             assert!(dbus_api.remote_command("create", Some(create_config),));
7214 
7215             // Then boot it
7216             assert!(http_api.remote_command("boot", None));
7217             guest.wait_vm_boot(None).unwrap();
7218 
7219             // Check that the VM booted as expected
7220             assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
7221             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
7222 
7223             // Sync and shutdown without powering off to prevent filesystem
7224             // corruption.
7225             guest.ssh_command("sync").unwrap();
7226             guest.ssh_command("sudo shutdown -H now").unwrap();
7227 
7228             // Wait for the guest to be fully shutdown
7229             thread::sleep(std::time::Duration::new(20, 0));
7230 
7231             // Then shutdown the VM
7232             assert!(dbus_api.remote_command("shutdown", None));
7233 
7234             // Then boot it again
7235             assert!(http_api.remote_command("boot", None));
7236             guest.wait_vm_boot(None).unwrap();
7237 
7238             // Check that the VM booted as expected
7239             assert_eq!(guest.get_cpu_count().unwrap_or_default() as u8, cpu_count);
7240             assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
7241         });
7242 
7243         kill_child(&mut child);
7244         let output = child.wait_with_output().unwrap();
7245 
7246         handle_child_output(r, &output);
7247     }
7248 
7249     #[test]
7250     fn test_api_dbus_create_boot() {
7251         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7252         let guest = Guest::new(Box::new(focal));
7253 
7254         _test_api_create_boot(TargetApi::new_dbus_api(&guest.tmp_dir), guest)
7255     }
7256 
7257     #[test]
7258     fn test_api_dbus_shutdown() {
7259         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7260         let guest = Guest::new(Box::new(focal));
7261 
7262         _test_api_shutdown(TargetApi::new_dbus_api(&guest.tmp_dir), guest)
7263     }
7264 
7265     #[test]
7266     fn test_api_dbus_delete() {
7267         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7268         let guest = Guest::new(Box::new(focal));
7269 
7270         _test_api_delete(TargetApi::new_dbus_api(&guest.tmp_dir), guest);
7271     }
7272 
7273     #[test]
7274     fn test_api_dbus_pause_resume() {
7275         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7276         let guest = Guest::new(Box::new(focal));
7277 
7278         _test_api_pause_resume(TargetApi::new_dbus_api(&guest.tmp_dir), guest)
7279     }
7280 }
7281 
7282 mod common_sequential {
7283     use std::fs::remove_dir_all;
7284 
7285     use crate::*;
7286 
7287     #[test]
7288     #[cfg(not(feature = "mshv"))]
7289     fn test_memory_mergeable_on() {
7290         test_memory_mergeable(true)
7291     }
7292 
7293     fn snapshot_and_check_events(api_socket: &str, snapshot_dir: &str, event_path: &str) {
7294         // Pause the VM
7295         assert!(remote_command(api_socket, "pause", None));
7296         let latest_events: [&MetaEvent; 2] = [
7297             &MetaEvent {
7298                 event: "pausing".to_string(),
7299                 device_id: None,
7300             },
7301             &MetaEvent {
7302                 event: "paused".to_string(),
7303                 device_id: None,
7304             },
7305         ];
7306         // See: #5938
7307         thread::sleep(std::time::Duration::new(1, 0));
7308         assert!(check_latest_events_exact(&latest_events, event_path));
7309 
7310         // Take a snapshot from the VM
7311         assert!(remote_command(
7312             api_socket,
7313             "snapshot",
7314             Some(format!("file://{snapshot_dir}").as_str()),
7315         ));
7316 
7317         // Wait to make sure the snapshot is completed
7318         thread::sleep(std::time::Duration::new(10, 0));
7319 
7320         let latest_events = [
7321             &MetaEvent {
7322                 event: "snapshotting".to_string(),
7323                 device_id: None,
7324             },
7325             &MetaEvent {
7326                 event: "snapshotted".to_string(),
7327                 device_id: None,
7328             },
7329         ];
7330         // See: #5938
7331         thread::sleep(std::time::Duration::new(1, 0));
7332         assert!(check_latest_events_exact(&latest_events, event_path));
7333     }
7334 
7335     // One thing to note about this test. The virtio-net device is heavily used
7336     // through each ssh command. There's no need to perform a dedicated test to
7337     // verify the migration went well for virtio-net.
7338     #[test]
7339     #[cfg(not(feature = "mshv"))]
7340     fn test_snapshot_restore_hotplug_virtiomem() {
7341         _test_snapshot_restore(true);
7342     }
7343 
7344     #[test]
7345     fn test_snapshot_restore_basic() {
7346         _test_snapshot_restore(false);
7347     }
7348 
7349     fn _test_snapshot_restore(use_hotplug: bool) {
7350         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7351         let guest = Guest::new(Box::new(focal));
7352         let kernel_path = direct_kernel_boot_path();
7353 
7354         let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir));
7355 
7356         let net_id = "net123";
7357         let net_params = format!(
7358             "id={},tap=,mac={},ip={},mask=255.255.255.0",
7359             net_id, guest.network.guest_mac, guest.network.host_ip
7360         );
7361         let mut mem_params = "size=2G";
7362 
7363         if use_hotplug {
7364             mem_params = "size=2G,hotplug_method=virtio-mem,hotplug_size=32G"
7365         }
7366 
7367         let cloudinit_params = format!(
7368             "path={},iommu=on",
7369             guest.disk_config.disk(DiskType::CloudInit).unwrap()
7370         );
7371 
7372         let socket = temp_vsock_path(&guest.tmp_dir);
7373         let event_path = temp_event_monitor_path(&guest.tmp_dir);
7374 
7375         let mut child = GuestCommand::new(&guest)
7376             .args(["--api-socket", &api_socket_source])
7377             .args(["--event-monitor", format!("path={event_path}").as_str()])
7378             .args(["--cpus", "boot=4"])
7379             .args(["--memory", mem_params])
7380             .args(["--balloon", "size=0"])
7381             .args(["--kernel", kernel_path.to_str().unwrap()])
7382             .args([
7383                 "--disk",
7384                 format!(
7385                     "path={}",
7386                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
7387                 )
7388                 .as_str(),
7389                 cloudinit_params.as_str(),
7390             ])
7391             .args(["--net", net_params.as_str()])
7392             .args(["--vsock", format!("cid=3,socket={socket}").as_str()])
7393             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
7394             .capture_output()
7395             .spawn()
7396             .unwrap();
7397 
7398         let console_text = String::from("On a branch floating down river a cricket, singing.");
7399         // Create the snapshot directory
7400         let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir);
7401 
7402         let r = std::panic::catch_unwind(|| {
7403             guest.wait_vm_boot(None).unwrap();
7404 
7405             // Check the number of vCPUs
7406             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4);
7407             // Check the guest RAM
7408             assert!(guest.get_total_memory().unwrap_or_default() > 1_920_000);
7409             if use_hotplug {
7410                 // Increase guest RAM with virtio-mem
7411                 resize_command(
7412                     &api_socket_source,
7413                     None,
7414                     Some(6 << 30),
7415                     None,
7416                     Some(&event_path),
7417                 );
7418                 thread::sleep(std::time::Duration::new(5, 0));
7419                 assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000);
7420                 // Use balloon to remove RAM from the VM
7421                 resize_command(
7422                     &api_socket_source,
7423                     None,
7424                     None,
7425                     Some(1 << 30),
7426                     Some(&event_path),
7427                 );
7428                 thread::sleep(std::time::Duration::new(5, 0));
7429                 let total_memory = guest.get_total_memory().unwrap_or_default();
7430                 assert!(total_memory > 4_800_000);
7431                 assert!(total_memory < 5_760_000);
7432             }
7433             // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net
7434             guest.check_devices_common(Some(&socket), Some(&console_text), None);
7435 
7436             // x86_64: We check that removing and adding back the virtio-net device
7437             // does not break the snapshot/restore support for virtio-pci.
7438             // This is an important thing to test as the hotplug will
7439             // trigger a PCI BAR reprogramming, which is a good way of
7440             // checking if the stored resources are correctly restored.
7441             // Unplug the virtio-net device
7442             // AArch64: Device hotplug is currently not supported, skipping here.
7443             #[cfg(target_arch = "x86_64")]
7444             {
7445                 assert!(remote_command(
7446                     &api_socket_source,
7447                     "remove-device",
7448                     Some(net_id),
7449                 ));
7450                 thread::sleep(std::time::Duration::new(10, 0));
7451                 let latest_events = [&MetaEvent {
7452                     event: "device-removed".to_string(),
7453                     device_id: Some(net_id.to_string()),
7454                 }];
7455                 // See: #5938
7456                 thread::sleep(std::time::Duration::new(1, 0));
7457                 assert!(check_latest_events_exact(&latest_events, &event_path));
7458 
7459                 // Plug the virtio-net device again
7460                 assert!(remote_command(
7461                     &api_socket_source,
7462                     "add-net",
7463                     Some(net_params.as_str()),
7464                 ));
7465                 thread::sleep(std::time::Duration::new(10, 0));
7466             }
7467 
7468             snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path);
7469         });
7470 
7471         // Shutdown the source VM and check console output
7472         kill_child(&mut child);
7473         let output = child.wait_with_output().unwrap();
7474         handle_child_output(r, &output);
7475 
7476         let r = std::panic::catch_unwind(|| {
7477             assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text));
7478         });
7479 
7480         handle_child_output(r, &output);
7481 
7482         // Remove the vsock socket file.
7483         Command::new("rm")
7484             .arg("-f")
7485             .arg(socket.as_str())
7486             .output()
7487             .unwrap();
7488 
7489         let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir));
7490         let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir));
7491 
7492         // Restore the VM from the snapshot
7493         let mut child = GuestCommand::new(&guest)
7494             .args(["--api-socket", &api_socket_restored])
7495             .args([
7496                 "--event-monitor",
7497                 format!("path={event_path_restored}").as_str(),
7498             ])
7499             .args([
7500                 "--restore",
7501                 format!("source_url=file://{snapshot_dir}").as_str(),
7502             ])
7503             .capture_output()
7504             .spawn()
7505             .unwrap();
7506 
7507         // Wait for the VM to be restored
7508         thread::sleep(std::time::Duration::new(20, 0));
7509         let expected_events = [
7510             &MetaEvent {
7511                 event: "starting".to_string(),
7512                 device_id: None,
7513             },
7514             &MetaEvent {
7515                 event: "activated".to_string(),
7516                 device_id: Some("__console".to_string()),
7517             },
7518             &MetaEvent {
7519                 event: "activated".to_string(),
7520                 device_id: Some("__rng".to_string()),
7521             },
7522             &MetaEvent {
7523                 event: "restoring".to_string(),
7524                 device_id: None,
7525             },
7526         ];
7527         assert!(check_sequential_events(
7528             &expected_events,
7529             &event_path_restored
7530         ));
7531         let latest_events = [&MetaEvent {
7532             event: "restored".to_string(),
7533             device_id: None,
7534         }];
7535         assert!(check_latest_events_exact(
7536             &latest_events,
7537             &event_path_restored
7538         ));
7539 
7540         // Remove the snapshot dir
7541         let _ = remove_dir_all(snapshot_dir.as_str());
7542 
7543         let r = std::panic::catch_unwind(|| {
7544             // Resume the VM
7545             assert!(remote_command(&api_socket_restored, "resume", None));
7546             // There is no way that we can ensure the 'write()' to the
7547             // event file is completed when the 'resume' request is
7548             // returned successfully, because the 'write()' was done
7549             // asynchronously from a different thread of Cloud
7550             // Hypervisor (e.g. the event-monitor thread).
7551             thread::sleep(std::time::Duration::new(1, 0));
7552             let latest_events = [
7553                 &MetaEvent {
7554                     event: "resuming".to_string(),
7555                     device_id: None,
7556                 },
7557                 &MetaEvent {
7558                     event: "resumed".to_string(),
7559                     device_id: None,
7560                 },
7561             ];
7562             assert!(check_latest_events_exact(
7563                 &latest_events,
7564                 &event_path_restored
7565             ));
7566 
7567             // Perform same checks to validate VM has been properly restored
7568             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 4);
7569             let total_memory = guest.get_total_memory().unwrap_or_default();
7570             if !use_hotplug {
7571                 assert!(total_memory > 1_920_000);
7572             } else {
7573                 assert!(total_memory > 4_800_000);
7574                 assert!(total_memory < 5_760_000);
7575                 // Deflate balloon to restore entire RAM to the VM
7576                 resize_command(&api_socket_restored, None, None, Some(0), None);
7577                 thread::sleep(std::time::Duration::new(5, 0));
7578                 assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000);
7579                 // Decrease guest RAM with virtio-mem
7580                 resize_command(&api_socket_restored, None, Some(5 << 30), None, None);
7581                 thread::sleep(std::time::Duration::new(5, 0));
7582                 let total_memory = guest.get_total_memory().unwrap_or_default();
7583                 assert!(total_memory > 4_800_000);
7584                 assert!(total_memory < 5_760_000);
7585             }
7586 
7587             guest.check_devices_common(Some(&socket), Some(&console_text), None);
7588         });
7589         // Shutdown the target VM and check console output
7590         kill_child(&mut child);
7591         let output = child.wait_with_output().unwrap();
7592         handle_child_output(r, &output);
7593 
7594         let r = std::panic::catch_unwind(|| {
7595             assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text));
7596         });
7597 
7598         handle_child_output(r, &output);
7599     }
7600 
7601     #[test]
7602     #[cfg_attr(target_arch = "aarch64", ignore = "See #6970")]
7603     fn test_snapshot_restore_with_fd() {
7604         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7605         let guest = Guest::new(Box::new(focal));
7606         let kernel_path = direct_kernel_boot_path();
7607 
7608         let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir));
7609 
7610         let net_id = "net123";
7611         let num_queue_pairs: usize = 2;
7612         // use a name that does not conflict with tap dev created from other tests
7613         let tap_name = "chtap999";
7614         use std::str::FromStr;
7615         let taps = net_util::open_tap(
7616             Some(tap_name),
7617             Some(std::net::IpAddr::V4(
7618                 std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap(),
7619             )),
7620             None,
7621             &mut None,
7622             None,
7623             num_queue_pairs,
7624             Some(libc::O_RDWR | libc::O_NONBLOCK),
7625         )
7626         .unwrap();
7627         let net_params = format!(
7628             "id={},fd=[{},{}],mac={},ip={},mask=255.255.255.0,num_queues={}",
7629             net_id,
7630             taps[0].as_raw_fd(),
7631             taps[1].as_raw_fd(),
7632             guest.network.guest_mac,
7633             guest.network.host_ip,
7634             num_queue_pairs * 2
7635         );
7636 
7637         let cloudinit_params = format!(
7638             "path={},iommu=on",
7639             guest.disk_config.disk(DiskType::CloudInit).unwrap()
7640         );
7641 
7642         let n_cpu = 2;
7643         let event_path = temp_event_monitor_path(&guest.tmp_dir);
7644 
7645         let mut child = GuestCommand::new(&guest)
7646             .args(["--api-socket", &api_socket_source])
7647             .args(["--event-monitor", format!("path={event_path}").as_str()])
7648             .args(["--cpus", format!("boot={n_cpu}").as_str()])
7649             .args(["--memory", "size=1G"])
7650             .args(["--kernel", kernel_path.to_str().unwrap()])
7651             .args([
7652                 "--disk",
7653                 format!(
7654                     "path={}",
7655                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
7656                 )
7657                 .as_str(),
7658                 cloudinit_params.as_str(),
7659             ])
7660             .args(["--net", net_params.as_str()])
7661             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
7662             .capture_output()
7663             .spawn()
7664             .unwrap();
7665 
7666         let console_text = String::from("On a branch floating down river a cricket, singing.");
7667         // Create the snapshot directory
7668         let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir);
7669 
7670         let r = std::panic::catch_unwind(|| {
7671             guest.wait_vm_boot(None).unwrap();
7672 
7673             // close the fds after VM boots, as CH duplicates them before using
7674             for tap in taps.iter() {
7675                 unsafe { libc::close(tap.as_raw_fd()) };
7676             }
7677 
7678             // Check the number of vCPUs
7679             assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu);
7680             // Check the guest RAM
7681             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
7682 
7683             // Check the guest virtio-devices, e.g. block, rng, vsock, console, and net
7684             guest.check_devices_common(None, Some(&console_text), None);
7685 
7686             snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path);
7687         });
7688 
7689         // Shutdown the source VM and check console output
7690         kill_child(&mut child);
7691         let output = child.wait_with_output().unwrap();
7692         handle_child_output(r, &output);
7693 
7694         let r = std::panic::catch_unwind(|| {
7695             assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text));
7696         });
7697 
7698         handle_child_output(r, &output);
7699 
7700         let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir));
7701         let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir));
7702 
7703         // Restore the VM from the snapshot
7704         let mut child = GuestCommand::new(&guest)
7705             .args(["--api-socket", &api_socket_restored])
7706             .args([
7707                 "--event-monitor",
7708                 format!("path={event_path_restored}").as_str(),
7709             ])
7710             .capture_output()
7711             .spawn()
7712             .unwrap();
7713         thread::sleep(std::time::Duration::new(2, 0));
7714 
7715         let taps = net_util::open_tap(
7716             Some(tap_name),
7717             Some(std::net::IpAddr::V4(
7718                 std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap(),
7719             )),
7720             None,
7721             &mut None,
7722             None,
7723             num_queue_pairs,
7724             Some(libc::O_RDWR | libc::O_NONBLOCK),
7725         )
7726         .unwrap();
7727         let restore_params = format!(
7728             "source_url=file://{},net_fds=[{}@[{},{}]]",
7729             snapshot_dir,
7730             net_id,
7731             taps[0].as_raw_fd(),
7732             taps[1].as_raw_fd()
7733         );
7734         assert!(remote_command(
7735             &api_socket_restored,
7736             "restore",
7737             Some(restore_params.as_str())
7738         ));
7739 
7740         // Wait for the VM to be restored
7741         thread::sleep(std::time::Duration::new(20, 0));
7742 
7743         // close the fds as CH duplicates them before using
7744         for tap in taps.iter() {
7745             unsafe { libc::close(tap.as_raw_fd()) };
7746         }
7747 
7748         let expected_events = [
7749             &MetaEvent {
7750                 event: "starting".to_string(),
7751                 device_id: None,
7752             },
7753             &MetaEvent {
7754                 event: "activated".to_string(),
7755                 device_id: Some("__console".to_string()),
7756             },
7757             &MetaEvent {
7758                 event: "activated".to_string(),
7759                 device_id: Some("__rng".to_string()),
7760             },
7761             &MetaEvent {
7762                 event: "restoring".to_string(),
7763                 device_id: None,
7764             },
7765         ];
7766         assert!(check_sequential_events(
7767             &expected_events,
7768             &event_path_restored
7769         ));
7770         let latest_events = [&MetaEvent {
7771             event: "restored".to_string(),
7772             device_id: None,
7773         }];
7774         assert!(check_latest_events_exact(
7775             &latest_events,
7776             &event_path_restored
7777         ));
7778 
7779         // Remove the snapshot dir
7780         let _ = remove_dir_all(snapshot_dir.as_str());
7781 
7782         let r = std::panic::catch_unwind(|| {
7783             // Resume the VM
7784             assert!(remote_command(&api_socket_restored, "resume", None));
7785             // There is no way that we can ensure the 'write()' to the
7786             // event file is completed when the 'resume' request is
7787             // returned successfully, because the 'write()' was done
7788             // asynchronously from a different thread of Cloud
7789             // Hypervisor (e.g. the event-monitor thread).
7790             thread::sleep(std::time::Duration::new(1, 0));
7791             let latest_events = [
7792                 &MetaEvent {
7793                     event: "resuming".to_string(),
7794                     device_id: None,
7795                 },
7796                 &MetaEvent {
7797                     event: "resumed".to_string(),
7798                     device_id: None,
7799                 },
7800             ];
7801             assert!(check_latest_events_exact(
7802                 &latest_events,
7803                 &event_path_restored
7804             ));
7805 
7806             // Perform same checks to validate VM has been properly restored
7807             assert_eq!(guest.get_cpu_count().unwrap_or_default(), n_cpu);
7808             assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
7809 
7810             guest.check_devices_common(None, Some(&console_text), None);
7811         });
7812         // Shutdown the target VM and check console output
7813         kill_child(&mut child);
7814         let output = child.wait_with_output().unwrap();
7815         handle_child_output(r, &output);
7816 
7817         let r = std::panic::catch_unwind(|| {
7818             assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text));
7819         });
7820 
7821         handle_child_output(r, &output);
7822     }
7823 
7824     #[test]
7825     #[cfg(target_arch = "x86_64")]
7826     fn test_snapshot_restore_pvpanic() {
7827         _test_snapshot_restore_devices(true);
7828     }
7829 
7830     fn _test_snapshot_restore_devices(pvpanic: bool) {
7831         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
7832         let guest = Guest::new(Box::new(focal));
7833         let kernel_path = direct_kernel_boot_path();
7834 
7835         let api_socket_source = format!("{}.1", temp_api_path(&guest.tmp_dir));
7836 
7837         let device_params = {
7838             let mut data = vec![];
7839             if pvpanic {
7840                 data.push("--pvpanic");
7841             }
7842             data
7843         };
7844 
7845         let socket = temp_vsock_path(&guest.tmp_dir);
7846         let event_path = temp_event_monitor_path(&guest.tmp_dir);
7847 
7848         let mut child = GuestCommand::new(&guest)
7849             .args(["--api-socket", &api_socket_source])
7850             .args(["--event-monitor", format!("path={event_path}").as_str()])
7851             .args(["--cpus", "boot=2"])
7852             .args(["--memory", "size=1G"])
7853             .args(["--kernel", kernel_path.to_str().unwrap()])
7854             .default_disks()
7855             .default_net()
7856             .args(["--vsock", format!("cid=3,socket={socket}").as_str()])
7857             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
7858             .args(device_params)
7859             .capture_output()
7860             .spawn()
7861             .unwrap();
7862 
7863         let console_text = String::from("On a branch floating down river a cricket, singing.");
7864         // Create the snapshot directory
7865         let snapshot_dir = temp_snapshot_dir_path(&guest.tmp_dir);
7866 
7867         let r = std::panic::catch_unwind(|| {
7868             guest.wait_vm_boot(None).unwrap();
7869 
7870             // Check the number of vCPUs
7871             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2);
7872 
7873             snapshot_and_check_events(&api_socket_source, &snapshot_dir, &event_path);
7874         });
7875 
7876         // Shutdown the source VM and check console output
7877         kill_child(&mut child);
7878         let output = child.wait_with_output().unwrap();
7879         handle_child_output(r, &output);
7880 
7881         // Remove the vsock socket file.
7882         Command::new("rm")
7883             .arg("-f")
7884             .arg(socket.as_str())
7885             .output()
7886             .unwrap();
7887 
7888         let api_socket_restored = format!("{}.2", temp_api_path(&guest.tmp_dir));
7889         let event_path_restored = format!("{}.2", temp_event_monitor_path(&guest.tmp_dir));
7890 
7891         // Restore the VM from the snapshot
7892         let mut child = GuestCommand::new(&guest)
7893             .args(["--api-socket", &api_socket_restored])
7894             .args([
7895                 "--event-monitor",
7896                 format!("path={event_path_restored}").as_str(),
7897             ])
7898             .args([
7899                 "--restore",
7900                 format!("source_url=file://{snapshot_dir}").as_str(),
7901             ])
7902             .capture_output()
7903             .spawn()
7904             .unwrap();
7905 
7906         // Wait for the VM to be restored
7907         thread::sleep(std::time::Duration::new(20, 0));
7908 
7909         let latest_events = [&MetaEvent {
7910             event: "restored".to_string(),
7911             device_id: None,
7912         }];
7913         assert!(check_latest_events_exact(
7914             &latest_events,
7915             &event_path_restored
7916         ));
7917 
7918         // Remove the snapshot dir
7919         let _ = remove_dir_all(snapshot_dir.as_str());
7920 
7921         let r = std::panic::catch_unwind(|| {
7922             // Resume the VM
7923             assert!(remote_command(&api_socket_restored, "resume", None));
7924             // There is no way that we can ensure the 'write()' to the
7925             // event file is completed when the 'resume' request is
7926             // returned successfully, because the 'write()' was done
7927             // asynchronously from a different thread of Cloud
7928             // Hypervisor (e.g. the event-monitor thread).
7929             thread::sleep(std::time::Duration::new(1, 0));
7930             let latest_events = [
7931                 &MetaEvent {
7932                     event: "resuming".to_string(),
7933                     device_id: None,
7934                 },
7935                 &MetaEvent {
7936                     event: "resumed".to_string(),
7937                     device_id: None,
7938                 },
7939             ];
7940             assert!(check_latest_events_exact(
7941                 &latest_events,
7942                 &event_path_restored
7943             ));
7944 
7945             // Check the number of vCPUs
7946             assert_eq!(guest.get_cpu_count().unwrap_or_default(), 2);
7947             guest.check_devices_common(Some(&socket), Some(&console_text), None);
7948 
7949             if pvpanic {
7950                 // Trigger guest a panic
7951                 make_guest_panic(&guest);
7952                 // Wait a while for guest
7953                 thread::sleep(std::time::Duration::new(10, 0));
7954 
7955                 let expected_sequential_events = [&MetaEvent {
7956                     event: "panic".to_string(),
7957                     device_id: None,
7958                 }];
7959                 assert!(check_latest_events_exact(
7960                     &expected_sequential_events,
7961                     &event_path_restored
7962                 ));
7963             }
7964         });
7965         // Shutdown the target VM and check console output
7966         kill_child(&mut child);
7967         let output = child.wait_with_output().unwrap();
7968         handle_child_output(r, &output);
7969 
7970         let r = std::panic::catch_unwind(|| {
7971             assert!(String::from_utf8_lossy(&output.stdout).contains(&console_text));
7972         });
7973 
7974         handle_child_output(r, &output);
7975     }
7976 
7977     #[test]
7978     fn test_virtio_pmem_persist_writes() {
7979         test_virtio_pmem(false, false)
7980     }
7981 }
7982 
7983 mod windows {
7984     use once_cell::sync::Lazy;
7985 
7986     use crate::*;
7987 
7988     static NEXT_DISK_ID: Lazy<Mutex<u8>> = Lazy::new(|| Mutex::new(1));
7989 
7990     struct WindowsGuest {
7991         guest: Guest,
7992         auth: PasswordAuth,
7993     }
7994 
7995     trait FsType {
7996         const FS_FAT: u8;
7997         const FS_NTFS: u8;
7998     }
7999     impl FsType for WindowsGuest {
8000         const FS_FAT: u8 = 0;
8001         const FS_NTFS: u8 = 1;
8002     }
8003 
8004     impl WindowsGuest {
8005         fn new() -> Self {
8006             let disk = WindowsDiskConfig::new(WINDOWS_IMAGE_NAME.to_string());
8007             let guest = Guest::new(Box::new(disk));
8008             let auth = PasswordAuth {
8009                 username: String::from("administrator"),
8010                 password: String::from("Admin123"),
8011             };
8012 
8013             WindowsGuest { guest, auth }
8014         }
8015 
8016         fn guest(&self) -> &Guest {
8017             &self.guest
8018         }
8019 
8020         fn ssh_cmd(&self, cmd: &str) -> String {
8021             ssh_command_ip_with_auth(
8022                 cmd,
8023                 &self.auth,
8024                 &self.guest.network.guest_ip,
8025                 DEFAULT_SSH_RETRIES,
8026                 DEFAULT_SSH_TIMEOUT,
8027             )
8028             .unwrap()
8029         }
8030 
8031         fn cpu_count(&self) -> u8 {
8032             self.ssh_cmd("powershell -Command \"(Get-CimInstance win32_computersystem).NumberOfLogicalProcessors\"")
8033                 .trim()
8034                 .parse::<u8>()
8035                 .unwrap_or(0)
8036         }
8037 
8038         fn ram_size(&self) -> usize {
8039             self.ssh_cmd("powershell -Command \"(Get-CimInstance win32_computersystem).TotalPhysicalMemory\"")
8040                 .trim()
8041                 .parse::<usize>()
8042                 .unwrap_or(0)
8043         }
8044 
8045         fn netdev_count(&self) -> u8 {
8046             self.ssh_cmd("powershell -Command \"netsh int ipv4 show interfaces | Select-String ethernet | Measure-Object -Line | Format-Table -HideTableHeaders\"")
8047                 .trim()
8048                 .parse::<u8>()
8049                 .unwrap_or(0)
8050         }
8051 
8052         fn disk_count(&self) -> u8 {
8053             self.ssh_cmd("powershell -Command \"Get-Disk | Measure-Object -Line | Format-Table -HideTableHeaders\"")
8054                 .trim()
8055                 .parse::<u8>()
8056                 .unwrap_or(0)
8057         }
8058 
8059         fn reboot(&self) {
8060             let _ = self.ssh_cmd("shutdown /r /t 0");
8061         }
8062 
8063         fn shutdown(&self) {
8064             let _ = self.ssh_cmd("shutdown /s /t 0");
8065         }
8066 
8067         fn run_dnsmasq(&self) -> std::process::Child {
8068             let listen_address = format!("--listen-address={}", self.guest.network.host_ip);
8069             let dhcp_host = format!(
8070                 "--dhcp-host={},{}",
8071                 self.guest.network.guest_mac, self.guest.network.guest_ip
8072             );
8073             let dhcp_range = format!(
8074                 "--dhcp-range=eth,{},{}",
8075                 self.guest.network.guest_ip, self.guest.network.guest_ip
8076             );
8077 
8078             Command::new("dnsmasq")
8079                 .arg("--no-daemon")
8080                 .arg("--log-queries")
8081                 .arg(listen_address.as_str())
8082                 .arg("--except-interface=lo")
8083                 .arg("--bind-dynamic") // Allow listening to host_ip while the interface is not ready yet.
8084                 .arg("--conf-file=/dev/null")
8085                 .arg(dhcp_host.as_str())
8086                 .arg(dhcp_range.as_str())
8087                 .spawn()
8088                 .unwrap()
8089         }
8090 
8091         // TODO Cleanup image file explicitly after test, if there's some space issues.
8092         fn disk_new(&self, fs: u8, sz: usize) -> String {
8093             let mut guard = NEXT_DISK_ID.lock().unwrap();
8094             let id = *guard;
8095             *guard = id + 1;
8096 
8097             let img = PathBuf::from(format!("/tmp/test-hotplug-{id}.raw"));
8098             let _ = fs::remove_file(&img);
8099 
8100             // Create an image file
8101             let out = Command::new("qemu-img")
8102                 .args([
8103                     "create",
8104                     "-f",
8105                     "raw",
8106                     img.to_str().unwrap(),
8107                     format!("{sz}m").as_str(),
8108                 ])
8109                 .output()
8110                 .expect("qemu-img command failed")
8111                 .stdout;
8112             println!("{out:?}");
8113 
8114             // Associate image to a loop device
8115             let out = Command::new("losetup")
8116                 .args(["--show", "-f", img.to_str().unwrap()])
8117                 .output()
8118                 .expect("failed to create loop device")
8119                 .stdout;
8120             let _tmp = String::from_utf8_lossy(&out);
8121             let loop_dev = _tmp.trim();
8122             println!("{out:?}");
8123 
8124             // Create a partition table
8125             // echo 'type=7' | sudo sfdisk "${LOOP}"
8126             let mut child = Command::new("sfdisk")
8127                 .args([loop_dev])
8128                 .stdin(Stdio::piped())
8129                 .spawn()
8130                 .unwrap();
8131             let stdin = child.stdin.as_mut().expect("failed to open stdin");
8132             stdin
8133                 .write_all("type=7".as_bytes())
8134                 .expect("failed to write stdin");
8135             let out = child.wait_with_output().expect("sfdisk failed").stdout;
8136             println!("{out:?}");
8137 
8138             // Disengage the loop device
8139             let out = Command::new("losetup")
8140                 .args(["-d", loop_dev])
8141                 .output()
8142                 .expect("loop device not found")
8143                 .stdout;
8144             println!("{out:?}");
8145 
8146             // Re-associate loop device pointing to the partition only
8147             let out = Command::new("losetup")
8148                 .args([
8149                     "--show",
8150                     "--offset",
8151                     (512 * 2048).to_string().as_str(),
8152                     "-f",
8153                     img.to_str().unwrap(),
8154                 ])
8155                 .output()
8156                 .expect("failed to create loop device")
8157                 .stdout;
8158             let _tmp = String::from_utf8_lossy(&out);
8159             let loop_dev = _tmp.trim();
8160             println!("{out:?}");
8161 
8162             // Create filesystem.
8163             let fs_cmd = match fs {
8164                 WindowsGuest::FS_FAT => "mkfs.msdos",
8165                 WindowsGuest::FS_NTFS => "mkfs.ntfs",
8166                 _ => panic!("Unknown filesystem type '{fs}'"),
8167             };
8168             let out = Command::new(fs_cmd)
8169                 .args([&loop_dev])
8170                 .output()
8171                 .unwrap_or_else(|_| panic!("{fs_cmd} failed"))
8172                 .stdout;
8173             println!("{out:?}");
8174 
8175             // Disengage the loop device
8176             let out = Command::new("losetup")
8177                 .args(["-d", loop_dev])
8178                 .output()
8179                 .unwrap_or_else(|_| panic!("loop device '{loop_dev}' not found"))
8180                 .stdout;
8181             println!("{out:?}");
8182 
8183             img.to_str().unwrap().to_string()
8184         }
8185 
8186         fn disks_set_rw(&self) {
8187             let _ = self.ssh_cmd("powershell -Command \"Get-Disk | Where-Object IsOffline -eq $True | Set-Disk -IsReadOnly $False\"");
8188         }
8189 
8190         fn disks_online(&self) {
8191             let _ = self.ssh_cmd("powershell -Command \"Get-Disk | Where-Object IsOffline -eq $True | Set-Disk -IsOffline $False\"");
8192         }
8193 
8194         fn disk_file_put(&self, fname: &str, data: &str) {
8195             let _ = self.ssh_cmd(&format!(
8196                 "powershell -Command \"'{data}' | Set-Content -Path {fname}\""
8197             ));
8198         }
8199 
8200         fn disk_file_read(&self, fname: &str) -> String {
8201             self.ssh_cmd(&format!(
8202                 "powershell -Command \"Get-Content -Path {fname}\""
8203             ))
8204         }
8205 
8206         fn wait_for_boot(&self) -> bool {
8207             let cmd = "dir /b c:\\ | find \"Windows\"";
8208             let tmo_max = 180;
8209             // The timeout increase by n*1+n*2+n*3+..., therefore the initial
8210             // interval must be small.
8211             let tmo_int = 2;
8212             let out = ssh_command_ip_with_auth(
8213                 cmd,
8214                 &self.auth,
8215                 &self.guest.network.guest_ip,
8216                 {
8217                     let mut ret = 1;
8218                     let mut tmo_acc = 0;
8219                     loop {
8220                         tmo_acc += tmo_int * ret;
8221                         if tmo_acc >= tmo_max {
8222                             break;
8223                         }
8224                         ret += 1;
8225                     }
8226                     ret
8227                 },
8228                 tmo_int,
8229             )
8230             .unwrap();
8231 
8232             if "Windows" == out.trim() {
8233                 return true;
8234             }
8235 
8236             false
8237         }
8238     }
8239 
8240     fn vcpu_threads_count(pid: u32) -> u8 {
8241         // ps -T -p 12345 | grep vcpu | wc -l
8242         let out = Command::new("ps")
8243             .args(["-T", "-p", format!("{pid}").as_str()])
8244             .output()
8245             .expect("ps command failed")
8246             .stdout;
8247         String::from_utf8_lossy(&out).matches("vcpu").count() as u8
8248     }
8249 
8250     fn netdev_ctrl_threads_count(pid: u32) -> u8 {
8251         // ps -T -p 12345 | grep "_net[0-9]*_ctrl" | wc -l
8252         let out = Command::new("ps")
8253             .args(["-T", "-p", format!("{pid}").as_str()])
8254             .output()
8255             .expect("ps command failed")
8256             .stdout;
8257         let mut n = 0;
8258         String::from_utf8_lossy(&out)
8259             .split_whitespace()
8260             .for_each(|s| n += (s.starts_with("_net") && s.ends_with("_ctrl")) as u8); // _net1_ctrl
8261         n
8262     }
8263 
8264     fn disk_ctrl_threads_count(pid: u32) -> u8 {
8265         // ps -T -p 15782  | grep "_disk[0-9]*_q0" | wc -l
8266         let out = Command::new("ps")
8267             .args(["-T", "-p", format!("{pid}").as_str()])
8268             .output()
8269             .expect("ps command failed")
8270             .stdout;
8271         let mut n = 0;
8272         String::from_utf8_lossy(&out)
8273             .split_whitespace()
8274             .for_each(|s| n += (s.starts_with("_disk") && s.ends_with("_q0")) as u8); // _disk0_q0, don't care about multiple queues as they're related to the same hdd
8275         n
8276     }
8277 
8278     #[test]
8279     fn test_windows_guest() {
8280         let windows_guest = WindowsGuest::new();
8281 
8282         let mut child = GuestCommand::new(windows_guest.guest())
8283             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8284             .args(["--memory", "size=4G"])
8285             .args(["--kernel", edk2_path().to_str().unwrap()])
8286             .args(["--serial", "tty"])
8287             .args(["--console", "off"])
8288             .default_disks()
8289             .default_net()
8290             .capture_output()
8291             .spawn()
8292             .unwrap();
8293 
8294         let fd = child.stdout.as_ref().unwrap().as_raw_fd();
8295         let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
8296         let fd = child.stderr.as_ref().unwrap().as_raw_fd();
8297         let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
8298 
8299         assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE);
8300 
8301         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8302 
8303         let r = std::panic::catch_unwind(|| {
8304             // Wait to make sure Windows boots up
8305             assert!(windows_guest.wait_for_boot());
8306 
8307             windows_guest.shutdown();
8308         });
8309 
8310         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8311         let _ = child.kill();
8312         let output = child.wait_with_output().unwrap();
8313 
8314         let _ = child_dnsmasq.kill();
8315         let _ = child_dnsmasq.wait();
8316 
8317         handle_child_output(r, &output);
8318     }
8319 
8320     #[test]
8321     fn test_windows_guest_multiple_queues() {
8322         let windows_guest = WindowsGuest::new();
8323 
8324         let mut ovmf_path = dirs::home_dir().unwrap();
8325         ovmf_path.push("workloads");
8326         ovmf_path.push(OVMF_NAME);
8327 
8328         let mut child = GuestCommand::new(windows_guest.guest())
8329             .args(["--cpus", "boot=4,kvm_hyperv=on"])
8330             .args(["--memory", "size=4G"])
8331             .args(["--kernel", ovmf_path.to_str().unwrap()])
8332             .args(["--serial", "tty"])
8333             .args(["--console", "off"])
8334             .args([
8335                 "--disk",
8336                 format!(
8337                     "path={},num_queues=4",
8338                     windows_guest
8339                         .guest()
8340                         .disk_config
8341                         .disk(DiskType::OperatingSystem)
8342                         .unwrap()
8343                 )
8344                 .as_str(),
8345             ])
8346             .args([
8347                 "--net",
8348                 format!(
8349                     "tap=,mac={},ip={},mask=255.255.255.0,num_queues=8",
8350                     windows_guest.guest().network.guest_mac,
8351                     windows_guest.guest().network.host_ip
8352                 )
8353                 .as_str(),
8354             ])
8355             .capture_output()
8356             .spawn()
8357             .unwrap();
8358 
8359         let fd = child.stdout.as_ref().unwrap().as_raw_fd();
8360         let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
8361         let fd = child.stderr.as_ref().unwrap().as_raw_fd();
8362         let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
8363 
8364         assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE);
8365 
8366         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8367 
8368         let r = std::panic::catch_unwind(|| {
8369             // Wait to make sure Windows boots up
8370             assert!(windows_guest.wait_for_boot());
8371 
8372             windows_guest.shutdown();
8373         });
8374 
8375         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8376         let _ = child.kill();
8377         let output = child.wait_with_output().unwrap();
8378 
8379         let _ = child_dnsmasq.kill();
8380         let _ = child_dnsmasq.wait();
8381 
8382         handle_child_output(r, &output);
8383     }
8384 
8385     #[test]
8386     #[cfg(not(feature = "mshv"))]
8387     #[ignore = "See #4327"]
8388     fn test_windows_guest_snapshot_restore() {
8389         let windows_guest = WindowsGuest::new();
8390 
8391         let mut ovmf_path = dirs::home_dir().unwrap();
8392         ovmf_path.push("workloads");
8393         ovmf_path.push(OVMF_NAME);
8394 
8395         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8396         let api_socket_source = format!("{}.1", temp_api_path(&tmp_dir));
8397 
8398         let mut child = GuestCommand::new(windows_guest.guest())
8399             .args(["--api-socket", &api_socket_source])
8400             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8401             .args(["--memory", "size=4G"])
8402             .args(["--kernel", ovmf_path.to_str().unwrap()])
8403             .args(["--serial", "tty"])
8404             .args(["--console", "off"])
8405             .default_disks()
8406             .default_net()
8407             .capture_output()
8408             .spawn()
8409             .unwrap();
8410 
8411         let fd = child.stdout.as_ref().unwrap().as_raw_fd();
8412         let pipesize = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
8413         let fd = child.stderr.as_ref().unwrap().as_raw_fd();
8414         let pipesize1 = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, PIPE_SIZE) };
8415 
8416         assert!(pipesize >= PIPE_SIZE && pipesize1 >= PIPE_SIZE);
8417 
8418         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8419 
8420         // Wait to make sure Windows boots up
8421         assert!(windows_guest.wait_for_boot());
8422 
8423         let snapshot_dir = temp_snapshot_dir_path(&tmp_dir);
8424 
8425         // Pause the VM
8426         assert!(remote_command(&api_socket_source, "pause", None));
8427 
8428         // Take a snapshot from the VM
8429         assert!(remote_command(
8430             &api_socket_source,
8431             "snapshot",
8432             Some(format!("file://{snapshot_dir}").as_str()),
8433         ));
8434 
8435         // Wait to make sure the snapshot is completed
8436         thread::sleep(std::time::Duration::new(30, 0));
8437 
8438         let _ = child.kill();
8439         child.wait().unwrap();
8440 
8441         let api_socket_restored = format!("{}.2", temp_api_path(&tmp_dir));
8442 
8443         // Restore the VM from the snapshot
8444         let mut child = GuestCommand::new(windows_guest.guest())
8445             .args(["--api-socket", &api_socket_restored])
8446             .args([
8447                 "--restore",
8448                 format!("source_url=file://{snapshot_dir}").as_str(),
8449             ])
8450             .capture_output()
8451             .spawn()
8452             .unwrap();
8453 
8454         // Wait for the VM to be restored
8455         thread::sleep(std::time::Duration::new(20, 0));
8456 
8457         let r = std::panic::catch_unwind(|| {
8458             // Resume the VM
8459             assert!(remote_command(&api_socket_restored, "resume", None));
8460 
8461             windows_guest.shutdown();
8462         });
8463 
8464         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8465         let _ = child.kill();
8466         let output = child.wait_with_output().unwrap();
8467 
8468         let _ = child_dnsmasq.kill();
8469         let _ = child_dnsmasq.wait();
8470 
8471         handle_child_output(r, &output);
8472     }
8473 
8474     #[test]
8475     #[cfg(not(feature = "mshv"))]
8476     #[cfg(not(target_arch = "aarch64"))]
8477     fn test_windows_guest_cpu_hotplug() {
8478         let windows_guest = WindowsGuest::new();
8479 
8480         let mut ovmf_path = dirs::home_dir().unwrap();
8481         ovmf_path.push("workloads");
8482         ovmf_path.push(OVMF_NAME);
8483 
8484         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8485         let api_socket = temp_api_path(&tmp_dir);
8486 
8487         let mut child = GuestCommand::new(windows_guest.guest())
8488             .args(["--api-socket", &api_socket])
8489             .args(["--cpus", "boot=2,max=8,kvm_hyperv=on"])
8490             .args(["--memory", "size=4G"])
8491             .args(["--kernel", ovmf_path.to_str().unwrap()])
8492             .args(["--serial", "tty"])
8493             .args(["--console", "off"])
8494             .default_disks()
8495             .default_net()
8496             .capture_output()
8497             .spawn()
8498             .unwrap();
8499 
8500         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8501 
8502         let r = std::panic::catch_unwind(|| {
8503             // Wait to make sure Windows boots up
8504             assert!(windows_guest.wait_for_boot());
8505 
8506             let vcpu_num = 2;
8507             // Check the initial number of CPUs the guest sees
8508             assert_eq!(windows_guest.cpu_count(), vcpu_num);
8509             // Check the initial number of vcpu threads in the CH process
8510             assert_eq!(vcpu_threads_count(child.id()), vcpu_num);
8511 
8512             let vcpu_num = 6;
8513             // Hotplug some CPUs
8514             resize_command(&api_socket, Some(vcpu_num), None, None, None);
8515             // Wait to make sure CPUs are added
8516             thread::sleep(std::time::Duration::new(10, 0));
8517             // Check the guest sees the correct number
8518             assert_eq!(windows_guest.cpu_count(), vcpu_num);
8519             // Check the CH process has the correct number of vcpu threads
8520             assert_eq!(vcpu_threads_count(child.id()), vcpu_num);
8521 
8522             let vcpu_num = 4;
8523             // Remove some CPUs. Note that Windows doesn't support hot-remove.
8524             resize_command(&api_socket, Some(vcpu_num), None, None, None);
8525             // Wait to make sure CPUs are removed
8526             thread::sleep(std::time::Duration::new(10, 0));
8527             // Reboot to let Windows catch up
8528             windows_guest.reboot();
8529             // Wait to make sure Windows completely rebooted
8530             thread::sleep(std::time::Duration::new(60, 0));
8531             // Check the guest sees the correct number
8532             assert_eq!(windows_guest.cpu_count(), vcpu_num);
8533             // Check the CH process has the correct number of vcpu threads
8534             assert_eq!(vcpu_threads_count(child.id()), vcpu_num);
8535 
8536             windows_guest.shutdown();
8537         });
8538 
8539         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8540         let _ = child.kill();
8541         let output = child.wait_with_output().unwrap();
8542 
8543         let _ = child_dnsmasq.kill();
8544         let _ = child_dnsmasq.wait();
8545 
8546         handle_child_output(r, &output);
8547     }
8548 
8549     #[test]
8550     #[cfg(not(feature = "mshv"))]
8551     #[cfg(not(target_arch = "aarch64"))]
8552     fn test_windows_guest_ram_hotplug() {
8553         let windows_guest = WindowsGuest::new();
8554 
8555         let mut ovmf_path = dirs::home_dir().unwrap();
8556         ovmf_path.push("workloads");
8557         ovmf_path.push(OVMF_NAME);
8558 
8559         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8560         let api_socket = temp_api_path(&tmp_dir);
8561 
8562         let mut child = GuestCommand::new(windows_guest.guest())
8563             .args(["--api-socket", &api_socket])
8564             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8565             .args(["--memory", "size=2G,hotplug_size=5G"])
8566             .args(["--kernel", ovmf_path.to_str().unwrap()])
8567             .args(["--serial", "tty"])
8568             .args(["--console", "off"])
8569             .default_disks()
8570             .default_net()
8571             .capture_output()
8572             .spawn()
8573             .unwrap();
8574 
8575         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8576 
8577         let r = std::panic::catch_unwind(|| {
8578             // Wait to make sure Windows boots up
8579             assert!(windows_guest.wait_for_boot());
8580 
8581             let ram_size = 2 * 1024 * 1024 * 1024;
8582             // Check the initial number of RAM the guest sees
8583             let current_ram_size = windows_guest.ram_size();
8584             // This size seems to be reserved by the system and thus the
8585             // reported amount differs by this constant value.
8586             let reserved_ram_size = ram_size - current_ram_size;
8587             // Verify that there's not more than 4mb constant diff wasted
8588             // by the reserved ram.
8589             assert!(reserved_ram_size < 4 * 1024 * 1024);
8590 
8591             let ram_size = 4 * 1024 * 1024 * 1024;
8592             // Hotplug some RAM
8593             resize_command(&api_socket, None, Some(ram_size), None, None);
8594             // Wait to make sure RAM has been added
8595             thread::sleep(std::time::Duration::new(10, 0));
8596             // Check the guest sees the correct number
8597             assert_eq!(windows_guest.ram_size(), ram_size - reserved_ram_size);
8598 
8599             let ram_size = 3 * 1024 * 1024 * 1024;
8600             // Unplug some RAM. Note that hot-remove most likely won't work.
8601             resize_command(&api_socket, None, Some(ram_size), None, None);
8602             // Wait to make sure RAM has been added
8603             thread::sleep(std::time::Duration::new(10, 0));
8604             // Reboot to let Windows catch up
8605             windows_guest.reboot();
8606             // Wait to make sure guest completely rebooted
8607             thread::sleep(std::time::Duration::new(60, 0));
8608             // Check the guest sees the correct number
8609             assert_eq!(windows_guest.ram_size(), ram_size - reserved_ram_size);
8610 
8611             windows_guest.shutdown();
8612         });
8613 
8614         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8615         let _ = child.kill();
8616         let output = child.wait_with_output().unwrap();
8617 
8618         let _ = child_dnsmasq.kill();
8619         let _ = child_dnsmasq.wait();
8620 
8621         handle_child_output(r, &output);
8622     }
8623 
8624     #[test]
8625     #[cfg(not(feature = "mshv"))]
8626     fn test_windows_guest_netdev_hotplug() {
8627         let windows_guest = WindowsGuest::new();
8628 
8629         let mut ovmf_path = dirs::home_dir().unwrap();
8630         ovmf_path.push("workloads");
8631         ovmf_path.push(OVMF_NAME);
8632 
8633         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8634         let api_socket = temp_api_path(&tmp_dir);
8635 
8636         let mut child = GuestCommand::new(windows_guest.guest())
8637             .args(["--api-socket", &api_socket])
8638             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8639             .args(["--memory", "size=4G"])
8640             .args(["--kernel", ovmf_path.to_str().unwrap()])
8641             .args(["--serial", "tty"])
8642             .args(["--console", "off"])
8643             .default_disks()
8644             .default_net()
8645             .capture_output()
8646             .spawn()
8647             .unwrap();
8648 
8649         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8650 
8651         let r = std::panic::catch_unwind(|| {
8652             // Wait to make sure Windows boots up
8653             assert!(windows_guest.wait_for_boot());
8654 
8655             // Initially present network device
8656             let netdev_num = 1;
8657             assert_eq!(windows_guest.netdev_count(), netdev_num);
8658             assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num);
8659 
8660             // Hotplug network device
8661             let (cmd_success, cmd_output) = remote_command_w_output(
8662                 &api_socket,
8663                 "add-net",
8664                 Some(windows_guest.guest().default_net_string().as_str()),
8665             );
8666             assert!(cmd_success);
8667             assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_net2\""));
8668             thread::sleep(std::time::Duration::new(5, 0));
8669             // Verify the device  is on the system
8670             let netdev_num = 2;
8671             assert_eq!(windows_guest.netdev_count(), netdev_num);
8672             assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num);
8673 
8674             // Remove network device
8675             let cmd_success = remote_command(&api_socket, "remove-device", Some("_net2"));
8676             assert!(cmd_success);
8677             thread::sleep(std::time::Duration::new(5, 0));
8678             // Verify the device has been removed
8679             let netdev_num = 1;
8680             assert_eq!(windows_guest.netdev_count(), netdev_num);
8681             assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num);
8682 
8683             windows_guest.shutdown();
8684         });
8685 
8686         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8687         let _ = child.kill();
8688         let output = child.wait_with_output().unwrap();
8689 
8690         let _ = child_dnsmasq.kill();
8691         let _ = child_dnsmasq.wait();
8692 
8693         handle_child_output(r, &output);
8694     }
8695 
8696     #[test]
8697     #[ignore = "See #6037"]
8698     #[cfg(not(feature = "mshv"))]
8699     #[cfg(not(target_arch = "aarch64"))]
8700     fn test_windows_guest_disk_hotplug() {
8701         let windows_guest = WindowsGuest::new();
8702 
8703         let mut ovmf_path = dirs::home_dir().unwrap();
8704         ovmf_path.push("workloads");
8705         ovmf_path.push(OVMF_NAME);
8706 
8707         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8708         let api_socket = temp_api_path(&tmp_dir);
8709 
8710         let mut child = GuestCommand::new(windows_guest.guest())
8711             .args(["--api-socket", &api_socket])
8712             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8713             .args(["--memory", "size=4G"])
8714             .args(["--kernel", ovmf_path.to_str().unwrap()])
8715             .args(["--serial", "tty"])
8716             .args(["--console", "off"])
8717             .default_disks()
8718             .default_net()
8719             .capture_output()
8720             .spawn()
8721             .unwrap();
8722 
8723         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8724 
8725         let disk = windows_guest.disk_new(WindowsGuest::FS_FAT, 100);
8726 
8727         let r = std::panic::catch_unwind(|| {
8728             // Wait to make sure Windows boots up
8729             assert!(windows_guest.wait_for_boot());
8730 
8731             // Initially present disk device
8732             let disk_num = 1;
8733             assert_eq!(windows_guest.disk_count(), disk_num);
8734             assert_eq!(disk_ctrl_threads_count(child.id()), disk_num);
8735 
8736             // Hotplug disk device
8737             let (cmd_success, cmd_output) = remote_command_w_output(
8738                 &api_socket,
8739                 "add-disk",
8740                 Some(format!("path={disk},readonly=off").as_str()),
8741             );
8742             assert!(cmd_success);
8743             assert!(String::from_utf8_lossy(&cmd_output).contains("\"id\":\"_disk2\""));
8744             thread::sleep(std::time::Duration::new(5, 0));
8745             // Online disk device
8746             windows_guest.disks_set_rw();
8747             windows_guest.disks_online();
8748             // Verify the device is on the system
8749             let disk_num = 2;
8750             assert_eq!(windows_guest.disk_count(), disk_num);
8751             assert_eq!(disk_ctrl_threads_count(child.id()), disk_num);
8752 
8753             let data = "hello";
8754             let fname = "d:\\world";
8755             windows_guest.disk_file_put(fname, data);
8756 
8757             // Unmount disk device
8758             let cmd_success = remote_command(&api_socket, "remove-device", Some("_disk2"));
8759             assert!(cmd_success);
8760             thread::sleep(std::time::Duration::new(5, 0));
8761             // Verify the device has been removed
8762             let disk_num = 1;
8763             assert_eq!(windows_guest.disk_count(), disk_num);
8764             assert_eq!(disk_ctrl_threads_count(child.id()), disk_num);
8765 
8766             // Remount and check the file exists with the expected contents
8767             let (cmd_success, _cmd_output) = remote_command_w_output(
8768                 &api_socket,
8769                 "add-disk",
8770                 Some(format!("path={disk},readonly=off").as_str()),
8771             );
8772             assert!(cmd_success);
8773             thread::sleep(std::time::Duration::new(5, 0));
8774             let out = windows_guest.disk_file_read(fname);
8775             assert_eq!(data, out.trim());
8776 
8777             // Intentionally no unmount, it'll happen at shutdown.
8778 
8779             windows_guest.shutdown();
8780         });
8781 
8782         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8783         let _ = child.kill();
8784         let output = child.wait_with_output().unwrap();
8785 
8786         let _ = child_dnsmasq.kill();
8787         let _ = child_dnsmasq.wait();
8788 
8789         handle_child_output(r, &output);
8790     }
8791 
8792     #[test]
8793     #[ignore = "See #6037"]
8794     #[cfg(not(feature = "mshv"))]
8795     #[cfg(not(target_arch = "aarch64"))]
8796     fn test_windows_guest_disk_hotplug_multi() {
8797         let windows_guest = WindowsGuest::new();
8798 
8799         let mut ovmf_path = dirs::home_dir().unwrap();
8800         ovmf_path.push("workloads");
8801         ovmf_path.push(OVMF_NAME);
8802 
8803         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8804         let api_socket = temp_api_path(&tmp_dir);
8805 
8806         let mut child = GuestCommand::new(windows_guest.guest())
8807             .args(["--api-socket", &api_socket])
8808             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8809             .args(["--memory", "size=2G"])
8810             .args(["--kernel", ovmf_path.to_str().unwrap()])
8811             .args(["--serial", "tty"])
8812             .args(["--console", "off"])
8813             .default_disks()
8814             .default_net()
8815             .capture_output()
8816             .spawn()
8817             .unwrap();
8818 
8819         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8820 
8821         // Predefined data to used at various test stages
8822         let disk_test_data: [[String; 4]; 2] = [
8823             [
8824                 "_disk2".to_string(),
8825                 windows_guest.disk_new(WindowsGuest::FS_FAT, 123),
8826                 "d:\\world".to_string(),
8827                 "hello".to_string(),
8828             ],
8829             [
8830                 "_disk3".to_string(),
8831                 windows_guest.disk_new(WindowsGuest::FS_NTFS, 333),
8832                 "e:\\hello".to_string(),
8833                 "world".to_string(),
8834             ],
8835         ];
8836 
8837         let r = std::panic::catch_unwind(|| {
8838             // Wait to make sure Windows boots up
8839             assert!(windows_guest.wait_for_boot());
8840 
8841             // Initially present disk device
8842             let disk_num = 1;
8843             assert_eq!(windows_guest.disk_count(), disk_num);
8844             assert_eq!(disk_ctrl_threads_count(child.id()), disk_num);
8845 
8846             for it in &disk_test_data {
8847                 let disk_id = it[0].as_str();
8848                 let disk = it[1].as_str();
8849                 // Hotplug disk device
8850                 let (cmd_success, cmd_output) = remote_command_w_output(
8851                     &api_socket,
8852                     "add-disk",
8853                     Some(format!("path={disk},readonly=off").as_str()),
8854                 );
8855                 assert!(cmd_success);
8856                 assert!(String::from_utf8_lossy(&cmd_output)
8857                     .contains(format!("\"id\":\"{disk_id}\"").as_str()));
8858                 thread::sleep(std::time::Duration::new(5, 0));
8859                 // Online disk devices
8860                 windows_guest.disks_set_rw();
8861                 windows_guest.disks_online();
8862             }
8863             // Verify the devices are on the system
8864             let disk_num = (disk_test_data.len() + 1) as u8;
8865             assert_eq!(windows_guest.disk_count(), disk_num);
8866             assert_eq!(disk_ctrl_threads_count(child.id()), disk_num);
8867 
8868             // Put test data
8869             for it in &disk_test_data {
8870                 let fname = it[2].as_str();
8871                 let data = it[3].as_str();
8872                 windows_guest.disk_file_put(fname, data);
8873             }
8874 
8875             // Unmount disk devices
8876             for it in &disk_test_data {
8877                 let disk_id = it[0].as_str();
8878                 let cmd_success = remote_command(&api_socket, "remove-device", Some(disk_id));
8879                 assert!(cmd_success);
8880                 thread::sleep(std::time::Duration::new(5, 0));
8881             }
8882 
8883             // Verify the devices have been removed
8884             let disk_num = 1;
8885             assert_eq!(windows_guest.disk_count(), disk_num);
8886             assert_eq!(disk_ctrl_threads_count(child.id()), disk_num);
8887 
8888             // Remount
8889             for it in &disk_test_data {
8890                 let disk = it[1].as_str();
8891                 let (cmd_success, _cmd_output) = remote_command_w_output(
8892                     &api_socket,
8893                     "add-disk",
8894                     Some(format!("path={disk},readonly=off").as_str()),
8895                 );
8896                 assert!(cmd_success);
8897                 thread::sleep(std::time::Duration::new(5, 0));
8898             }
8899 
8900             // Check the files exists with the expected contents
8901             for it in &disk_test_data {
8902                 let fname = it[2].as_str();
8903                 let data = it[3].as_str();
8904                 let out = windows_guest.disk_file_read(fname);
8905                 assert_eq!(data, out.trim());
8906             }
8907 
8908             // Intentionally no unmount, it'll happen at shutdown.
8909 
8910             windows_guest.shutdown();
8911         });
8912 
8913         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8914         let _ = child.kill();
8915         let output = child.wait_with_output().unwrap();
8916 
8917         let _ = child_dnsmasq.kill();
8918         let _ = child_dnsmasq.wait();
8919 
8920         handle_child_output(r, &output);
8921     }
8922 
8923     #[test]
8924     #[cfg(not(feature = "mshv"))]
8925     #[cfg(not(target_arch = "aarch64"))]
8926     fn test_windows_guest_netdev_multi() {
8927         let windows_guest = WindowsGuest::new();
8928 
8929         let mut ovmf_path = dirs::home_dir().unwrap();
8930         ovmf_path.push("workloads");
8931         ovmf_path.push(OVMF_NAME);
8932 
8933         let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap();
8934         let api_socket = temp_api_path(&tmp_dir);
8935 
8936         let mut child = GuestCommand::new(windows_guest.guest())
8937             .args(["--api-socket", &api_socket])
8938             .args(["--cpus", "boot=2,kvm_hyperv=on"])
8939             .args(["--memory", "size=4G"])
8940             .args(["--kernel", ovmf_path.to_str().unwrap()])
8941             .args(["--serial", "tty"])
8942             .args(["--console", "off"])
8943             .default_disks()
8944             // The multi net dev config is borrowed from test_multiple_network_interfaces
8945             .args([
8946                 "--net",
8947                 windows_guest.guest().default_net_string().as_str(),
8948                 "tap=,mac=8a:6b:6f:5a:de:ac,ip=192.168.3.1,mask=255.255.255.0",
8949                 "tap=mytap42,mac=fe:1f:9e:e1:60:f2,ip=192.168.4.1,mask=255.255.255.0",
8950             ])
8951             .capture_output()
8952             .spawn()
8953             .unwrap();
8954 
8955         let mut child_dnsmasq = windows_guest.run_dnsmasq();
8956 
8957         let r = std::panic::catch_unwind(|| {
8958             // Wait to make sure Windows boots up
8959             assert!(windows_guest.wait_for_boot());
8960 
8961             let netdev_num = 3;
8962             assert_eq!(windows_guest.netdev_count(), netdev_num);
8963             assert_eq!(netdev_ctrl_threads_count(child.id()), netdev_num);
8964 
8965             let tap_count = exec_host_command_output("ip link | grep -c mytap42");
8966             assert_eq!(String::from_utf8_lossy(&tap_count.stdout).trim(), "1");
8967 
8968             windows_guest.shutdown();
8969         });
8970 
8971         let _ = child.wait_timeout(std::time::Duration::from_secs(60));
8972         let _ = child.kill();
8973         let output = child.wait_with_output().unwrap();
8974 
8975         let _ = child_dnsmasq.kill();
8976         let _ = child_dnsmasq.wait();
8977 
8978         handle_child_output(r, &output);
8979     }
8980 }
8981 
8982 #[cfg(target_arch = "x86_64")]
8983 mod sgx {
8984     use crate::*;
8985 
8986     #[test]
8987     fn test_sgx() {
8988         let jammy_image = JAMMY_IMAGE_NAME.to_string();
8989         let jammy = UbuntuDiskConfig::new(jammy_image);
8990         let guest = Guest::new(Box::new(jammy));
8991 
8992         let mut child = GuestCommand::new(&guest)
8993             .args(["--cpus", "boot=1"])
8994             .args(["--memory", "size=512M"])
8995             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
8996             .default_disks()
8997             .default_net()
8998             .args(["--sgx-epc", "id=epc0,size=64M"])
8999             .capture_output()
9000             .spawn()
9001             .unwrap();
9002 
9003         let r = std::panic::catch_unwind(|| {
9004             guest.wait_vm_boot(None).unwrap();
9005 
9006             // Check if SGX is correctly detected in the guest.
9007             guest.check_sgx_support().unwrap();
9008 
9009             // Validate the SGX EPC section is 64MiB.
9010             assert_eq!(
9011                 guest
9012                     .ssh_command("cpuid -l 0x12 -s 2 | grep 'section size' | cut -d '=' -f 2")
9013                     .unwrap()
9014                     .trim(),
9015                 "0x0000000004000000"
9016             );
9017         });
9018 
9019         let _ = child.kill();
9020         let output = child.wait_with_output().unwrap();
9021 
9022         handle_child_output(r, &output);
9023     }
9024 }
9025 
9026 #[cfg(target_arch = "x86_64")]
9027 mod vfio {
9028     use crate::*;
9029     const NVIDIA_VFIO_DEVICE: &str = "/sys/bus/pci/devices/0002:00:01.0";
9030 
9031     fn test_nvidia_card_memory_hotplug(hotplug_method: &str) {
9032         let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string());
9033         let guest = Guest::new(Box::new(jammy));
9034         let api_socket = temp_api_path(&guest.tmp_dir);
9035 
9036         let mut child = GuestCommand::new(&guest)
9037             .args(["--cpus", "boot=4"])
9038             .args([
9039                 "--memory",
9040                 format!("size=4G,hotplug_size=4G,hotplug_method={hotplug_method}").as_str(),
9041             ])
9042             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
9043             .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()])
9044             .args(["--api-socket", &api_socket])
9045             .default_disks()
9046             .default_net()
9047             .capture_output()
9048             .spawn()
9049             .unwrap();
9050 
9051         let r = std::panic::catch_unwind(|| {
9052             guest.wait_vm_boot(None).unwrap();
9053 
9054             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
9055 
9056             guest.enable_memory_hotplug();
9057 
9058             // Add RAM to the VM
9059             let desired_ram = 6 << 30;
9060             resize_command(&api_socket, None, Some(desired_ram), None, None);
9061             thread::sleep(std::time::Duration::new(30, 0));
9062             assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000);
9063 
9064             // Check the VFIO device works when RAM is increased to 6GiB
9065             guest.check_nvidia_gpu();
9066         });
9067 
9068         let _ = child.kill();
9069         let output = child.wait_with_output().unwrap();
9070 
9071         handle_child_output(r, &output);
9072     }
9073 
9074     #[test]
9075     fn test_nvidia_card_memory_hotplug_acpi() {
9076         test_nvidia_card_memory_hotplug("acpi")
9077     }
9078 
9079     #[test]
9080     fn test_nvidia_card_memory_hotplug_virtio_mem() {
9081         test_nvidia_card_memory_hotplug("virtio-mem")
9082     }
9083 
9084     #[test]
9085     fn test_nvidia_card_pci_hotplug() {
9086         let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string());
9087         let guest = Guest::new(Box::new(jammy));
9088         let api_socket = temp_api_path(&guest.tmp_dir);
9089 
9090         let mut child = GuestCommand::new(&guest)
9091             .args(["--cpus", "boot=4"])
9092             .args(["--memory", "size=4G"])
9093             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
9094             .args(["--api-socket", &api_socket])
9095             .default_disks()
9096             .default_net()
9097             .capture_output()
9098             .spawn()
9099             .unwrap();
9100 
9101         let r = std::panic::catch_unwind(|| {
9102             guest.wait_vm_boot(None).unwrap();
9103 
9104             // Hotplug the card to the VM
9105             let (cmd_success, cmd_output) = remote_command_w_output(
9106                 &api_socket,
9107                 "add-device",
9108                 Some(format!("id=vfio0,path={NVIDIA_VFIO_DEVICE}").as_str()),
9109             );
9110             assert!(cmd_success);
9111             assert!(String::from_utf8_lossy(&cmd_output)
9112                 .contains("{\"id\":\"vfio0\",\"bdf\":\"0000:00:06.0\"}"));
9113 
9114             thread::sleep(std::time::Duration::new(10, 0));
9115 
9116             // Check the VFIO device works after hotplug
9117             guest.check_nvidia_gpu();
9118         });
9119 
9120         let _ = child.kill();
9121         let output = child.wait_with_output().unwrap();
9122 
9123         handle_child_output(r, &output);
9124     }
9125 
9126     #[test]
9127     fn test_nvidia_card_reboot() {
9128         let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string());
9129         let guest = Guest::new(Box::new(jammy));
9130         let api_socket = temp_api_path(&guest.tmp_dir);
9131 
9132         let mut child = GuestCommand::new(&guest)
9133             .args(["--cpus", "boot=4"])
9134             .args(["--memory", "size=4G"])
9135             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
9136             .args([
9137                 "--device",
9138                 format!("path={NVIDIA_VFIO_DEVICE},iommu=on").as_str(),
9139             ])
9140             .args(["--api-socket", &api_socket])
9141             .default_disks()
9142             .default_net()
9143             .capture_output()
9144             .spawn()
9145             .unwrap();
9146 
9147         let r = std::panic::catch_unwind(|| {
9148             guest.wait_vm_boot(None).unwrap();
9149 
9150             // Check the VFIO device works after boot
9151             guest.check_nvidia_gpu();
9152 
9153             guest.reboot_linux(0, None);
9154 
9155             // Check the VFIO device works after reboot
9156             guest.check_nvidia_gpu();
9157         });
9158 
9159         let _ = child.kill();
9160         let output = child.wait_with_output().unwrap();
9161 
9162         handle_child_output(r, &output);
9163     }
9164 
9165     #[test]
9166     fn test_nvidia_card_iommu_address_width() {
9167         let jammy = UbuntuDiskConfig::new(JAMMY_VFIO_IMAGE_NAME.to_string());
9168         let guest = Guest::new(Box::new(jammy));
9169         let api_socket = temp_api_path(&guest.tmp_dir);
9170 
9171         let mut child = GuestCommand::new(&guest)
9172             .args(["--cpus", "boot=4"])
9173             .args(["--memory", "size=4G"])
9174             .args(["--kernel", fw_path(FwType::RustHypervisorFirmware).as_str()])
9175             .args(["--device", format!("path={NVIDIA_VFIO_DEVICE}").as_str()])
9176             .args([
9177                 "--platform",
9178                 "num_pci_segments=2,iommu_segments=1,iommu_address_width=42",
9179             ])
9180             .args(["--api-socket", &api_socket])
9181             .default_disks()
9182             .default_net()
9183             .capture_output()
9184             .spawn()
9185             .unwrap();
9186 
9187         let r = std::panic::catch_unwind(|| {
9188             guest.wait_vm_boot(None).unwrap();
9189 
9190             assert!(guest
9191                 .ssh_command("sudo dmesg")
9192                 .unwrap()
9193                 .contains("input address: 42 bits"));
9194         });
9195 
9196         let _ = child.kill();
9197         let output = child.wait_with_output().unwrap();
9198 
9199         handle_child_output(r, &output);
9200     }
9201 }
9202 
9203 mod live_migration {
9204     use crate::*;
9205 
9206     fn start_live_migration(
9207         migration_socket: &str,
9208         src_api_socket: &str,
9209         dest_api_socket: &str,
9210         local: bool,
9211     ) -> bool {
9212         // Start to receive migration from the destination VM
9213         let mut receive_migration = Command::new(clh_command("ch-remote"))
9214             .args([
9215                 &format!("--api-socket={dest_api_socket}"),
9216                 "receive-migration",
9217                 &format! {"unix:{migration_socket}"},
9218             ])
9219             .stderr(Stdio::piped())
9220             .stdout(Stdio::piped())
9221             .spawn()
9222             .unwrap();
9223         // Give it '1s' to make sure the 'migration_socket' file is properly created
9224         thread::sleep(std::time::Duration::new(1, 0));
9225         // Start to send migration from the source VM
9226 
9227         let mut args = [
9228             format!("--api-socket={}", &src_api_socket),
9229             "send-migration".to_string(),
9230             format! {"unix:{migration_socket}"},
9231         ]
9232         .to_vec();
9233 
9234         if local {
9235             args.insert(2, "--local".to_string());
9236         }
9237 
9238         let mut send_migration = Command::new(clh_command("ch-remote"))
9239             .args(&args)
9240             .stderr(Stdio::piped())
9241             .stdout(Stdio::piped())
9242             .spawn()
9243             .unwrap();
9244 
9245         // The 'send-migration' command should be executed successfully within the given timeout
9246         let send_success = if let Some(status) = send_migration
9247             .wait_timeout(std::time::Duration::from_secs(30))
9248             .unwrap()
9249         {
9250             status.success()
9251         } else {
9252             false
9253         };
9254 
9255         if !send_success {
9256             let _ = send_migration.kill();
9257             let output = send_migration.wait_with_output().unwrap();
9258             eprintln!(
9259                 "\n\n==== Start 'send_migration' output ==== \
9260                 \n\n---stdout---\n{}\n\n---stderr---\n{} \
9261                 \n\n==== End 'send_migration' output ====\n\n",
9262                 String::from_utf8_lossy(&output.stdout),
9263                 String::from_utf8_lossy(&output.stderr)
9264             );
9265         }
9266 
9267         // The 'receive-migration' command should be executed successfully within the given timeout
9268         let receive_success = if let Some(status) = receive_migration
9269             .wait_timeout(std::time::Duration::from_secs(30))
9270             .unwrap()
9271         {
9272             status.success()
9273         } else {
9274             false
9275         };
9276 
9277         if !receive_success {
9278             let _ = receive_migration.kill();
9279             let output = receive_migration.wait_with_output().unwrap();
9280             eprintln!(
9281                 "\n\n==== Start 'receive_migration' output ==== \
9282                 \n\n---stdout---\n{}\n\n---stderr---\n{} \
9283                 \n\n==== End 'receive_migration' output ====\n\n",
9284                 String::from_utf8_lossy(&output.stdout),
9285                 String::from_utf8_lossy(&output.stderr)
9286             );
9287         }
9288 
9289         send_success && receive_success
9290     }
9291 
9292     fn print_and_panic(src_vm: Child, dest_vm: Child, ovs_vm: Option<Child>, message: &str) -> ! {
9293         let mut src_vm = src_vm;
9294         let mut dest_vm = dest_vm;
9295 
9296         let _ = src_vm.kill();
9297         let src_output = src_vm.wait_with_output().unwrap();
9298         eprintln!(
9299             "\n\n==== Start 'source_vm' stdout ====\n\n{}\n\n==== End 'source_vm' stdout ====",
9300             String::from_utf8_lossy(&src_output.stdout)
9301         );
9302         eprintln!(
9303             "\n\n==== Start 'source_vm' stderr ====\n\n{}\n\n==== End 'source_vm' stderr ====",
9304             String::from_utf8_lossy(&src_output.stderr)
9305         );
9306         let _ = dest_vm.kill();
9307         let dest_output = dest_vm.wait_with_output().unwrap();
9308         eprintln!(
9309                 "\n\n==== Start 'destination_vm' stdout ====\n\n{}\n\n==== End 'destination_vm' stdout ====",
9310                 String::from_utf8_lossy(&dest_output.stdout)
9311             );
9312         eprintln!(
9313                 "\n\n==== Start 'destination_vm' stderr ====\n\n{}\n\n==== End 'destination_vm' stderr ====",
9314                 String::from_utf8_lossy(&dest_output.stderr)
9315             );
9316 
9317         if let Some(ovs_vm) = ovs_vm {
9318             let mut ovs_vm = ovs_vm;
9319             let _ = ovs_vm.kill();
9320             let ovs_output = ovs_vm.wait_with_output().unwrap();
9321             eprintln!(
9322                 "\n\n==== Start 'ovs_vm' stdout ====\n\n{}\n\n==== End 'ovs_vm' stdout ====",
9323                 String::from_utf8_lossy(&ovs_output.stdout)
9324             );
9325             eprintln!(
9326                 "\n\n==== Start 'ovs_vm' stderr ====\n\n{}\n\n==== End 'ovs_vm' stderr ====",
9327                 String::from_utf8_lossy(&ovs_output.stderr)
9328             );
9329 
9330             cleanup_ovs_dpdk();
9331         }
9332 
9333         panic!("Test failed: {message}")
9334     }
9335 
9336     // This test exercises the local live-migration between two Cloud Hypervisor VMs on the
9337     // same host. It ensures the following behaviors:
9338     // 1. The source VM is up and functional (including various virtio-devices are working properly);
9339     // 2. The 'send-migration' and 'receive-migration' command finished successfully;
9340     // 3. The source VM terminated gracefully after live migration;
9341     // 4. The destination VM is functional (including various virtio-devices are working properly) after
9342     //    live migration;
9343     // Note: This test does not use vsock as we can't create two identical vsock on the same host.
9344     fn _test_live_migration(upgrade_test: bool, local: bool) {
9345         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
9346         let guest = Guest::new(Box::new(focal));
9347         let kernel_path = direct_kernel_boot_path();
9348         let console_text = String::from("On a branch floating down river a cricket, singing.");
9349         let net_id = "net123";
9350         let net_params = format!(
9351             "id={},tap=,mac={},ip={},mask=255.255.255.0",
9352             net_id, guest.network.guest_mac, guest.network.host_ip
9353         );
9354 
9355         let memory_param: &[&str] = if local {
9356             &["--memory", "size=4G,shared=on"]
9357         } else {
9358             &["--memory", "size=4G"]
9359         };
9360 
9361         let boot_vcpus = 2;
9362         let max_vcpus = 4;
9363 
9364         let pmem_temp_file = TempFile::new().unwrap();
9365         pmem_temp_file.as_file().set_len(128 << 20).unwrap();
9366         std::process::Command::new("mkfs.ext4")
9367             .arg(pmem_temp_file.as_path())
9368             .output()
9369             .expect("Expect creating disk image to succeed");
9370         let pmem_path = String::from("/dev/pmem0");
9371 
9372         // Start the source VM
9373         let src_vm_path = if !upgrade_test {
9374             clh_command("cloud-hypervisor")
9375         } else {
9376             cloud_hypervisor_release_path()
9377         };
9378         let src_api_socket = temp_api_path(&guest.tmp_dir);
9379         let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path);
9380         src_vm_cmd
9381             .args([
9382                 "--cpus",
9383                 format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
9384             ])
9385             .args(memory_param)
9386             .args(["--kernel", kernel_path.to_str().unwrap()])
9387             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
9388             .default_disks()
9389             .args(["--net", net_params.as_str()])
9390             .args(["--api-socket", &src_api_socket])
9391             .args([
9392                 "--pmem",
9393                 format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(),
9394             ]);
9395         let mut src_child = src_vm_cmd.capture_output().spawn().unwrap();
9396 
9397         // Start the destination VM
9398         let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
9399         dest_api_socket.push_str(".dest");
9400         let mut dest_child = GuestCommand::new(&guest)
9401             .args(["--api-socket", &dest_api_socket])
9402             .capture_output()
9403             .spawn()
9404             .unwrap();
9405 
9406         let r = std::panic::catch_unwind(|| {
9407             guest.wait_vm_boot(None).unwrap();
9408 
9409             // Make sure the source VM is functional
9410             // Check the number of vCPUs
9411             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
9412 
9413             // Check the guest RAM
9414             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
9415 
9416             // Check the guest virtio-devices, e.g. block, rng, console, and net
9417             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
9418 
9419             // x86_64: Following what's done in the `test_snapshot_restore`, we need
9420             // to make sure that removing and adding back the virtio-net device does
9421             // not break the live-migration support for virtio-pci.
9422             #[cfg(target_arch = "x86_64")]
9423             {
9424                 assert!(remote_command(
9425                     &src_api_socket,
9426                     "remove-device",
9427                     Some(net_id),
9428                 ));
9429                 thread::sleep(std::time::Duration::new(10, 0));
9430 
9431                 // Plug the virtio-net device again
9432                 assert!(remote_command(
9433                     &src_api_socket,
9434                     "add-net",
9435                     Some(net_params.as_str()),
9436                 ));
9437                 thread::sleep(std::time::Duration::new(10, 0));
9438             }
9439 
9440             // Start the live-migration
9441             let migration_socket = String::from(
9442                 guest
9443                     .tmp_dir
9444                     .as_path()
9445                     .join("live-migration.sock")
9446                     .to_str()
9447                     .unwrap(),
9448             );
9449 
9450             assert!(
9451                 start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local),
9452                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
9453             );
9454         });
9455 
9456         // Check and report any errors occurred during the live-migration
9457         if r.is_err() {
9458             print_and_panic(
9459                 src_child,
9460                 dest_child,
9461                 None,
9462                 "Error occurred during live-migration",
9463             );
9464         }
9465 
9466         // Check the source vm has been terminated successful (give it '3s' to settle)
9467         thread::sleep(std::time::Duration::new(3, 0));
9468         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
9469             print_and_panic(
9470                 src_child,
9471                 dest_child,
9472                 None,
9473                 "source VM was not terminated successfully.",
9474             );
9475         };
9476 
9477         // Post live-migration check to make sure the destination VM is functional
9478         let r = std::panic::catch_unwind(|| {
9479             // Perform same checks to validate VM has been properly migrated
9480             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
9481             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
9482 
9483             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
9484         });
9485 
9486         // Clean-up the destination VM and make sure it terminated correctly
9487         let _ = dest_child.kill();
9488         let dest_output = dest_child.wait_with_output().unwrap();
9489         handle_child_output(r, &dest_output);
9490 
9491         // Check the destination VM has the expected 'console_text' from its output
9492         let r = std::panic::catch_unwind(|| {
9493             assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text));
9494         });
9495         handle_child_output(r, &dest_output);
9496     }
9497 
9498     fn _test_live_migration_balloon(upgrade_test: bool, local: bool) {
9499         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
9500         let guest = Guest::new(Box::new(focal));
9501         let kernel_path = direct_kernel_boot_path();
9502         let console_text = String::from("On a branch floating down river a cricket, singing.");
9503         let net_id = "net123";
9504         let net_params = format!(
9505             "id={},tap=,mac={},ip={},mask=255.255.255.0",
9506             net_id, guest.network.guest_mac, guest.network.host_ip
9507         );
9508 
9509         let memory_param: &[&str] = if local {
9510             &[
9511                 "--memory",
9512                 "size=4G,hotplug_method=virtio-mem,hotplug_size=8G,shared=on",
9513                 "--balloon",
9514                 "size=0",
9515             ]
9516         } else {
9517             &[
9518                 "--memory",
9519                 "size=4G,hotplug_method=virtio-mem,hotplug_size=8G",
9520                 "--balloon",
9521                 "size=0",
9522             ]
9523         };
9524 
9525         let boot_vcpus = 2;
9526         let max_vcpus = 4;
9527 
9528         let pmem_temp_file = TempFile::new().unwrap();
9529         pmem_temp_file.as_file().set_len(128 << 20).unwrap();
9530         std::process::Command::new("mkfs.ext4")
9531             .arg(pmem_temp_file.as_path())
9532             .output()
9533             .expect("Expect creating disk image to succeed");
9534         let pmem_path = String::from("/dev/pmem0");
9535 
9536         // Start the source VM
9537         let src_vm_path = if !upgrade_test {
9538             clh_command("cloud-hypervisor")
9539         } else {
9540             cloud_hypervisor_release_path()
9541         };
9542         let src_api_socket = temp_api_path(&guest.tmp_dir);
9543         let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path);
9544         src_vm_cmd
9545             .args([
9546                 "--cpus",
9547                 format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
9548             ])
9549             .args(memory_param)
9550             .args(["--kernel", kernel_path.to_str().unwrap()])
9551             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
9552             .default_disks()
9553             .args(["--net", net_params.as_str()])
9554             .args(["--api-socket", &src_api_socket])
9555             .args([
9556                 "--pmem",
9557                 format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(),
9558             ]);
9559         let mut src_child = src_vm_cmd.capture_output().spawn().unwrap();
9560 
9561         // Start the destination VM
9562         let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
9563         dest_api_socket.push_str(".dest");
9564         let mut dest_child = GuestCommand::new(&guest)
9565             .args(["--api-socket", &dest_api_socket])
9566             .capture_output()
9567             .spawn()
9568             .unwrap();
9569 
9570         let r = std::panic::catch_unwind(|| {
9571             guest.wait_vm_boot(None).unwrap();
9572 
9573             // Make sure the source VM is functional
9574             // Check the number of vCPUs
9575             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
9576 
9577             // Check the guest RAM
9578             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
9579             // Increase the guest RAM
9580             resize_command(&src_api_socket, None, Some(6 << 30), None, None);
9581             thread::sleep(std::time::Duration::new(5, 0));
9582             assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000);
9583             // Use balloon to remove RAM from the VM
9584             resize_command(&src_api_socket, None, None, Some(1 << 30), None);
9585             thread::sleep(std::time::Duration::new(5, 0));
9586             let total_memory = guest.get_total_memory().unwrap_or_default();
9587             assert!(total_memory > 4_800_000);
9588             assert!(total_memory < 5_760_000);
9589 
9590             // Check the guest virtio-devices, e.g. block, rng, console, and net
9591             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
9592 
9593             // x86_64: Following what's done in the `test_snapshot_restore`, we need
9594             // to make sure that removing and adding back the virtio-net device does
9595             // not break the live-migration support for virtio-pci.
9596             #[cfg(target_arch = "x86_64")]
9597             {
9598                 assert!(remote_command(
9599                     &src_api_socket,
9600                     "remove-device",
9601                     Some(net_id),
9602                 ));
9603                 thread::sleep(std::time::Duration::new(10, 0));
9604 
9605                 // Plug the virtio-net device again
9606                 assert!(remote_command(
9607                     &src_api_socket,
9608                     "add-net",
9609                     Some(net_params.as_str()),
9610                 ));
9611                 thread::sleep(std::time::Duration::new(10, 0));
9612             }
9613 
9614             // Start the live-migration
9615             let migration_socket = String::from(
9616                 guest
9617                     .tmp_dir
9618                     .as_path()
9619                     .join("live-migration.sock")
9620                     .to_str()
9621                     .unwrap(),
9622             );
9623 
9624             assert!(
9625                 start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local),
9626                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
9627             );
9628         });
9629 
9630         // Check and report any errors occurred during the live-migration
9631         if r.is_err() {
9632             print_and_panic(
9633                 src_child,
9634                 dest_child,
9635                 None,
9636                 "Error occurred during live-migration",
9637             );
9638         }
9639 
9640         // Check the source vm has been terminated successful (give it '3s' to settle)
9641         thread::sleep(std::time::Duration::new(3, 0));
9642         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
9643             print_and_panic(
9644                 src_child,
9645                 dest_child,
9646                 None,
9647                 "source VM was not terminated successfully.",
9648             );
9649         };
9650 
9651         // Post live-migration check to make sure the destination VM is functional
9652         let r = std::panic::catch_unwind(|| {
9653             // Perform same checks to validate VM has been properly migrated
9654             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
9655             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
9656 
9657             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
9658 
9659             // Perform checks on guest RAM using balloon
9660             let total_memory = guest.get_total_memory().unwrap_or_default();
9661             assert!(total_memory > 4_800_000);
9662             assert!(total_memory < 5_760_000);
9663             // Deflate balloon to restore entire RAM to the VM
9664             resize_command(&dest_api_socket, None, None, Some(0), None);
9665             thread::sleep(std::time::Duration::new(5, 0));
9666             assert!(guest.get_total_memory().unwrap_or_default() > 5_760_000);
9667             // Decrease guest RAM with virtio-mem
9668             resize_command(&dest_api_socket, None, Some(5 << 30), None, None);
9669             thread::sleep(std::time::Duration::new(5, 0));
9670             let total_memory = guest.get_total_memory().unwrap_or_default();
9671             assert!(total_memory > 4_800_000);
9672             assert!(total_memory < 5_760_000);
9673         });
9674 
9675         // Clean-up the destination VM and make sure it terminated correctly
9676         let _ = dest_child.kill();
9677         let dest_output = dest_child.wait_with_output().unwrap();
9678         handle_child_output(r, &dest_output);
9679 
9680         // Check the destination VM has the expected 'console_text' from its output
9681         let r = std::panic::catch_unwind(|| {
9682             assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text));
9683         });
9684         handle_child_output(r, &dest_output);
9685     }
9686 
9687     fn _test_live_migration_numa(upgrade_test: bool, local: bool) {
9688         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
9689         let guest = Guest::new(Box::new(focal));
9690         let kernel_path = direct_kernel_boot_path();
9691         let console_text = String::from("On a branch floating down river a cricket, singing.");
9692         let net_id = "net123";
9693         let net_params = format!(
9694             "id={},tap=,mac={},ip={},mask=255.255.255.0",
9695             net_id, guest.network.guest_mac, guest.network.host_ip
9696         );
9697 
9698         let memory_param: &[&str] = if local {
9699             &[
9700                 "--memory",
9701                 "size=0,hotplug_method=virtio-mem,shared=on",
9702                 "--memory-zone",
9703                 "id=mem0,size=1G,hotplug_size=4G,shared=on",
9704                 "id=mem1,size=1G,hotplug_size=4G,shared=on",
9705                 "id=mem2,size=2G,hotplug_size=4G,shared=on",
9706                 "--numa",
9707                 "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0",
9708                 "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1",
9709                 "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2",
9710             ]
9711         } else {
9712             &[
9713                 "--memory",
9714                 "size=0,hotplug_method=virtio-mem",
9715                 "--memory-zone",
9716                 "id=mem0,size=1G,hotplug_size=4G",
9717                 "id=mem1,size=1G,hotplug_size=4G",
9718                 "id=mem2,size=2G,hotplug_size=4G",
9719                 "--numa",
9720                 "guest_numa_id=0,cpus=[0-2,9],distances=[1@15,2@20],memory_zones=mem0",
9721                 "guest_numa_id=1,cpus=[3-4,6-8],distances=[0@20,2@25],memory_zones=mem1",
9722                 "guest_numa_id=2,cpus=[5,10-11],distances=[0@25,1@30],memory_zones=mem2",
9723             ]
9724         };
9725 
9726         let boot_vcpus = 6;
9727         let max_vcpus = 12;
9728 
9729         let pmem_temp_file = TempFile::new().unwrap();
9730         pmem_temp_file.as_file().set_len(128 << 20).unwrap();
9731         std::process::Command::new("mkfs.ext4")
9732             .arg(pmem_temp_file.as_path())
9733             .output()
9734             .expect("Expect creating disk image to succeed");
9735         let pmem_path = String::from("/dev/pmem0");
9736 
9737         // Start the source VM
9738         let src_vm_path = if !upgrade_test {
9739             clh_command("cloud-hypervisor")
9740         } else {
9741             cloud_hypervisor_release_path()
9742         };
9743         let src_api_socket = temp_api_path(&guest.tmp_dir);
9744         let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path);
9745         src_vm_cmd
9746             .args([
9747                 "--cpus",
9748                 format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
9749             ])
9750             .args(memory_param)
9751             .args(["--kernel", kernel_path.to_str().unwrap()])
9752             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
9753             .default_disks()
9754             .args(["--net", net_params.as_str()])
9755             .args(["--api-socket", &src_api_socket])
9756             .args([
9757                 "--pmem",
9758                 format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(),
9759             ]);
9760         let mut src_child = src_vm_cmd.capture_output().spawn().unwrap();
9761 
9762         // Start the destination VM
9763         let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
9764         dest_api_socket.push_str(".dest");
9765         let mut dest_child = GuestCommand::new(&guest)
9766             .args(["--api-socket", &dest_api_socket])
9767             .capture_output()
9768             .spawn()
9769             .unwrap();
9770 
9771         let r = std::panic::catch_unwind(|| {
9772             guest.wait_vm_boot(None).unwrap();
9773 
9774             // Make sure the source VM is functional
9775             // Check the number of vCPUs
9776             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
9777 
9778             // Check the guest RAM
9779             assert!(guest.get_total_memory().unwrap_or_default() > 2_880_000);
9780 
9781             // Check the guest virtio-devices, e.g. block, rng, console, and net
9782             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
9783 
9784             // Check the NUMA parameters are applied correctly and resize
9785             // each zone to test the case where we migrate a VM with the
9786             // virtio-mem regions being used.
9787             {
9788                 guest.check_numa_common(
9789                     Some(&[960_000, 960_000, 1_920_000]),
9790                     Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]),
9791                     Some(&["10 15 20", "20 10 25", "25 30 10"]),
9792                 );
9793 
9794                 // AArch64 currently does not support hotplug, and therefore we only
9795                 // test hotplug-related function on x86_64 here.
9796                 #[cfg(target_arch = "x86_64")]
9797                 {
9798                     guest.enable_memory_hotplug();
9799 
9800                     // Resize every memory zone and check each associated NUMA node
9801                     // has been assigned the right amount of memory.
9802                     resize_zone_command(&src_api_socket, "mem0", "2G");
9803                     resize_zone_command(&src_api_socket, "mem1", "2G");
9804                     resize_zone_command(&src_api_socket, "mem2", "3G");
9805                     thread::sleep(std::time::Duration::new(5, 0));
9806 
9807                     guest.check_numa_common(Some(&[1_920_000, 1_920_000, 1_920_000]), None, None);
9808                 }
9809             }
9810 
9811             // x86_64: Following what's done in the `test_snapshot_restore`, we need
9812             // to make sure that removing and adding back the virtio-net device does
9813             // not break the live-migration support for virtio-pci.
9814             #[cfg(target_arch = "x86_64")]
9815             {
9816                 assert!(remote_command(
9817                     &src_api_socket,
9818                     "remove-device",
9819                     Some(net_id),
9820                 ));
9821                 thread::sleep(std::time::Duration::new(10, 0));
9822 
9823                 // Plug the virtio-net device again
9824                 assert!(remote_command(
9825                     &src_api_socket,
9826                     "add-net",
9827                     Some(net_params.as_str()),
9828                 ));
9829                 thread::sleep(std::time::Duration::new(10, 0));
9830             }
9831 
9832             // Start the live-migration
9833             let migration_socket = String::from(
9834                 guest
9835                     .tmp_dir
9836                     .as_path()
9837                     .join("live-migration.sock")
9838                     .to_str()
9839                     .unwrap(),
9840             );
9841 
9842             assert!(
9843                 start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local),
9844                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
9845             );
9846         });
9847 
9848         // Check and report any errors occurred during the live-migration
9849         if r.is_err() {
9850             print_and_panic(
9851                 src_child,
9852                 dest_child,
9853                 None,
9854                 "Error occurred during live-migration",
9855             );
9856         }
9857 
9858         // Check the source vm has been terminated successful (give it '3s' to settle)
9859         thread::sleep(std::time::Duration::new(3, 0));
9860         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
9861             print_and_panic(
9862                 src_child,
9863                 dest_child,
9864                 None,
9865                 "source VM was not terminated successfully.",
9866             );
9867         };
9868 
9869         // Post live-migration check to make sure the destination VM is functional
9870         let r = std::panic::catch_unwind(|| {
9871             // Perform same checks to validate VM has been properly migrated
9872             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
9873             #[cfg(target_arch = "x86_64")]
9874             assert!(guest.get_total_memory().unwrap_or_default() > 6_720_000);
9875             #[cfg(target_arch = "aarch64")]
9876             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
9877 
9878             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
9879 
9880             // Perform NUMA related checks
9881             {
9882                 #[cfg(target_arch = "aarch64")]
9883                 {
9884                     guest.check_numa_common(
9885                         Some(&[960_000, 960_000, 1_920_000]),
9886                         Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]),
9887                         Some(&["10 15 20", "20 10 25", "25 30 10"]),
9888                     );
9889                 }
9890 
9891                 // AArch64 currently does not support hotplug, and therefore we only
9892                 // test hotplug-related function on x86_64 here.
9893                 #[cfg(target_arch = "x86_64")]
9894                 {
9895                     guest.check_numa_common(
9896                         Some(&[1_920_000, 1_920_000, 2_880_000]),
9897                         Some(&[vec![0, 1, 2], vec![3, 4], vec![5]]),
9898                         Some(&["10 15 20", "20 10 25", "25 30 10"]),
9899                     );
9900 
9901                     guest.enable_memory_hotplug();
9902 
9903                     // Resize every memory zone and check each associated NUMA node
9904                     // has been assigned the right amount of memory.
9905                     resize_zone_command(&dest_api_socket, "mem0", "4G");
9906                     resize_zone_command(&dest_api_socket, "mem1", "4G");
9907                     resize_zone_command(&dest_api_socket, "mem2", "4G");
9908                     // Resize to the maximum amount of CPUs and check each NUMA
9909                     // node has been assigned the right CPUs set.
9910                     resize_command(&dest_api_socket, Some(max_vcpus), None, None, None);
9911                     thread::sleep(std::time::Duration::new(5, 0));
9912 
9913                     guest.check_numa_common(
9914                         Some(&[3_840_000, 3_840_000, 3_840_000]),
9915                         Some(&[vec![0, 1, 2, 9], vec![3, 4, 6, 7, 8], vec![5, 10, 11]]),
9916                         None,
9917                     );
9918                 }
9919             }
9920         });
9921 
9922         // Clean-up the destination VM and make sure it terminated correctly
9923         let _ = dest_child.kill();
9924         let dest_output = dest_child.wait_with_output().unwrap();
9925         handle_child_output(r, &dest_output);
9926 
9927         // Check the destination VM has the expected 'console_text' from its output
9928         let r = std::panic::catch_unwind(|| {
9929             assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text));
9930         });
9931         handle_child_output(r, &dest_output);
9932     }
9933 
9934     fn _test_live_migration_watchdog(upgrade_test: bool, local: bool) {
9935         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
9936         let guest = Guest::new(Box::new(focal));
9937         let kernel_path = direct_kernel_boot_path();
9938         let console_text = String::from("On a branch floating down river a cricket, singing.");
9939         let net_id = "net123";
9940         let net_params = format!(
9941             "id={},tap=,mac={},ip={},mask=255.255.255.0",
9942             net_id, guest.network.guest_mac, guest.network.host_ip
9943         );
9944 
9945         let memory_param: &[&str] = if local {
9946             &["--memory", "size=4G,shared=on"]
9947         } else {
9948             &["--memory", "size=4G"]
9949         };
9950 
9951         let boot_vcpus = 2;
9952         let max_vcpus = 4;
9953 
9954         let pmem_temp_file = TempFile::new().unwrap();
9955         pmem_temp_file.as_file().set_len(128 << 20).unwrap();
9956         std::process::Command::new("mkfs.ext4")
9957             .arg(pmem_temp_file.as_path())
9958             .output()
9959             .expect("Expect creating disk image to succeed");
9960         let pmem_path = String::from("/dev/pmem0");
9961 
9962         // Start the source VM
9963         let src_vm_path = if !upgrade_test {
9964             clh_command("cloud-hypervisor")
9965         } else {
9966             cloud_hypervisor_release_path()
9967         };
9968         let src_api_socket = temp_api_path(&guest.tmp_dir);
9969         let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path);
9970         src_vm_cmd
9971             .args([
9972                 "--cpus",
9973                 format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
9974             ])
9975             .args(memory_param)
9976             .args(["--kernel", kernel_path.to_str().unwrap()])
9977             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
9978             .default_disks()
9979             .args(["--net", net_params.as_str()])
9980             .args(["--api-socket", &src_api_socket])
9981             .args([
9982                 "--pmem",
9983                 format!("file={}", pmem_temp_file.as_path().to_str().unwrap(),).as_str(),
9984             ])
9985             .args(["--watchdog"]);
9986         let mut src_child = src_vm_cmd.capture_output().spawn().unwrap();
9987 
9988         // Start the destination VM
9989         let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
9990         dest_api_socket.push_str(".dest");
9991         let mut dest_child = GuestCommand::new(&guest)
9992             .args(["--api-socket", &dest_api_socket])
9993             .capture_output()
9994             .spawn()
9995             .unwrap();
9996 
9997         let r = std::panic::catch_unwind(|| {
9998             guest.wait_vm_boot(None).unwrap();
9999 
10000             // Make sure the source VM is functional
10001             // Check the number of vCPUs
10002             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
10003             // Check the guest RAM
10004             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
10005             // Check the guest virtio-devices, e.g. block, rng, console, and net
10006             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
10007             // x86_64: Following what's done in the `test_snapshot_restore`, we need
10008             // to make sure that removing and adding back the virtio-net device does
10009             // not break the live-migration support for virtio-pci.
10010             #[cfg(target_arch = "x86_64")]
10011             {
10012                 assert!(remote_command(
10013                     &src_api_socket,
10014                     "remove-device",
10015                     Some(net_id),
10016                 ));
10017                 thread::sleep(std::time::Duration::new(10, 0));
10018 
10019                 // Plug the virtio-net device again
10020                 assert!(remote_command(
10021                     &src_api_socket,
10022                     "add-net",
10023                     Some(net_params.as_str()),
10024                 ));
10025                 thread::sleep(std::time::Duration::new(10, 0));
10026             }
10027 
10028             // Enable watchdog and ensure its functional
10029             let expected_reboot_count = 1;
10030             // Enable the watchdog with a 15s timeout
10031             enable_guest_watchdog(&guest, 15);
10032 
10033             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
10034             assert_eq!(
10035                 guest
10036                     .ssh_command("sudo journalctl | grep -c -- \"Watchdog started\"")
10037                     .unwrap()
10038                     .trim()
10039                     .parse::<u32>()
10040                     .unwrap_or_default(),
10041                 1
10042             );
10043             // Allow some normal time to elapse to check we don't get spurious reboots
10044             thread::sleep(std::time::Duration::new(40, 0));
10045             // Check no reboot
10046             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
10047 
10048             // Start the live-migration
10049             let migration_socket = String::from(
10050                 guest
10051                     .tmp_dir
10052                     .as_path()
10053                     .join("live-migration.sock")
10054                     .to_str()
10055                     .unwrap(),
10056             );
10057 
10058             assert!(
10059                 start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local),
10060                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
10061             );
10062         });
10063 
10064         // Check and report any errors occurred during the live-migration
10065         if r.is_err() {
10066             print_and_panic(
10067                 src_child,
10068                 dest_child,
10069                 None,
10070                 "Error occurred during live-migration",
10071             );
10072         }
10073 
10074         // Check the source vm has been terminated successful (give it '3s' to settle)
10075         thread::sleep(std::time::Duration::new(3, 0));
10076         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
10077             print_and_panic(
10078                 src_child,
10079                 dest_child,
10080                 None,
10081                 "source VM was not terminated successfully.",
10082             );
10083         };
10084 
10085         // Post live-migration check to make sure the destination VM is functional
10086         let r = std::panic::catch_unwind(|| {
10087             // Perform same checks to validate VM has been properly migrated
10088             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
10089             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
10090 
10091             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
10092 
10093             // Perform checks on watchdog
10094             let mut expected_reboot_count = 1;
10095 
10096             // Allow some normal time to elapse to check we don't get spurious reboots
10097             thread::sleep(std::time::Duration::new(40, 0));
10098             // Check no reboot
10099             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
10100 
10101             // Trigger a panic (sync first). We need to do this inside a screen with a delay so the SSH command returns.
10102             guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap();
10103             // Allow some time for the watchdog to trigger (max 30s) and reboot to happen
10104             guest.wait_vm_boot(Some(50)).unwrap();
10105             // Check a reboot is triggered by the watchdog
10106             expected_reboot_count += 1;
10107             assert_eq!(get_reboot_count(&guest), expected_reboot_count);
10108 
10109             #[cfg(target_arch = "x86_64")]
10110             {
10111                 // Now pause the VM and remain offline for 30s
10112                 assert!(remote_command(&dest_api_socket, "pause", None));
10113                 thread::sleep(std::time::Duration::new(30, 0));
10114                 assert!(remote_command(&dest_api_socket, "resume", None));
10115 
10116                 // Check no reboot
10117                 assert_eq!(get_reboot_count(&guest), expected_reboot_count);
10118             }
10119         });
10120 
10121         // Clean-up the destination VM and make sure it terminated correctly
10122         let _ = dest_child.kill();
10123         let dest_output = dest_child.wait_with_output().unwrap();
10124         handle_child_output(r, &dest_output);
10125 
10126         // Check the destination VM has the expected 'console_text' from its output
10127         let r = std::panic::catch_unwind(|| {
10128             assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text));
10129         });
10130         handle_child_output(r, &dest_output);
10131     }
10132 
10133     fn _test_live_migration_ovs_dpdk(upgrade_test: bool, local: bool) {
10134         let ovs_focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10135         let ovs_guest = Guest::new(Box::new(ovs_focal));
10136 
10137         let migration_focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10138         let migration_guest = Guest::new(Box::new(migration_focal));
10139         let src_api_socket = temp_api_path(&migration_guest.tmp_dir);
10140 
10141         // Start two VMs that are connected through ovs-dpdk and one of the VMs is the source VM for live-migration
10142         let (mut ovs_child, mut src_child) =
10143             setup_ovs_dpdk_guests(&ovs_guest, &migration_guest, &src_api_socket, upgrade_test);
10144 
10145         // Start the destination VM
10146         let mut dest_api_socket = temp_api_path(&migration_guest.tmp_dir);
10147         dest_api_socket.push_str(".dest");
10148         let mut dest_child = GuestCommand::new(&migration_guest)
10149             .args(["--api-socket", &dest_api_socket])
10150             .capture_output()
10151             .spawn()
10152             .unwrap();
10153 
10154         let r = std::panic::catch_unwind(|| {
10155             // Give it '1s' to make sure the 'dest_api_socket' file is properly created
10156             thread::sleep(std::time::Duration::new(1, 0));
10157 
10158             // Start the live-migration
10159             let migration_socket = String::from(
10160                 migration_guest
10161                     .tmp_dir
10162                     .as_path()
10163                     .join("live-migration.sock")
10164                     .to_str()
10165                     .unwrap(),
10166             );
10167 
10168             assert!(
10169                 start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, local),
10170                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
10171             );
10172         });
10173 
10174         // Check and report any errors occurred during the live-migration
10175         if r.is_err() {
10176             print_and_panic(
10177                 src_child,
10178                 dest_child,
10179                 Some(ovs_child),
10180                 "Error occurred during live-migration",
10181             );
10182         }
10183 
10184         // Check the source vm has been terminated successful (give it '3s' to settle)
10185         thread::sleep(std::time::Duration::new(3, 0));
10186         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
10187             print_and_panic(
10188                 src_child,
10189                 dest_child,
10190                 Some(ovs_child),
10191                 "source VM was not terminated successfully.",
10192             );
10193         };
10194 
10195         // Post live-migration check to make sure the destination VM is functional
10196         let r = std::panic::catch_unwind(|| {
10197             // Perform same checks to validate VM has been properly migrated
10198             // Spawn a new netcat listener in the OVS VM
10199             let guest_ip = ovs_guest.network.guest_ip.clone();
10200             thread::spawn(move || {
10201                 ssh_command_ip(
10202                     "nc -l 12345",
10203                     &guest_ip,
10204                     DEFAULT_SSH_RETRIES,
10205                     DEFAULT_SSH_TIMEOUT,
10206                 )
10207                 .unwrap();
10208             });
10209 
10210             // Wait for the server to be listening
10211             thread::sleep(std::time::Duration::new(5, 0));
10212 
10213             // And check the connection is still functional after live-migration
10214             migration_guest
10215                 .ssh_command("nc -vz 172.100.0.1 12345")
10216                 .unwrap();
10217         });
10218 
10219         // Clean-up the destination VM and OVS VM, and make sure they terminated correctly
10220         let _ = dest_child.kill();
10221         let _ = ovs_child.kill();
10222         let dest_output = dest_child.wait_with_output().unwrap();
10223         let ovs_output = ovs_child.wait_with_output().unwrap();
10224 
10225         cleanup_ovs_dpdk();
10226 
10227         handle_child_output(r, &dest_output);
10228         handle_child_output(Ok(()), &ovs_output);
10229     }
10230 
10231     // This test exercises the local live-migration between two Cloud Hypervisor VMs on the
10232     // same host with Landlock enabled on both VMs. The test validates the following:
10233     // 1. The source VM is up and functional
10234     // 2. Ensure Landlock is enabled on source VM by hotplugging a disk. As the path for this
10235     //    disk is not known to the source VM this step will fail.
10236     // 3. The 'send-migration' and 'receive-migration' command finished successfully;
10237     // 4. The source VM terminated gracefully after live migration;
10238     // 5. The destination VM is functional after live migration;
10239     // 6. Ensure Landlock is enabled on destination VM by hotplugging a disk. As the path for
10240     //    this disk is not known to the destination VM this step will fail.
10241     fn _test_live_migration_with_landlock() {
10242         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10243         let guest = Guest::new(Box::new(focal));
10244         let kernel_path = direct_kernel_boot_path();
10245         let net_id = "net123";
10246         let net_params = format!(
10247             "id={},tap=,mac={},ip={},mask=255.255.255.0",
10248             net_id, guest.network.guest_mac, guest.network.host_ip
10249         );
10250 
10251         let boot_vcpus = 2;
10252         let max_vcpus = 4;
10253 
10254         let mut blk_file_path = dirs::home_dir().unwrap();
10255         blk_file_path.push("workloads");
10256         blk_file_path.push("blk.img");
10257 
10258         let src_api_socket = temp_api_path(&guest.tmp_dir);
10259         let mut src_child = GuestCommand::new(&guest)
10260             .args([
10261                 "--cpus",
10262                 format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
10263             ])
10264             .args(["--memory", "size=4G,shared=on"])
10265             .args(["--kernel", kernel_path.to_str().unwrap()])
10266             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
10267             .default_disks()
10268             .args(["--api-socket", &src_api_socket])
10269             .args(["--landlock"])
10270             .args(["--net", net_params.as_str()])
10271             .args([
10272                 "--landlock-rules",
10273                 format!("path={:?},access=rw", guest.tmp_dir.as_path()).as_str(),
10274             ])
10275             .capture_output()
10276             .spawn()
10277             .unwrap();
10278 
10279         // Start the destination VM
10280         let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
10281         dest_api_socket.push_str(".dest");
10282         let mut dest_child = GuestCommand::new(&guest)
10283             .args(["--api-socket", &dest_api_socket])
10284             .capture_output()
10285             .spawn()
10286             .unwrap();
10287 
10288         let r = std::panic::catch_unwind(|| {
10289             guest.wait_vm_boot(None).unwrap();
10290 
10291             // Make sure the source VM is functaionl
10292             // Check the number of vCPUs
10293             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
10294 
10295             // Check the guest RAM
10296             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
10297 
10298             // Check Landlock is enabled by hot-plugging a disk.
10299             assert!(!remote_command(
10300                 &src_api_socket,
10301                 "add-disk",
10302                 Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()),
10303             ));
10304 
10305             // Start the live-migration
10306             let migration_socket = String::from(
10307                 guest
10308                     .tmp_dir
10309                     .as_path()
10310                     .join("live-migration.sock")
10311                     .to_str()
10312                     .unwrap(),
10313             );
10314 
10315             assert!(
10316                 start_live_migration(&migration_socket, &src_api_socket, &dest_api_socket, true),
10317                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
10318             );
10319         });
10320 
10321         // Check and report any errors occurred during the live-migration
10322         if r.is_err() {
10323             print_and_panic(
10324                 src_child,
10325                 dest_child,
10326                 None,
10327                 "Error occurred during live-migration",
10328             );
10329         }
10330 
10331         // Check the source vm has been terminated successful (give it '3s' to settle)
10332         thread::sleep(std::time::Duration::new(3, 0));
10333         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
10334             print_and_panic(
10335                 src_child,
10336                 dest_child,
10337                 None,
10338                 "source VM was not terminated successfully.",
10339             );
10340         };
10341 
10342         // Post live-migration check to make sure the destination VM is funcational
10343         let r = std::panic::catch_unwind(|| {
10344             // Perform same checks to validate VM has been properly migrated
10345             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
10346             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
10347         });
10348 
10349         // Check Landlock is enabled on destination VM by hot-plugging a disk.
10350         assert!(!remote_command(
10351             &dest_api_socket,
10352             "add-disk",
10353             Some(format!("path={},id=test0", blk_file_path.to_str().unwrap()).as_str()),
10354         ));
10355 
10356         // Clean-up the destination VM and make sure it terminated correctly
10357         let _ = dest_child.kill();
10358         let dest_output = dest_child.wait_with_output().unwrap();
10359         handle_child_output(r, &dest_output);
10360     }
10361 
10362     // Function to get an available port
10363     fn get_available_port() -> u16 {
10364         TcpListener::bind("127.0.0.1:0")
10365             .expect("Failed to bind to address")
10366             .local_addr()
10367             .unwrap()
10368             .port()
10369     }
10370 
10371     fn start_live_migration_tcp(src_api_socket: &str, dest_api_socket: &str) -> bool {
10372         // Get an available TCP port
10373         let migration_port = get_available_port();
10374         let host_ip = "127.0.0.1";
10375 
10376         // Start the 'receive-migration' command on the destination
10377         let mut receive_migration = Command::new(clh_command("ch-remote"))
10378             .args([
10379                 &format!("--api-socket={dest_api_socket}"),
10380                 "receive-migration",
10381                 &format!("tcp:0.0.0.0:{migration_port}"),
10382             ])
10383             .stdin(Stdio::null())
10384             .stderr(Stdio::piped())
10385             .stdout(Stdio::piped())
10386             .spawn()
10387             .unwrap();
10388 
10389         // Give the destination some time to start listening
10390         thread::sleep(Duration::from_secs(1));
10391 
10392         // Start the 'send-migration' command on the source
10393         let mut send_migration = Command::new(clh_command("ch-remote"))
10394             .args([
10395                 &format!("--api-socket={src_api_socket}"),
10396                 "send-migration",
10397                 &format!("tcp:{host_ip}:{migration_port}"),
10398             ])
10399             .stdin(Stdio::null())
10400             .stderr(Stdio::piped())
10401             .stdout(Stdio::piped())
10402             .spawn()
10403             .unwrap();
10404 
10405         // Check if the 'send-migration' command executed successfully
10406         let send_success = if let Some(status) = send_migration
10407             .wait_timeout(Duration::from_secs(60))
10408             .unwrap()
10409         {
10410             status.success()
10411         } else {
10412             false
10413         };
10414 
10415         if !send_success {
10416             let _ = send_migration.kill();
10417             let output = send_migration.wait_with_output().unwrap();
10418             eprintln!(
10419                 "\n\n==== Start 'send_migration' output ====\n\n---stdout---\n{}\n\n---stderr---\n{}\n\n==== End 'send_migration' output ====\n\n",
10420                 String::from_utf8_lossy(&output.stdout),
10421                 String::from_utf8_lossy(&output.stderr)
10422             );
10423         }
10424 
10425         // Check if the 'receive-migration' command executed successfully
10426         let receive_success = if let Some(status) = receive_migration
10427             .wait_timeout(Duration::from_secs(60))
10428             .unwrap()
10429         {
10430             status.success()
10431         } else {
10432             false
10433         };
10434 
10435         if !receive_success {
10436             let _ = receive_migration.kill();
10437             let output = receive_migration.wait_with_output().unwrap();
10438             eprintln!(
10439                 "\n\n==== Start 'receive_migration' output ====\n\n---stdout---\n{}\n\n---stderr---\n{}\n\n==== End 'receive_migration' output ====\n\n",
10440                 String::from_utf8_lossy(&output.stdout),
10441                 String::from_utf8_lossy(&output.stderr)
10442             );
10443         }
10444 
10445         send_success && receive_success
10446     }
10447 
10448     fn _test_live_migration_tcp() {
10449         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10450         let guest = Guest::new(Box::new(focal));
10451         let kernel_path = direct_kernel_boot_path();
10452         let console_text = String::from("On a branch floating down river a cricket, singing.");
10453         let net_id = "net123";
10454         let net_params = format!(
10455             "id={},tap=,mac={},ip={},mask=255.255.255.0",
10456             net_id, guest.network.guest_mac, guest.network.host_ip
10457         );
10458         let memory_param: &[&str] = &["--memory", "size=4G,shared=on"];
10459         let boot_vcpus = 2;
10460         let max_vcpus = 4;
10461         let pmem_temp_file = TempFile::new().unwrap();
10462         pmem_temp_file.as_file().set_len(128 << 20).unwrap();
10463         std::process::Command::new("mkfs.ext4")
10464             .arg(pmem_temp_file.as_path())
10465             .output()
10466             .expect("Expect creating disk image to succeed");
10467         let pmem_path = String::from("/dev/pmem0");
10468 
10469         // Start the source VM
10470         let src_vm_path = clh_command("cloud-hypervisor");
10471         let src_api_socket = temp_api_path(&guest.tmp_dir);
10472         let mut src_vm_cmd = GuestCommand::new_with_binary_path(&guest, &src_vm_path);
10473         src_vm_cmd
10474             .args([
10475                 "--cpus",
10476                 format!("boot={boot_vcpus},max={max_vcpus}").as_str(),
10477             ])
10478             .args(memory_param)
10479             .args(["--kernel", kernel_path.to_str().unwrap()])
10480             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
10481             .default_disks()
10482             .args(["--net", net_params.as_str()])
10483             .args(["--api-socket", &src_api_socket])
10484             .args([
10485                 "--pmem",
10486                 format!(
10487                     "file={},discard_writes=on",
10488                     pmem_temp_file.as_path().to_str().unwrap(),
10489                 )
10490                 .as_str(),
10491             ])
10492             .capture_output();
10493         let mut src_child = src_vm_cmd.spawn().unwrap();
10494 
10495         // Start the destination VM
10496         let mut dest_api_socket = temp_api_path(&guest.tmp_dir);
10497         dest_api_socket.push_str(".dest");
10498         let mut dest_child = GuestCommand::new(&guest)
10499             .args(["--api-socket", &dest_api_socket])
10500             .capture_output()
10501             .spawn()
10502             .unwrap();
10503 
10504         let r = std::panic::catch_unwind(|| {
10505             guest.wait_vm_boot(None).unwrap();
10506             // Ensure the source VM is running normally
10507             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
10508             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
10509             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
10510 
10511             // On x86_64 architecture, remove and re-add the virtio-net device
10512             #[cfg(target_arch = "x86_64")]
10513             {
10514                 assert!(remote_command(
10515                     &src_api_socket,
10516                     "remove-device",
10517                     Some(net_id),
10518                 ));
10519                 thread::sleep(Duration::new(10, 0));
10520                 // Re-add the virtio-net device
10521                 assert!(remote_command(
10522                     &src_api_socket,
10523                     "add-net",
10524                     Some(net_params.as_str()),
10525                 ));
10526                 thread::sleep(Duration::new(10, 0));
10527             }
10528             // Start TCP live migration
10529             assert!(
10530                 start_live_migration_tcp(&src_api_socket, &dest_api_socket),
10531                 "Unsuccessful command: 'send-migration' or 'receive-migration'."
10532             );
10533         });
10534 
10535         // Check and report any errors that occurred during live migration
10536         if r.is_err() {
10537             print_and_panic(
10538                 src_child,
10539                 dest_child,
10540                 None,
10541                 "Error occurred during live-migration",
10542             );
10543         }
10544 
10545         // Check the source vm has been terminated successful (give it '3s' to settle)
10546         thread::sleep(std::time::Duration::new(3, 0));
10547         if !src_child.try_wait().unwrap().is_some_and(|s| s.success()) {
10548             print_and_panic(
10549                 src_child,
10550                 dest_child,
10551                 None,
10552                 "Source VM was not terminated successfully.",
10553             );
10554         };
10555 
10556         // After live migration, ensure the destination VM is running normally
10557         let r = std::panic::catch_unwind(|| {
10558             // Perform the same checks to ensure the VM has migrated correctly
10559             assert_eq!(guest.get_cpu_count().unwrap_or_default(), boot_vcpus);
10560             assert!(guest.get_total_memory().unwrap_or_default() > 3_840_000);
10561             guest.check_devices_common(None, Some(&console_text), Some(&pmem_path));
10562         });
10563 
10564         // Clean up the destination VM and ensure it terminates properly
10565         let _ = dest_child.kill();
10566         let dest_output = dest_child.wait_with_output().unwrap();
10567         handle_child_output(r, &dest_output);
10568 
10569         // Check if the expected `console_text` is present in the destination VM's output
10570         let r = std::panic::catch_unwind(|| {
10571             assert!(String::from_utf8_lossy(&dest_output.stdout).contains(&console_text));
10572         });
10573         handle_child_output(r, &dest_output);
10574     }
10575 
10576     mod live_migration_parallel {
10577         use super::*;
10578         #[test]
10579         fn test_live_migration_basic() {
10580             _test_live_migration(false, false)
10581         }
10582 
10583         #[test]
10584         fn test_live_migration_local() {
10585             _test_live_migration(false, true)
10586         }
10587 
10588         #[test]
10589         fn test_live_migration_tcp() {
10590             _test_live_migration_tcp();
10591         }
10592 
10593         #[test]
10594         fn test_live_migration_watchdog() {
10595             _test_live_migration_watchdog(false, false)
10596         }
10597 
10598         #[test]
10599         fn test_live_migration_watchdog_local() {
10600             _test_live_migration_watchdog(false, true)
10601         }
10602 
10603         #[test]
10604         fn test_live_upgrade_basic() {
10605             _test_live_migration(true, false)
10606         }
10607 
10608         #[test]
10609         fn test_live_upgrade_local() {
10610             _test_live_migration(true, true)
10611         }
10612 
10613         #[test]
10614         fn test_live_upgrade_watchdog() {
10615             _test_live_migration_watchdog(true, false)
10616         }
10617 
10618         #[test]
10619         fn test_live_upgrade_watchdog_local() {
10620             _test_live_migration_watchdog(true, true)
10621         }
10622         #[test]
10623         #[cfg(target_arch = "x86_64")]
10624         fn test_live_migration_with_landlock() {
10625             _test_live_migration_with_landlock()
10626         }
10627     }
10628 
10629     mod live_migration_sequential {
10630         use super::*;
10631 
10632         // NUMA & balloon live migration tests are large so run sequentially
10633 
10634         #[test]
10635         fn test_live_migration_balloon() {
10636             _test_live_migration_balloon(false, false)
10637         }
10638 
10639         #[test]
10640         fn test_live_migration_balloon_local() {
10641             _test_live_migration_balloon(false, true)
10642         }
10643 
10644         #[test]
10645         fn test_live_upgrade_balloon() {
10646             _test_live_migration_balloon(true, false)
10647         }
10648 
10649         #[test]
10650         fn test_live_upgrade_balloon_local() {
10651             _test_live_migration_balloon(true, true)
10652         }
10653 
10654         #[test]
10655         #[cfg(not(feature = "mshv"))]
10656         fn test_live_migration_numa() {
10657             _test_live_migration_numa(false, false)
10658         }
10659 
10660         #[test]
10661         #[cfg(not(feature = "mshv"))]
10662         fn test_live_migration_numa_local() {
10663             _test_live_migration_numa(false, true)
10664         }
10665 
10666         #[test]
10667         #[cfg(not(feature = "mshv"))]
10668         fn test_live_upgrade_numa() {
10669             _test_live_migration_numa(true, false)
10670         }
10671 
10672         #[test]
10673         #[cfg(not(feature = "mshv"))]
10674         fn test_live_upgrade_numa_local() {
10675             _test_live_migration_numa(true, true)
10676         }
10677 
10678         // Require to run ovs-dpdk tests sequentially because they rely on the same ovs-dpdk setup
10679         #[test]
10680         #[ignore = "See #5532"]
10681         #[cfg(target_arch = "x86_64")]
10682         #[cfg(not(feature = "mshv"))]
10683         fn test_live_migration_ovs_dpdk() {
10684             _test_live_migration_ovs_dpdk(false, false);
10685         }
10686 
10687         #[test]
10688         #[cfg(target_arch = "x86_64")]
10689         #[cfg(not(feature = "mshv"))]
10690         fn test_live_migration_ovs_dpdk_local() {
10691             _test_live_migration_ovs_dpdk(false, true);
10692         }
10693 
10694         #[test]
10695         #[ignore = "See #5532"]
10696         #[cfg(target_arch = "x86_64")]
10697         #[cfg(not(feature = "mshv"))]
10698         fn test_live_upgrade_ovs_dpdk() {
10699             _test_live_migration_ovs_dpdk(true, false);
10700         }
10701 
10702         #[test]
10703         #[ignore = "See #5532"]
10704         #[cfg(target_arch = "x86_64")]
10705         #[cfg(not(feature = "mshv"))]
10706         fn test_live_upgrade_ovs_dpdk_local() {
10707             _test_live_migration_ovs_dpdk(true, true);
10708         }
10709     }
10710 }
10711 
10712 #[cfg(target_arch = "aarch64")]
10713 mod aarch64_acpi {
10714     use crate::*;
10715 
10716     #[test]
10717     fn test_simple_launch_acpi() {
10718         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10719 
10720         vec![Box::new(focal)].drain(..).for_each(|disk_config| {
10721             let guest = Guest::new(disk_config);
10722 
10723             let mut child = GuestCommand::new(&guest)
10724                 .args(["--cpus", "boot=1"])
10725                 .args(["--memory", "size=512M"])
10726                 .args(["--kernel", edk2_path().to_str().unwrap()])
10727                 .default_disks()
10728                 .default_net()
10729                 .args(["--serial", "tty", "--console", "off"])
10730                 .capture_output()
10731                 .spawn()
10732                 .unwrap();
10733 
10734             let r = std::panic::catch_unwind(|| {
10735                 guest.wait_vm_boot(Some(120)).unwrap();
10736 
10737                 assert_eq!(guest.get_cpu_count().unwrap_or_default(), 1);
10738                 assert!(guest.get_total_memory().unwrap_or_default() > 400_000);
10739                 assert_eq!(guest.get_pci_bridge_class().unwrap_or_default(), "0x060000");
10740             });
10741 
10742             let _ = child.kill();
10743             let output = child.wait_with_output().unwrap();
10744 
10745             handle_child_output(r, &output);
10746         });
10747     }
10748 
10749     #[test]
10750     fn test_guest_numa_nodes_acpi() {
10751         _test_guest_numa_nodes(true);
10752     }
10753 
10754     #[test]
10755     fn test_cpu_topology_421_acpi() {
10756         test_cpu_topology(4, 2, 1, true);
10757     }
10758 
10759     #[test]
10760     fn test_cpu_topology_142_acpi() {
10761         test_cpu_topology(1, 4, 2, true);
10762     }
10763 
10764     #[test]
10765     fn test_cpu_topology_262_acpi() {
10766         test_cpu_topology(2, 6, 2, true);
10767     }
10768 
10769     #[test]
10770     fn test_power_button_acpi() {
10771         _test_power_button(true);
10772     }
10773 
10774     #[test]
10775     fn test_virtio_iommu() {
10776         _test_virtio_iommu(true)
10777     }
10778 }
10779 
10780 mod rate_limiter {
10781     use super::*;
10782 
10783     // Check if the 'measured' rate is within the expected 'difference' (in percentage)
10784     // compared to given 'limit' rate.
10785     fn check_rate_limit(measured: f64, limit: f64, difference: f64) -> bool {
10786         let upper_limit = limit * (1_f64 + difference);
10787         let lower_limit = limit * (1_f64 - difference);
10788 
10789         if measured > lower_limit && measured < upper_limit {
10790             return true;
10791         }
10792 
10793         eprintln!(
10794             "\n\n==== Start 'check_rate_limit' failed ==== \
10795             \n\nmeasured={measured}, , lower_limit={lower_limit}, upper_limit={upper_limit} \
10796             \n\n==== End 'check_rate_limit' failed ====\n\n"
10797         );
10798 
10799         false
10800     }
10801 
10802     fn _test_rate_limiter_net(rx: bool) {
10803         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10804         let guest = Guest::new(Box::new(focal));
10805 
10806         let test_timeout = 10;
10807         let num_queues = 2;
10808         let queue_size = 256;
10809         let bw_size = 10485760_u64; // bytes
10810         let bw_refill_time = 100; // ms
10811         let limit_bps = (bw_size * 8 * 1000) as f64 / bw_refill_time as f64;
10812 
10813         let net_params = format!(
10814             "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={},bw_size={},bw_refill_time={}",
10815             guest.network.guest_mac,
10816             guest.network.host_ip,
10817             num_queues,
10818             queue_size,
10819             bw_size,
10820             bw_refill_time,
10821         );
10822 
10823         let mut child = GuestCommand::new(&guest)
10824             .args(["--cpus", &format!("boot={}", num_queues / 2)])
10825             .args(["--memory", "size=4G"])
10826             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
10827             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
10828             .default_disks()
10829             .args(["--net", net_params.as_str()])
10830             .capture_output()
10831             .spawn()
10832             .unwrap();
10833 
10834         let r = std::panic::catch_unwind(|| {
10835             guest.wait_vm_boot(None).unwrap();
10836             let measured_bps =
10837                 measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, true)
10838                     .unwrap();
10839             assert!(check_rate_limit(measured_bps, limit_bps, 0.1));
10840         });
10841 
10842         let _ = child.kill();
10843         let output = child.wait_with_output().unwrap();
10844         handle_child_output(r, &output);
10845     }
10846 
10847     #[test]
10848     fn test_rate_limiter_net_rx() {
10849         _test_rate_limiter_net(true);
10850     }
10851 
10852     #[test]
10853     fn test_rate_limiter_net_tx() {
10854         _test_rate_limiter_net(false);
10855     }
10856 
10857     fn _test_rate_limiter_block(bandwidth: bool, num_queues: u32) {
10858         let test_timeout = 10;
10859         let fio_ops = FioOps::RandRW;
10860 
10861         let bw_size = if bandwidth {
10862             10485760_u64 // bytes
10863         } else {
10864             100_u64 // I/O
10865         };
10866         let bw_refill_time = 100; // ms
10867         let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64;
10868 
10869         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10870         let guest = Guest::new(Box::new(focal));
10871         let api_socket = temp_api_path(&guest.tmp_dir);
10872         let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap();
10873         let blk_rate_limiter_test_img =
10874             String::from(test_img_dir.as_path().join("blk.img").to_str().unwrap());
10875 
10876         // Create the test block image
10877         assert!(exec_host_command_output(&format!(
10878             "dd if=/dev/zero of={blk_rate_limiter_test_img} bs=1M count=1024"
10879         ))
10880         .status
10881         .success());
10882 
10883         let test_blk_params = if bandwidth {
10884             format!(
10885                 "path={blk_rate_limiter_test_img},num_queues={num_queues},bw_size={bw_size},bw_refill_time={bw_refill_time}"
10886             )
10887         } else {
10888             format!(
10889                 "path={blk_rate_limiter_test_img},num_queues={num_queues},ops_size={bw_size},ops_refill_time={bw_refill_time}"
10890             )
10891         };
10892 
10893         let mut child = GuestCommand::new(&guest)
10894             .args(["--cpus", &format!("boot={num_queues}")])
10895             .args(["--memory", "size=4G"])
10896             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
10897             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
10898             .args([
10899                 "--disk",
10900                 format!(
10901                     "path={}",
10902                     guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
10903                 )
10904                 .as_str(),
10905                 format!(
10906                     "path={}",
10907                     guest.disk_config.disk(DiskType::CloudInit).unwrap()
10908                 )
10909                 .as_str(),
10910                 test_blk_params.as_str(),
10911             ])
10912             .default_net()
10913             .args(["--api-socket", &api_socket])
10914             .capture_output()
10915             .spawn()
10916             .unwrap();
10917 
10918         let r = std::panic::catch_unwind(|| {
10919             guest.wait_vm_boot(None).unwrap();
10920 
10921             let fio_command = format!(
10922                 "sudo fio --filename=/dev/vdc --name=test --output-format=json \
10923                 --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \
10924                 --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}"
10925             );
10926             let output = guest.ssh_command(&fio_command).unwrap();
10927 
10928             // Parse fio output
10929             let measured_rate = if bandwidth {
10930                 parse_fio_output(&output, &fio_ops, num_queues).unwrap()
10931             } else {
10932                 parse_fio_output_iops(&output, &fio_ops, num_queues).unwrap()
10933             };
10934             assert!(check_rate_limit(measured_rate, limit_rate, 0.1));
10935         });
10936 
10937         let _ = child.kill();
10938         let output = child.wait_with_output().unwrap();
10939         handle_child_output(r, &output);
10940     }
10941 
10942     fn _test_rate_limiter_group_block(bandwidth: bool, num_queues: u32, num_disks: u32) {
10943         let test_timeout = 10;
10944         let fio_ops = FioOps::RandRW;
10945 
10946         let bw_size = if bandwidth {
10947             10485760_u64 // bytes
10948         } else {
10949             100_u64 // I/O
10950         };
10951         let bw_refill_time = 100; // ms
10952         let limit_rate = (bw_size * 1000) as f64 / bw_refill_time as f64;
10953 
10954         let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
10955         let guest = Guest::new(Box::new(focal));
10956         let api_socket = temp_api_path(&guest.tmp_dir);
10957         let test_img_dir = TempDir::new_with_prefix("/var/tmp/ch").unwrap();
10958 
10959         let rate_limit_group_arg = if bandwidth {
10960             format!("id=group0,bw_size={bw_size},bw_refill_time={bw_refill_time}")
10961         } else {
10962             format!("id=group0,ops_size={bw_size},ops_refill_time={bw_refill_time}")
10963         };
10964 
10965         let mut disk_args = vec![
10966             "--disk".to_string(),
10967             format!(
10968                 "path={}",
10969                 guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
10970             ),
10971             format!(
10972                 "path={}",
10973                 guest.disk_config.disk(DiskType::CloudInit).unwrap()
10974             ),
10975         ];
10976 
10977         for i in 0..num_disks {
10978             let test_img_path = String::from(
10979                 test_img_dir
10980                     .as_path()
10981                     .join(format!("blk{i}.img"))
10982                     .to_str()
10983                     .unwrap(),
10984             );
10985 
10986             assert!(exec_host_command_output(&format!(
10987                 "dd if=/dev/zero of={test_img_path} bs=1M count=1024"
10988             ))
10989             .status
10990             .success());
10991 
10992             disk_args.push(format!(
10993                 "path={test_img_path},num_queues={num_queues},rate_limit_group=group0"
10994             ));
10995         }
10996 
10997         let mut child = GuestCommand::new(&guest)
10998             .args(["--cpus", &format!("boot={}", num_queues * num_disks)])
10999             .args(["--memory", "size=4G"])
11000             .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
11001             .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
11002             .args(["--rate-limit-group", &rate_limit_group_arg])
11003             .args(disk_args)
11004             .default_net()
11005             .args(["--api-socket", &api_socket])
11006             .capture_output()
11007             .spawn()
11008             .unwrap();
11009 
11010         let r = std::panic::catch_unwind(|| {
11011             guest.wait_vm_boot(None).unwrap();
11012 
11013             let mut fio_command = format!(
11014                 "sudo fio --name=global --output-format=json \
11015                 --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \
11016                 --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}"
11017             );
11018 
11019             // Generate additional argument for each disk:
11020             // --name=job0 --filename=/dev/vdc \
11021             // --name=job1 --filename=/dev/vdd \
11022             // --name=job2 --filename=/dev/vde \
11023             // ...
11024             for i in 0..num_disks {
11025                 let c: char = 'c';
11026                 let arg = format!(
11027                     " --name=job{i} --filename=/dev/vd{}",
11028                     char::from_u32((c as u32) + i).unwrap()
11029                 );
11030                 fio_command += &arg;
11031             }
11032             let output = guest.ssh_command(&fio_command).unwrap();
11033 
11034             // Parse fio output
11035             let measured_rate = if bandwidth {
11036                 parse_fio_output(&output, &fio_ops, num_queues * num_disks).unwrap()
11037             } else {
11038                 parse_fio_output_iops(&output, &fio_ops, num_queues * num_disks).unwrap()
11039             };
11040             assert!(check_rate_limit(measured_rate, limit_rate, 0.2));
11041         });
11042 
11043         let _ = child.kill();
11044         let output = child.wait_with_output().unwrap();
11045         handle_child_output(r, &output);
11046     }
11047 
11048     #[test]
11049     fn test_rate_limiter_block_bandwidth() {
11050         _test_rate_limiter_block(true, 1);
11051         _test_rate_limiter_block(true, 2)
11052     }
11053 
11054     #[test]
11055     fn test_rate_limiter_group_block_bandwidth() {
11056         _test_rate_limiter_group_block(true, 1, 1);
11057         _test_rate_limiter_group_block(true, 2, 1);
11058         _test_rate_limiter_group_block(true, 1, 2);
11059         _test_rate_limiter_group_block(true, 2, 2);
11060     }
11061 
11062     #[test]
11063     fn test_rate_limiter_block_iops() {
11064         _test_rate_limiter_block(false, 1);
11065         _test_rate_limiter_block(false, 2);
11066     }
11067 
11068     #[test]
11069     fn test_rate_limiter_group_block_iops() {
11070         _test_rate_limiter_group_block(false, 1, 1);
11071         _test_rate_limiter_group_block(false, 2, 1);
11072         _test_rate_limiter_group_block(false, 1, 2);
11073         _test_rate_limiter_group_block(false, 2, 2);
11074     }
11075 }
11076