1*bb4ee6a4SAndroid Build Coastguard Worker // Copyright 2022 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker // found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker
5*bb4ee6a4SAndroid Build Coastguard Worker #![cfg(any(target_os = "android", target_os = "linux"))]
6*bb4ee6a4SAndroid Build Coastguard Worker
7*bb4ee6a4SAndroid Build Coastguard Worker use std::io::Write;
8*bb4ee6a4SAndroid Build Coastguard Worker use std::path::Path;
9*bb4ee6a4SAndroid Build Coastguard Worker
10*bb4ee6a4SAndroid Build Coastguard Worker use base::test_utils::call_test_with_sudo;
11*bb4ee6a4SAndroid Build Coastguard Worker use fixture::utils::create_vu_block_config;
12*bb4ee6a4SAndroid Build Coastguard Worker use fixture::utils::prepare_disk_img;
13*bb4ee6a4SAndroid Build Coastguard Worker use fixture::vhost_user::CmdType;
14*bb4ee6a4SAndroid Build Coastguard Worker use fixture::vhost_user::Config as VuConfig;
15*bb4ee6a4SAndroid Build Coastguard Worker use fixture::vhost_user::VhostUserBackend;
16*bb4ee6a4SAndroid Build Coastguard Worker use fixture::vm::Config;
17*bb4ee6a4SAndroid Build Coastguard Worker use fixture::vm::TestVm;
18*bb4ee6a4SAndroid Build Coastguard Worker use tempfile::tempdir;
19*bb4ee6a4SAndroid Build Coastguard Worker use tempfile::NamedTempFile;
20*bb4ee6a4SAndroid Build Coastguard Worker
21*bb4ee6a4SAndroid Build Coastguard Worker // Tests for suspend/resume.
22*bb4ee6a4SAndroid Build Coastguard Worker //
23*bb4ee6a4SAndroid Build Coastguard Worker // System-wide suspend/resume, snapshot/restore.
24*bb4ee6a4SAndroid Build Coastguard Worker // Tests below check for snapshot/restore functionality, and suspend/resume.
25*bb4ee6a4SAndroid Build Coastguard Worker
compare_snapshots(a: &Path, b: &Path) -> (bool, String)26*bb4ee6a4SAndroid Build Coastguard Worker fn compare_snapshots(a: &Path, b: &Path) -> (bool, String) {
27*bb4ee6a4SAndroid Build Coastguard Worker let result = std::process::Command::new("diff")
28*bb4ee6a4SAndroid Build Coastguard Worker .arg("-qr")
29*bb4ee6a4SAndroid Build Coastguard Worker // vcpu and irqchip have timestamps that differ even if a freshly restored VM is
30*bb4ee6a4SAndroid Build Coastguard Worker // snapshotted before it starts running again.
31*bb4ee6a4SAndroid Build Coastguard Worker .arg("--exclude")
32*bb4ee6a4SAndroid Build Coastguard Worker .arg("vcpu*")
33*bb4ee6a4SAndroid Build Coastguard Worker .arg("--exclude")
34*bb4ee6a4SAndroid Build Coastguard Worker .arg("irqchip")
35*bb4ee6a4SAndroid Build Coastguard Worker // KVM's pvclock seems to advance some even if the vCPUs haven't started yet.
36*bb4ee6a4SAndroid Build Coastguard Worker .arg("--exclude")
37*bb4ee6a4SAndroid Build Coastguard Worker .arg("pvclock")
38*bb4ee6a4SAndroid Build Coastguard Worker .arg(a)
39*bb4ee6a4SAndroid Build Coastguard Worker .arg(b)
40*bb4ee6a4SAndroid Build Coastguard Worker .output()
41*bb4ee6a4SAndroid Build Coastguard Worker .unwrap();
42*bb4ee6a4SAndroid Build Coastguard Worker (
43*bb4ee6a4SAndroid Build Coastguard Worker result.status.success(),
44*bb4ee6a4SAndroid Build Coastguard Worker String::from_utf8(result.stdout).unwrap(),
45*bb4ee6a4SAndroid Build Coastguard Worker )
46*bb4ee6a4SAndroid Build Coastguard Worker }
47*bb4ee6a4SAndroid Build Coastguard Worker
48*bb4ee6a4SAndroid Build Coastguard Worker #[test]
suspend_snapshot_restore_resume() -> anyhow::Result<()>49*bb4ee6a4SAndroid Build Coastguard Worker fn suspend_snapshot_restore_resume() -> anyhow::Result<()> {
50*bb4ee6a4SAndroid Build Coastguard Worker suspend_resume_system(false)
51*bb4ee6a4SAndroid Build Coastguard Worker }
52*bb4ee6a4SAndroid Build Coastguard Worker
53*bb4ee6a4SAndroid Build Coastguard Worker #[test]
suspend_snapshot_restore_resume_disable_sandbox() -> anyhow::Result<()>54*bb4ee6a4SAndroid Build Coastguard Worker fn suspend_snapshot_restore_resume_disable_sandbox() -> anyhow::Result<()> {
55*bb4ee6a4SAndroid Build Coastguard Worker suspend_resume_system(true)
56*bb4ee6a4SAndroid Build Coastguard Worker }
57*bb4ee6a4SAndroid Build Coastguard Worker
suspend_resume_system(disabled_sandbox: bool) -> anyhow::Result<()>58*bb4ee6a4SAndroid Build Coastguard Worker fn suspend_resume_system(disabled_sandbox: bool) -> anyhow::Result<()> {
59*bb4ee6a4SAndroid Build Coastguard Worker let mut pmem_file = NamedTempFile::new().unwrap();
60*bb4ee6a4SAndroid Build Coastguard Worker pmem_file.write_all(&[0; 4096]).unwrap();
61*bb4ee6a4SAndroid Build Coastguard Worker pmem_file.as_file_mut().sync_all().unwrap();
62*bb4ee6a4SAndroid Build Coastguard Worker
63*bb4ee6a4SAndroid Build Coastguard Worker let new_config = || {
64*bb4ee6a4SAndroid Build Coastguard Worker let mut config = Config::new();
65*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_stdout_hardware("legacy-virtio-console");
66*bb4ee6a4SAndroid Build Coastguard Worker config = config.extra_args(vec![
67*bb4ee6a4SAndroid Build Coastguard Worker "--pmem".to_string(),
68*bb4ee6a4SAndroid Build Coastguard Worker format!("{},ro=true", pmem_file.path().display().to_string()),
69*bb4ee6a4SAndroid Build Coastguard Worker ]);
70*bb4ee6a4SAndroid Build Coastguard Worker // TODO: Remove once USB has snapshot/restore support.
71*bb4ee6a4SAndroid Build Coastguard Worker config = config.extra_args(vec!["--no-usb".to_string()]);
72*bb4ee6a4SAndroid Build Coastguard Worker if disabled_sandbox {
73*bb4ee6a4SAndroid Build Coastguard Worker config = config.disable_sandbox();
74*bb4ee6a4SAndroid Build Coastguard Worker }
75*bb4ee6a4SAndroid Build Coastguard Worker config
76*bb4ee6a4SAndroid Build Coastguard Worker };
77*bb4ee6a4SAndroid Build Coastguard Worker
78*bb4ee6a4SAndroid Build Coastguard Worker let mut vm = TestVm::new(new_config()).unwrap();
79*bb4ee6a4SAndroid Build Coastguard Worker
80*bb4ee6a4SAndroid Build Coastguard Worker // Verify RAM is saved and restored by interacting with a filesystem pinned in RAM (i.e. tmpfs
81*bb4ee6a4SAndroid Build Coastguard Worker // with swap disabled).
82*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("swapoff -a").unwrap();
83*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("mount -t tmpfs none /tmp").unwrap();
84*bb4ee6a4SAndroid Build Coastguard Worker
85*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("echo foo > /tmp/foo").unwrap();
86*bb4ee6a4SAndroid Build Coastguard Worker assert_eq!(
87*bb4ee6a4SAndroid Build Coastguard Worker "foo",
88*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("cat /tmp/foo").unwrap().stdout.trim()
89*bb4ee6a4SAndroid Build Coastguard Worker );
90*bb4ee6a4SAndroid Build Coastguard Worker
91*bb4ee6a4SAndroid Build Coastguard Worker vm.suspend_full().unwrap();
92*bb4ee6a4SAndroid Build Coastguard Worker // Take snapshot of original VM state
93*bb4ee6a4SAndroid Build Coastguard Worker println!("snapshotting VM - clean state");
94*bb4ee6a4SAndroid Build Coastguard Worker let dir = tempdir().unwrap();
95*bb4ee6a4SAndroid Build Coastguard Worker let snap1_path = dir.path().join("snapshot.bkp");
96*bb4ee6a4SAndroid Build Coastguard Worker vm.snapshot(&snap1_path).unwrap();
97*bb4ee6a4SAndroid Build Coastguard Worker vm.resume_full().unwrap();
98*bb4ee6a4SAndroid Build Coastguard Worker
99*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("echo bar > /tmp/foo").unwrap();
100*bb4ee6a4SAndroid Build Coastguard Worker assert_eq!(
101*bb4ee6a4SAndroid Build Coastguard Worker "bar",
102*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("cat /tmp/foo").unwrap().stdout.trim()
103*bb4ee6a4SAndroid Build Coastguard Worker );
104*bb4ee6a4SAndroid Build Coastguard Worker
105*bb4ee6a4SAndroid Build Coastguard Worker vm.suspend_full().unwrap();
106*bb4ee6a4SAndroid Build Coastguard Worker let snap2_path = dir.path().join("snapshot2.bkp");
107*bb4ee6a4SAndroid Build Coastguard Worker
108*bb4ee6a4SAndroid Build Coastguard Worker // Write command to VM
109*bb4ee6a4SAndroid Build Coastguard Worker // This command will get queued and not run while the VM is suspended. The command is saved in
110*bb4ee6a4SAndroid Build Coastguard Worker // the serial device. After the snapshot is taken, the VM is resumed. At that point, the
111*bb4ee6a4SAndroid Build Coastguard Worker // command runs and is validated.
112*bb4ee6a4SAndroid Build Coastguard Worker let echo_cmd = vm.exec_in_guest_async("echo 42").unwrap();
113*bb4ee6a4SAndroid Build Coastguard Worker // Take snapshot of modified VM
114*bb4ee6a4SAndroid Build Coastguard Worker println!("snapshotting VM - mod state");
115*bb4ee6a4SAndroid Build Coastguard Worker vm.snapshot(&snap2_path).unwrap();
116*bb4ee6a4SAndroid Build Coastguard Worker
117*bb4ee6a4SAndroid Build Coastguard Worker vm.resume_full().unwrap();
118*bb4ee6a4SAndroid Build Coastguard Worker assert_eq!("42", echo_cmd.wait_ok(&mut vm).unwrap().stdout.trim());
119*bb4ee6a4SAndroid Build Coastguard Worker
120*bb4ee6a4SAndroid Build Coastguard Worker println!("restoring VM - to clean state");
121*bb4ee6a4SAndroid Build Coastguard Worker
122*bb4ee6a4SAndroid Build Coastguard Worker // shut down VM
123*bb4ee6a4SAndroid Build Coastguard Worker drop(vm);
124*bb4ee6a4SAndroid Build Coastguard Worker // Start up VM with cold restore.
125*bb4ee6a4SAndroid Build Coastguard Worker let mut vm = TestVm::new_restore_suspended(new_config().extra_args(vec![
126*bb4ee6a4SAndroid Build Coastguard Worker "--restore".to_string(),
127*bb4ee6a4SAndroid Build Coastguard Worker snap1_path.to_str().unwrap().to_string(),
128*bb4ee6a4SAndroid Build Coastguard Worker "--suspended".to_string(),
129*bb4ee6a4SAndroid Build Coastguard Worker ]))
130*bb4ee6a4SAndroid Build Coastguard Worker .unwrap();
131*bb4ee6a4SAndroid Build Coastguard Worker
132*bb4ee6a4SAndroid Build Coastguard Worker // snapshot VM after restore
133*bb4ee6a4SAndroid Build Coastguard Worker println!("snapshotting VM - clean state restored");
134*bb4ee6a4SAndroid Build Coastguard Worker let snap3_path = dir.path().join("snapshot3.bkp");
135*bb4ee6a4SAndroid Build Coastguard Worker vm.snapshot(&snap3_path).unwrap();
136*bb4ee6a4SAndroid Build Coastguard Worker vm.resume_full().unwrap();
137*bb4ee6a4SAndroid Build Coastguard Worker
138*bb4ee6a4SAndroid Build Coastguard Worker assert_eq!(
139*bb4ee6a4SAndroid Build Coastguard Worker "foo",
140*bb4ee6a4SAndroid Build Coastguard Worker vm.exec_in_guest("cat /tmp/foo").unwrap().stdout.trim()
141*bb4ee6a4SAndroid Build Coastguard Worker );
142*bb4ee6a4SAndroid Build Coastguard Worker
143*bb4ee6a4SAndroid Build Coastguard Worker let (equal, output) = compare_snapshots(&snap1_path, &snap2_path);
144*bb4ee6a4SAndroid Build Coastguard Worker assert!(
145*bb4ee6a4SAndroid Build Coastguard Worker !equal,
146*bb4ee6a4SAndroid Build Coastguard Worker "1st and 2nd snapshot are unexpectedly equal:\n{output}"
147*bb4ee6a4SAndroid Build Coastguard Worker );
148*bb4ee6a4SAndroid Build Coastguard Worker
149*bb4ee6a4SAndroid Build Coastguard Worker let (equal, output) = compare_snapshots(&snap1_path, &snap3_path);
150*bb4ee6a4SAndroid Build Coastguard Worker assert!(equal, "1st and 3rd snapshot are not equal:\n{output}");
151*bb4ee6a4SAndroid Build Coastguard Worker
152*bb4ee6a4SAndroid Build Coastguard Worker Ok(())
153*bb4ee6a4SAndroid Build Coastguard Worker }
154*bb4ee6a4SAndroid Build Coastguard Worker
155*bb4ee6a4SAndroid Build Coastguard Worker #[test]
snapshot_vhost_user_root()156*bb4ee6a4SAndroid Build Coastguard Worker fn snapshot_vhost_user_root() {
157*bb4ee6a4SAndroid Build Coastguard Worker call_test_with_sudo("snapshot_vhost_user")
158*bb4ee6a4SAndroid Build Coastguard Worker }
159*bb4ee6a4SAndroid Build Coastguard Worker
160*bb4ee6a4SAndroid Build Coastguard Worker // This test will fail/hang if ran by its self.
161*bb4ee6a4SAndroid Build Coastguard Worker #[ignore = "Only to be called by snapshot_vhost_user_root"]
162*bb4ee6a4SAndroid Build Coastguard Worker #[test]
snapshot_vhost_user()163*bb4ee6a4SAndroid Build Coastguard Worker fn snapshot_vhost_user() {
164*bb4ee6a4SAndroid Build Coastguard Worker fn spin_up_vhost_user_devices() -> (
165*bb4ee6a4SAndroid Build Coastguard Worker VhostUserBackend,
166*bb4ee6a4SAndroid Build Coastguard Worker VhostUserBackend,
167*bb4ee6a4SAndroid Build Coastguard Worker NamedTempFile,
168*bb4ee6a4SAndroid Build Coastguard Worker NamedTempFile,
169*bb4ee6a4SAndroid Build Coastguard Worker ) {
170*bb4ee6a4SAndroid Build Coastguard Worker let block_socket = NamedTempFile::new().unwrap();
171*bb4ee6a4SAndroid Build Coastguard Worker let disk = prepare_disk_img();
172*bb4ee6a4SAndroid Build Coastguard Worker
173*bb4ee6a4SAndroid Build Coastguard Worker // Spin up block vhost user process
174*bb4ee6a4SAndroid Build Coastguard Worker let block_vu_config =
175*bb4ee6a4SAndroid Build Coastguard Worker create_vu_block_config(CmdType::Device, block_socket.path(), disk.path());
176*bb4ee6a4SAndroid Build Coastguard Worker let block_vu_device = VhostUserBackend::new(block_vu_config).unwrap();
177*bb4ee6a4SAndroid Build Coastguard Worker
178*bb4ee6a4SAndroid Build Coastguard Worker // Spin up net vhost user process.
179*bb4ee6a4SAndroid Build Coastguard Worker // Queue handlers don't get activated currently.
180*bb4ee6a4SAndroid Build Coastguard Worker let net_socket = NamedTempFile::new().unwrap();
181*bb4ee6a4SAndroid Build Coastguard Worker let net_config = create_net_config(net_socket.path());
182*bb4ee6a4SAndroid Build Coastguard Worker let net_vu_device = VhostUserBackend::new(net_config).unwrap();
183*bb4ee6a4SAndroid Build Coastguard Worker
184*bb4ee6a4SAndroid Build Coastguard Worker (block_vu_device, net_vu_device, block_socket, net_socket)
185*bb4ee6a4SAndroid Build Coastguard Worker }
186*bb4ee6a4SAndroid Build Coastguard Worker
187*bb4ee6a4SAndroid Build Coastguard Worker let dir = tempdir().unwrap();
188*bb4ee6a4SAndroid Build Coastguard Worker let snap_path = dir.path().join("snapshot.bkp");
189*bb4ee6a4SAndroid Build Coastguard Worker
190*bb4ee6a4SAndroid Build Coastguard Worker {
191*bb4ee6a4SAndroid Build Coastguard Worker let (_block_vu_device, _net_vu_device, block_socket, net_socket) =
192*bb4ee6a4SAndroid Build Coastguard Worker spin_up_vhost_user_devices();
193*bb4ee6a4SAndroid Build Coastguard Worker
194*bb4ee6a4SAndroid Build Coastguard Worker let mut config = Config::new();
195*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_stdout_hardware("legacy-virtio-console");
196*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_vhost_user("block", block_socket.path());
197*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_vhost_user("net", net_socket.path());
198*bb4ee6a4SAndroid Build Coastguard Worker config = config.extra_args(vec!["--no-usb".to_string()]);
199*bb4ee6a4SAndroid Build Coastguard Worker let mut vm = TestVm::new(config).unwrap();
200*bb4ee6a4SAndroid Build Coastguard Worker
201*bb4ee6a4SAndroid Build Coastguard Worker // suspend VM
202*bb4ee6a4SAndroid Build Coastguard Worker vm.suspend_full().unwrap();
203*bb4ee6a4SAndroid Build Coastguard Worker vm.snapshot(&snap_path).unwrap();
204*bb4ee6a4SAndroid Build Coastguard Worker drop(vm);
205*bb4ee6a4SAndroid Build Coastguard Worker }
206*bb4ee6a4SAndroid Build Coastguard Worker
207*bb4ee6a4SAndroid Build Coastguard Worker let (_block_vu_device, _net_vu_device, block_socket, net_socket) = spin_up_vhost_user_devices();
208*bb4ee6a4SAndroid Build Coastguard Worker
209*bb4ee6a4SAndroid Build Coastguard Worker let mut config = Config::new();
210*bb4ee6a4SAndroid Build Coastguard Worker // Start up VM with cold restore.
211*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_stdout_hardware("legacy-virtio-console");
212*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_vhost_user("block", block_socket.path());
213*bb4ee6a4SAndroid Build Coastguard Worker config = config.with_vhost_user("net", net_socket.path());
214*bb4ee6a4SAndroid Build Coastguard Worker config = config.extra_args(vec![
215*bb4ee6a4SAndroid Build Coastguard Worker "--restore".to_string(),
216*bb4ee6a4SAndroid Build Coastguard Worker snap_path.to_str().unwrap().to_string(),
217*bb4ee6a4SAndroid Build Coastguard Worker "--no-usb".to_string(),
218*bb4ee6a4SAndroid Build Coastguard Worker ]);
219*bb4ee6a4SAndroid Build Coastguard Worker let _vm = TestVm::new_restore(config).unwrap();
220*bb4ee6a4SAndroid Build Coastguard Worker }
221*bb4ee6a4SAndroid Build Coastguard Worker
create_net_config(socket: &Path) -> VuConfig222*bb4ee6a4SAndroid Build Coastguard Worker fn create_net_config(socket: &Path) -> VuConfig {
223*bb4ee6a4SAndroid Build Coastguard Worker let socket_path = socket.to_str().unwrap();
224*bb4ee6a4SAndroid Build Coastguard Worker println!("socket={socket_path}");
225*bb4ee6a4SAndroid Build Coastguard Worker VuConfig::new(CmdType::Device, "net").extra_args(vec![
226*bb4ee6a4SAndroid Build Coastguard Worker "net".to_string(),
227*bb4ee6a4SAndroid Build Coastguard Worker "--device".to_string(),
228*bb4ee6a4SAndroid Build Coastguard Worker format!(
229*bb4ee6a4SAndroid Build Coastguard Worker "{},{},{},{}",
230*bb4ee6a4SAndroid Build Coastguard Worker socket_path, "192.168.10.1", "255.255.255.0", "12:34:56:78:9a:bc"
231*bb4ee6a4SAndroid Build Coastguard Worker ),
232*bb4ee6a4SAndroid Build Coastguard Worker ])
233*bb4ee6a4SAndroid Build Coastguard Worker }
234