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