1 // Copyright 2023 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 //! Testing vmm-swap
6
7 #![cfg(any(target_os = "android", target_os = "linux"))]
8
9 use std::time::Duration;
10 use std::time::Instant;
11
12 use anyhow::bail;
13 use base::test_utils::call_test_with_sudo;
14 use fixture::vm::Config;
15 use fixture::vm::TestVm;
16 use swap::SwapState;
17 use swap::SwapStatus;
18
19 const SWAP_STATE_CHANGE_TIMEOUT: Duration = Duration::from_secs(2);
20 const SWAP_FILE_PATH: &str = "/tmp/swap_test";
21
get_swap_state(vm: &mut TestVm) -> SwapState22 fn get_swap_state(vm: &mut TestVm) -> SwapState {
23 let output = vm.swap_command("status").unwrap();
24 let status = serde_json::from_slice::<SwapStatus>(&output).unwrap();
25 status.state
26 }
27
wait_until_swap_state_change( vm: &mut TestVm, state: SwapState, transition_state: &[SwapState], timeout: Duration, ) -> anyhow::Result<()>28 fn wait_until_swap_state_change(
29 vm: &mut TestVm,
30 state: SwapState,
31 transition_state: &[SwapState],
32 timeout: Duration,
33 ) -> anyhow::Result<()> {
34 let start = Instant::now();
35 loop {
36 let current_state = get_swap_state(vm);
37 if current_state == state {
38 return Ok(());
39 }
40 if start.elapsed() > timeout {
41 bail!(
42 "state change timeout: current: {:?}, target: {:?}",
43 current_state,
44 state
45 );
46 }
47 if !transition_state.contains(¤t_state) {
48 bail!(
49 "unexpected state while waiting: current: {:?}, target: {:?}",
50 current_state,
51 state
52 );
53 }
54 std::thread::sleep(Duration::from_millis(100));
55 }
56 }
57
create_tmpfs_file_in_guest(vm: &mut TestVm, size: usize)58 fn create_tmpfs_file_in_guest(vm: &mut TestVm, size: usize) {
59 vm.exec_in_guest("mount -t tmpfs -o size=64m /dev/shm /tmp")
60 .unwrap();
61 vm.exec_in_guest(&format!(
62 "head -c {} /dev/urandom > {}",
63 size, SWAP_FILE_PATH
64 ))
65 .unwrap();
66 }
67
load_checksum_tmpfs_file(vm: &mut TestVm) -> String68 fn load_checksum_tmpfs_file(vm: &mut TestVm) -> String {
69 // Use checksum to validate that the RAM on the guest is not broken. Sending the whole content
70 // does not work due to the protocol of the connection between host and guest.
71 vm.exec_in_guest(&format!("cat {} | sha256sum", SWAP_FILE_PATH))
72 .unwrap()
73 .stdout
74 }
75
76 #[ignore = "Only to be called by swap_enabled"]
77 #[test]
swap_enabled_impl()78 fn swap_enabled_impl() {
79 let mut config = Config::new();
80 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
81 let mut vm = TestVm::new_sudo(config).unwrap();
82
83 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
84 vm.swap_command("enable").unwrap();
85 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
86 vm.swap_command("trim").unwrap();
87 wait_until_swap_state_change(
88 &mut vm,
89 SwapState::Pending,
90 &[SwapState::TrimInProgress],
91 SWAP_STATE_CHANGE_TIMEOUT,
92 )
93 .unwrap();
94 vm.swap_command("out").unwrap();
95 wait_until_swap_state_change(
96 &mut vm,
97 SwapState::Active,
98 &[SwapState::SwapOutInProgress],
99 SWAP_STATE_CHANGE_TIMEOUT,
100 )
101 .unwrap();
102 vm.swap_command("disable").unwrap();
103 wait_until_swap_state_change(
104 &mut vm,
105 SwapState::Ready,
106 &[SwapState::SwapInInProgress],
107 SWAP_STATE_CHANGE_TIMEOUT,
108 )
109 .unwrap();
110 }
111
112 #[test]
swap_enabled()113 fn swap_enabled() {
114 call_test_with_sudo("swap_enabled_impl");
115 }
116
117 #[ignore = "Only to be called by swap_out_multiple_times"]
118 #[test]
swap_out_multiple_times_impl()119 fn swap_out_multiple_times_impl() {
120 let mut config = Config::new();
121 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
122 let mut vm = TestVm::new_sudo(config).unwrap();
123
124 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
125 vm.swap_command("enable").unwrap();
126 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
127 vm.swap_command("trim").unwrap();
128 wait_until_swap_state_change(
129 &mut vm,
130 SwapState::Pending,
131 &[SwapState::TrimInProgress],
132 SWAP_STATE_CHANGE_TIMEOUT,
133 )
134 .unwrap();
135 vm.swap_command("out").unwrap();
136 wait_until_swap_state_change(
137 &mut vm,
138 SwapState::Active,
139 &[SwapState::SwapOutInProgress],
140 SWAP_STATE_CHANGE_TIMEOUT,
141 )
142 .unwrap();
143 vm.swap_command("enable").unwrap();
144 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
145 vm.swap_command("trim").unwrap();
146 wait_until_swap_state_change(
147 &mut vm,
148 SwapState::Pending,
149 &[SwapState::TrimInProgress],
150 SWAP_STATE_CHANGE_TIMEOUT,
151 )
152 .unwrap();
153 vm.swap_command("out").unwrap();
154 wait_until_swap_state_change(
155 &mut vm,
156 SwapState::Active,
157 &[SwapState::SwapOutInProgress],
158 SWAP_STATE_CHANGE_TIMEOUT,
159 )
160 .unwrap();
161 vm.swap_command("enable").unwrap();
162 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
163 vm.swap_command("disable").unwrap();
164 wait_until_swap_state_change(
165 &mut vm,
166 SwapState::Ready,
167 &[SwapState::SwapInInProgress],
168 SWAP_STATE_CHANGE_TIMEOUT,
169 )
170 .unwrap();
171 }
172
173 #[test]
swap_out_multiple_times()174 fn swap_out_multiple_times() {
175 call_test_with_sudo("swap_out_multiple_times_impl");
176 }
177
178 #[ignore = "Only to be called by swap_disabled_without_swapped_out"]
179 #[test]
swap_disabled_without_swapped_out_impl()180 fn swap_disabled_without_swapped_out_impl() {
181 let mut config = Config::new();
182 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
183 let mut vm = TestVm::new_sudo(config).unwrap();
184
185 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
186 vm.swap_command("enable").unwrap();
187 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
188 vm.swap_command("disable").unwrap();
189 wait_until_swap_state_change(
190 &mut vm,
191 SwapState::Ready,
192 &[SwapState::SwapInInProgress],
193 SWAP_STATE_CHANGE_TIMEOUT,
194 )
195 .unwrap();
196 }
197
198 #[test]
swap_disabled_without_swapped_out()199 fn swap_disabled_without_swapped_out() {
200 call_test_with_sudo("swap_disabled_without_swapped_out_impl");
201 }
202
203 #[ignore = "Only to be called by stopped_with_swap_enabled"]
204 #[test]
stopped_with_swap_enabled_impl()205 fn stopped_with_swap_enabled_impl() {
206 let mut config = Config::new();
207 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
208 let mut vm = TestVm::new_sudo(config).unwrap();
209
210 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
211 vm.swap_command("enable").unwrap();
212 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
213 vm.swap_command("trim").unwrap();
214 wait_until_swap_state_change(
215 &mut vm,
216 SwapState::Pending,
217 &[SwapState::TrimInProgress],
218 SWAP_STATE_CHANGE_TIMEOUT,
219 )
220 .unwrap();
221 vm.swap_command("out").unwrap();
222 wait_until_swap_state_change(
223 &mut vm,
224 SwapState::Active,
225 &[SwapState::SwapOutInProgress],
226 SWAP_STATE_CHANGE_TIMEOUT,
227 )
228 .unwrap();
229 // dropping TestVm sends crosvm stop command and wait until the process exits.
230 }
231
232 #[test]
stopped_with_swap_enabled()233 fn stopped_with_swap_enabled() {
234 call_test_with_sudo("stopped_with_swap_enabled_impl");
235 }
236
237 #[ignore = "Only to be called by memory_contents_preserved_while_vmm_swap_enabled"]
238 #[test]
memory_contents_preserved_while_vmm_swap_enabled_impl()239 fn memory_contents_preserved_while_vmm_swap_enabled_impl() {
240 let mut config = Config::new();
241 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
242 let mut vm = TestVm::new_sudo(config).unwrap();
243 create_tmpfs_file_in_guest(&mut vm, 1024 * 1024);
244 let checksum = load_checksum_tmpfs_file(&mut vm);
245
246 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
247 vm.swap_command("enable").unwrap();
248 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
249
250 assert_eq!(load_checksum_tmpfs_file(&mut vm), checksum);
251 }
252
253 #[test]
memory_contents_preserved_while_vmm_swap_enabled()254 fn memory_contents_preserved_while_vmm_swap_enabled() {
255 call_test_with_sudo("memory_contents_preserved_while_vmm_swap_enabled_impl");
256 }
257
258 #[ignore = "Only to be called by memory_contents_preserved_after_vmm_swap_out"]
259 #[test]
memory_contents_preserved_after_vmm_swap_out_impl()260 fn memory_contents_preserved_after_vmm_swap_out_impl() {
261 let mut config = Config::new();
262 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
263 let mut vm = TestVm::new_sudo(config).unwrap();
264 create_tmpfs_file_in_guest(&mut vm, 1024 * 1024);
265 let checksum = load_checksum_tmpfs_file(&mut vm);
266
267 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
268 vm.swap_command("enable").unwrap();
269 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
270 vm.swap_command("trim").unwrap();
271 wait_until_swap_state_change(
272 &mut vm,
273 SwapState::Pending,
274 &[SwapState::TrimInProgress],
275 SWAP_STATE_CHANGE_TIMEOUT,
276 )
277 .unwrap();
278 vm.swap_command("out").unwrap();
279 wait_until_swap_state_change(
280 &mut vm,
281 SwapState::Active,
282 &[SwapState::SwapOutInProgress],
283 SWAP_STATE_CHANGE_TIMEOUT,
284 )
285 .unwrap();
286
287 assert_eq!(load_checksum_tmpfs_file(&mut vm), checksum);
288 }
289
290 #[test]
memory_contents_preserved_after_vmm_swap_out()291 fn memory_contents_preserved_after_vmm_swap_out() {
292 call_test_with_sudo("memory_contents_preserved_after_vmm_swap_out_impl");
293 }
294
295 #[ignore = "Only to be called by memory_contents_preserved_after_vmm_swap_disabled"]
296 #[test]
memory_contents_preserved_after_vmm_swap_disabled_impl()297 fn memory_contents_preserved_after_vmm_swap_disabled_impl() {
298 let mut config = Config::new();
299 config = config.extra_args(vec!["--swap".to_string(), ".".to_string()]);
300 let mut vm = TestVm::new_sudo(config).unwrap();
301 create_tmpfs_file_in_guest(&mut vm, 1024 * 1024);
302 let checksum = load_checksum_tmpfs_file(&mut vm);
303
304 assert_eq!(get_swap_state(&mut vm), SwapState::Ready);
305 vm.swap_command("enable").unwrap();
306 assert_eq!(get_swap_state(&mut vm), SwapState::Pending);
307 vm.swap_command("trim").unwrap();
308 wait_until_swap_state_change(
309 &mut vm,
310 SwapState::Pending,
311 &[SwapState::TrimInProgress],
312 SWAP_STATE_CHANGE_TIMEOUT,
313 )
314 .unwrap();
315 vm.swap_command("out").unwrap();
316 wait_until_swap_state_change(
317 &mut vm,
318 SwapState::Active,
319 &[SwapState::SwapOutInProgress],
320 SWAP_STATE_CHANGE_TIMEOUT,
321 )
322 .unwrap();
323 vm.swap_command("disable").unwrap();
324 wait_until_swap_state_change(
325 &mut vm,
326 SwapState::Ready,
327 &[SwapState::SwapInInProgress],
328 SWAP_STATE_CHANGE_TIMEOUT,
329 )
330 .unwrap();
331
332 assert_eq!(load_checksum_tmpfs_file(&mut vm), checksum);
333 }
334
335 #[test]
memory_contents_preserved_after_vmm_swap_disabled()336 fn memory_contents_preserved_after_vmm_swap_disabled() {
337 call_test_with_sudo("memory_contents_preserved_after_vmm_swap_disabled_impl");
338 }
339