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