Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/src/vmm/tests/integration_tests.rs
1958 views
1
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
// SPDX-License-Identifier: Apache-2.0
3
use std::io;
4
use std::io::{Seek, SeekFrom};
5
use std::thread;
6
use std::time::Duration;
7
8
use snapshot::Snapshot;
9
use utils::tempfile::TempFile;
10
use vmm::builder::{build_microvm_for_boot, build_microvm_from_snapshot, setup_serial_device};
11
use vmm::persist::{self, snapshot_state_sanity_check, LoadSnapshotError, MicrovmState};
12
use vmm::resources::VmResources;
13
use vmm::seccomp_filters::{get_filters, SeccompConfig};
14
use vmm::version_map::VERSION_MAP;
15
use vmm::vmm_config::snapshot::{CreateSnapshotParams, SnapshotType};
16
use vmm::{EventManager, FC_EXIT_CODE_OK};
17
18
use vmm::utilities::mock_devices::MockSerialInput;
19
use vmm::utilities::mock_resources::{MockVmResources, NOISY_KERNEL_IMAGE};
20
#[cfg(target_arch = "x86_64")]
21
use vmm::utilities::test_utils::dirty_tracking_vmm;
22
use vmm::utilities::test_utils::{create_vmm, default_vmm};
23
use vmm::vmm_config::instance_info::InstanceInfo;
24
25
#[test]
26
fn test_setup_serial_device() {
27
let read_tempfile = TempFile::new().unwrap();
28
let read_handle = MockSerialInput(read_tempfile.into_file());
29
let mut event_manager = EventManager::new().unwrap();
30
31
assert!(setup_serial_device(
32
&mut event_manager,
33
Box::new(read_handle),
34
Box::new(io::stdout()),
35
)
36
.is_ok());
37
}
38
39
#[test]
40
fn test_build_microvm() {
41
// Error case: no boot source configured.
42
{
43
let resources: VmResources = MockVmResources::new().into();
44
let mut event_manager = EventManager::new().unwrap();
45
let mut empty_seccomp_filters = get_filters(SeccompConfig::None).unwrap();
46
47
let vmm_ret = build_microvm_for_boot(
48
&InstanceInfo::default(),
49
&resources,
50
&mut event_manager,
51
&mut empty_seccomp_filters,
52
);
53
assert_eq!(format!("{:?}", vmm_ret.err()), "Some(MissingKernelConfig)");
54
}
55
56
// Success case.
57
let (vmm, mut _evmgr) = default_vmm(None);
58
59
// On x86_64, the vmm should exit once its workload completes and signals the exit event.
60
// On aarch64, the test kernel doesn't exit, so the vmm is force-stopped.
61
#[cfg(target_arch = "x86_64")]
62
_evmgr.run_with_timeout(500).unwrap();
63
#[cfg(target_arch = "aarch64")]
64
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
65
66
assert_eq!(
67
vmm.lock().unwrap().shutdown_exit_code(),
68
Some(FC_EXIT_CODE_OK)
69
);
70
}
71
72
#[test]
73
fn test_pause_resume_microvm() {
74
// Tests that pausing and resuming a microVM work as expected.
75
let (vmm, _) = default_vmm(None);
76
77
// There's a race between this thread and the vcpu thread, but this thread
78
// should be able to pause vcpu thread before it finishes running its test-binary.
79
assert!(vmm.lock().unwrap().pause_vm().is_ok());
80
// Pausing again the microVM should not fail (microVM remains in the
81
// `Paused` state).
82
assert!(vmm.lock().unwrap().pause_vm().is_ok());
83
assert!(vmm.lock().unwrap().resume_vm().is_ok());
84
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
85
}
86
87
#[test]
88
fn test_dirty_bitmap_error() {
89
// Error case: dirty tracking disabled.
90
let (vmm, _) = default_vmm(None);
91
92
// The vmm will start with dirty page tracking = OFF.
93
// With dirty tracking disabled, the underlying KVM_GET_DIRTY_LOG ioctl will fail
94
// with errno 2 (ENOENT) because KVM can't find any guest memory regions with dirty
95
// page tracking enabled.
96
assert_eq!(
97
format!("{:?}", vmm.lock().unwrap().get_dirty_bitmap().err()),
98
"Some(DirtyBitmap(Error(2)))"
99
);
100
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
101
}
102
103
#[test]
104
#[cfg(target_arch = "x86_64")]
105
fn test_dirty_bitmap_success() {
106
// The vmm will start with dirty page tracking = ON.
107
let (vmm, _) = dirty_tracking_vmm(Some(NOISY_KERNEL_IMAGE));
108
109
// Let it churn for a while and dirty some pages...
110
thread::sleep(Duration::from_millis(100));
111
let bitmap = vmm.lock().unwrap().get_dirty_bitmap().unwrap();
112
let num_dirty_pages: u32 = bitmap
113
.iter()
114
.map(|(_, bitmap_per_region)| {
115
// Gently coerce to u32
116
let num_dirty_pages_per_region: u32 =
117
bitmap_per_region.iter().map(|n| n.count_ones()).sum();
118
num_dirty_pages_per_region
119
})
120
.sum();
121
assert!(num_dirty_pages > 0);
122
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
123
}
124
125
#[test]
126
fn test_disallow_snapshots_without_pausing() {
127
let (vmm, _) = default_vmm(Some(NOISY_KERNEL_IMAGE));
128
129
// Verify saving state while running is not allowed.
130
// Can't do unwrap_err() because MicrovmState doesn't impl Debug.
131
match vmm.lock().unwrap().save_state() {
132
Err(e) => assert!(format!("{:?}", e).contains("NotAllowed")),
133
Ok(_) => panic!("Should not be allowed."),
134
};
135
136
// Pause microVM.
137
vmm.lock().unwrap().pause_vm().unwrap();
138
// It is now allowed.
139
vmm.lock().unwrap().save_state().unwrap();
140
// Stop.
141
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
142
}
143
144
fn verify_create_snapshot(is_diff: bool) -> (TempFile, TempFile) {
145
let snapshot_file = TempFile::new().unwrap();
146
let memory_file = TempFile::new().unwrap();
147
148
let (vmm, _) = create_vmm(Some(NOISY_KERNEL_IMAGE), is_diff);
149
150
// Be sure that the microVM is running.
151
thread::sleep(Duration::from_millis(200));
152
153
// Pause microVM.
154
vmm.lock().unwrap().pause_vm().unwrap();
155
156
// Create snapshot.
157
let snapshot_type = match is_diff {
158
true => SnapshotType::Diff,
159
false => SnapshotType::Full,
160
};
161
let snapshot_params = CreateSnapshotParams {
162
snapshot_type,
163
snapshot_path: snapshot_file.as_path().to_path_buf(),
164
mem_file_path: memory_file.as_path().to_path_buf(),
165
version: Some(String::from("0.24.0")),
166
};
167
168
{
169
let mut locked_vmm = vmm.lock().unwrap();
170
persist::create_snapshot(&mut locked_vmm, &snapshot_params, VERSION_MAP.clone()).unwrap();
171
}
172
173
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
174
175
// Check that we can deserialize the microVM state from `snapshot_file`.
176
let snapshot_path = snapshot_file.as_path().to_path_buf();
177
let snapshot_file_metadata = std::fs::metadata(snapshot_path).unwrap();
178
let snapshot_len = snapshot_file_metadata.len() as usize;
179
let restored_microvm_state: MicrovmState = Snapshot::load(
180
&mut snapshot_file.as_file(),
181
snapshot_len,
182
VERSION_MAP.clone(),
183
)
184
.unwrap();
185
186
// Check memory file size.
187
let memory_file_size_mib = memory_file.as_file().metadata().unwrap().len() >> 20;
188
assert_eq!(
189
restored_microvm_state.vm_info.mem_size_mib,
190
memory_file_size_mib
191
);
192
193
// Verify deserialized data.
194
// The default vmm has no devices and one vCPU.
195
assert_eq!(restored_microvm_state.device_states.block_devices.len(), 0);
196
assert_eq!(restored_microvm_state.device_states.net_devices.len(), 0);
197
assert!(restored_microvm_state.device_states.vsock_device.is_none());
198
assert_eq!(restored_microvm_state.vcpu_states.len(), 1);
199
200
(snapshot_file, memory_file)
201
}
202
203
fn verify_load_snapshot(snapshot_file: TempFile, memory_file: TempFile) {
204
use vm_memory::GuestMemoryMmap;
205
use vmm::memory_snapshot::SnapshotMemory;
206
207
let mut event_manager = EventManager::new().unwrap();
208
let mut empty_seccomp_filters = get_filters(SeccompConfig::None).unwrap();
209
210
// Deserialize microVM state.
211
let snapshot_file_metadata = snapshot_file.as_file().metadata().unwrap();
212
let snapshot_len = snapshot_file_metadata.len() as usize;
213
snapshot_file.as_file().seek(SeekFrom::Start(0)).unwrap();
214
let microvm_state: MicrovmState = Snapshot::load(
215
&mut snapshot_file.as_file(),
216
snapshot_len,
217
VERSION_MAP.clone(),
218
)
219
.unwrap();
220
let mem = GuestMemoryMmap::restore(memory_file.as_file(), &microvm_state.memory_state, false)
221
.unwrap();
222
223
// Build microVM from state.
224
let vmm = build_microvm_from_snapshot(
225
&InstanceInfo::default(),
226
&mut event_manager,
227
microvm_state,
228
mem,
229
false,
230
&mut empty_seccomp_filters,
231
)
232
.unwrap();
233
// For now we're happy we got this far, we don't test what the guest is actually doing.
234
vmm.lock().unwrap().stop(FC_EXIT_CODE_OK);
235
}
236
237
#[test]
238
fn test_create_and_load_snapshot() {
239
// Create diff snapshot.
240
let (snapshot_file, memory_file) = verify_create_snapshot(true);
241
// Create a new microVm from snapshot. This only tests code-level logic; it verifies
242
// that a microVM can be built with no errors from given snapshot.
243
// It does _not_ verify that the guest is actually restored properly. We're using
244
// python integration tests for that.
245
verify_load_snapshot(snapshot_file, memory_file);
246
247
// Create full snapshot.
248
let (snapshot_file, memory_file) = verify_create_snapshot(false);
249
// Create a new microVm from snapshot. This only tests code-level logic; it verifies
250
// that a microVM can be built with no errors from given snapshot.
251
// It does _not_ verify that the guest is actually restored properly. We're using
252
// python integration tests for that.
253
verify_load_snapshot(snapshot_file, memory_file);
254
}
255
256
#[test]
257
fn test_snapshot_load_sanity_checks() {
258
use vmm::vmm_config::machine_config::MAX_SUPPORTED_VCPUS;
259
260
let mut microvm_state = get_microvm_state_from_snapshot();
261
262
assert!(snapshot_state_sanity_check(&microvm_state).is_ok());
263
264
// Remove memory regions.
265
microvm_state.memory_state.regions.clear();
266
267
// Validate sanity checks fail because there is no mem region in state.
268
let err = snapshot_state_sanity_check(&microvm_state).unwrap_err();
269
match err {
270
LoadSnapshotError::InvalidSnapshot(err_msg) => {
271
assert_eq!(err_msg, "No memory region defined.")
272
}
273
_ => unreachable!(),
274
}
275
276
// Create MAX_SUPPORTED_VCPUS vCPUs starting from 1 vCPU.
277
for _ in 0..(MAX_SUPPORTED_VCPUS as f64).log2() as usize {
278
microvm_state
279
.vcpu_states
280
.append(&mut microvm_state.vcpu_states.clone());
281
}
282
283
// After this line we will have 33 vCPUs, FC max si 32.
284
microvm_state
285
.vcpu_states
286
.push(microvm_state.vcpu_states[0].clone());
287
288
// Validate sanity checks fail because there are too many vCPUs.
289
let err = snapshot_state_sanity_check(&microvm_state).unwrap_err();
290
match err {
291
LoadSnapshotError::InvalidSnapshot(err_msg) => assert_eq!(err_msg, "Invalid vCPU count."),
292
_ => unreachable!(),
293
}
294
295
// Remove all vCPUs states from microvm state.
296
microvm_state.vcpu_states.clear();
297
298
// Validate sanity checks fail because there is no vCPU in state.
299
let err = snapshot_state_sanity_check(&microvm_state).unwrap_err();
300
match err {
301
LoadSnapshotError::InvalidSnapshot(err_msg) => assert_eq!(err_msg, "Invalid vCPU count."),
302
_ => unreachable!(),
303
}
304
}
305
306
fn get_microvm_state_from_snapshot() -> MicrovmState {
307
// Create a diff snapshot
308
let (snapshot_file, _) = verify_create_snapshot(true);
309
310
// Deserialize the microVM state.
311
let snapshot_file_metadata = snapshot_file.as_file().metadata().unwrap();
312
let snapshot_len = snapshot_file_metadata.len() as usize;
313
snapshot_file.as_file().seek(SeekFrom::Start(0)).unwrap();
314
Snapshot::load(
315
&mut snapshot_file.as_file(),
316
snapshot_len,
317
VERSION_MAP.clone(),
318
)
319
.unwrap()
320
}
321
322
#[cfg(target_arch = "x86_64")]
323
#[test]
324
fn test_snapshot_cpu_vendor() {
325
use vmm::persist::validate_cpu_vendor;
326
let microvm_state = get_microvm_state_from_snapshot();
327
328
// Check if the snapshot created above passes validation since
329
// the snapshot was created locally.
330
assert!(validate_cpu_vendor(&microvm_state).is_ok());
331
}
332
333
#[cfg(target_arch = "x86_64")]
334
#[test]
335
fn test_snapshot_cpu_vendor_mismatch() {
336
use vmm::persist::validate_cpu_vendor;
337
let mut microvm_state = get_microvm_state_from_snapshot();
338
339
// Check if the snapshot created above passes validation since
340
// the snapshot was created locally.
341
assert!(validate_cpu_vendor(&microvm_state).is_ok());
342
343
// Modify the vendor id in CPUID.
344
for entry in microvm_state.vcpu_states[0].cpuid.as_mut_slice().iter_mut() {
345
if entry.function == 0 && entry.index == 0 {
346
// Fail if vendor id is NULL as this needs furhter investigation.
347
assert_ne!(entry.ebx, 0);
348
assert_ne!(entry.ecx, 0);
349
assert_ne!(entry.edx, 0);
350
entry.ebx = 0;
351
break;
352
}
353
}
354
355
// This must fail as the cpu vendor has been mangled.
356
assert!(validate_cpu_vendor(&microvm_state).is_err());
357
358
// Negative test: remove the vendor id from cpuid.
359
for entry in microvm_state.vcpu_states[0].cpuid.as_mut_slice().iter_mut() {
360
if entry.function == 0 && entry.index == 0 {
361
entry.function = 1234;
362
}
363
}
364
365
// This must fail as the cpu vendor has been mangled.
366
assert!(validate_cpu_vendor(&microvm_state).is_err());
367
}
368
369
#[cfg(target_arch = "x86_64")]
370
#[test]
371
fn test_snapshot_cpu_vendor_missing() {
372
use vmm::persist::validate_cpu_vendor;
373
let mut microvm_state = get_microvm_state_from_snapshot();
374
375
// Check if the snapshot created above passes validation since
376
// the snapshot was created locally.
377
assert!(validate_cpu_vendor(&microvm_state).is_ok());
378
379
// Negative test: remove the vendor id from cpuid.
380
for entry in microvm_state.vcpu_states[0].cpuid.as_mut_slice().iter_mut() {
381
if entry.function == 0 && entry.index == 0 {
382
entry.function = 1234;
383
}
384
}
385
386
// This must fail as the cpu vendor entry does not exist.
387
assert!(validate_cpu_vendor(&microvm_state).is_err());
388
}
389
390
#[cfg(target_arch = "aarch64")]
391
#[test]
392
fn test_snapshot_cpu_vendor() {
393
use vmm::persist::validate_cpu_manufacturer_id;
394
395
let microvm_state = get_microvm_state_from_snapshot();
396
397
// Check if the snapshot created above passes validation since
398
// the snapshot was created locally.
399
assert!(validate_cpu_manufacturer_id(&microvm_state).is_ok());
400
}
401
402
#[cfg(target_arch = "aarch64")]
403
#[test]
404
fn test_snapshot_cpu_vendor_missing() {
405
use arch::regs::MIDR_EL1;
406
use vmm::persist::validate_cpu_manufacturer_id;
407
408
let mut microvm_state = get_microvm_state_from_snapshot();
409
410
// Check if the snapshot created above passes validation since
411
// the snapshot was created locally.
412
assert!(validate_cpu_manufacturer_id(&microvm_state).is_ok());
413
414
// Remove the MIDR_EL1 value from the VCPU states, by setting it to 0
415
for state in microvm_state.vcpu_states.as_mut_slice().iter_mut() {
416
for reg in state.regs.as_mut_slice().iter_mut() {
417
if reg.id == MIDR_EL1 {
418
reg.id = 0;
419
}
420
}
421
}
422
assert!(validate_cpu_manufacturer_id(&microvm_state).is_err());
423
}
424
425
#[cfg(target_arch = "aarch64")]
426
#[test]
427
fn test_snapshot_cpu_vendor_mismatch() {
428
use arch::regs::MIDR_EL1;
429
use vmm::persist::validate_cpu_manufacturer_id;
430
431
let mut microvm_state = get_microvm_state_from_snapshot();
432
433
// Check if the snapshot created above passes validation since
434
// the snapshot was created locally.
435
assert!(validate_cpu_manufacturer_id(&microvm_state).is_ok());
436
437
// Change the MIDR_EL1 value from the VCPU states, to contain an
438
// invalid manufacturer ID
439
for state in microvm_state.vcpu_states.as_mut_slice().iter_mut() {
440
for reg in state.regs.as_mut_slice().iter_mut() {
441
if reg.id == MIDR_EL1 {
442
reg.addr = 0x710FD081;
443
}
444
}
445
}
446
assert!(validate_cpu_manufacturer_id(&microvm_state).is_err());
447
}
448
449