1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Integration test for VM bootloader.
16
17 use android_system_virtualizationservice::{
18 aidl::android::system::virtualizationservice::{
19 CpuTopology::CpuTopology, DiskImage::DiskImage, VirtualMachineConfig::VirtualMachineConfig,
20 VirtualMachineRawConfig::VirtualMachineRawConfig,
21 },
22 binder::{ParcelFileDescriptor, ProcessState},
23 };
24 use anyhow::{Context, Error};
25 use log::info;
26 use std::{
27 collections::{HashSet, VecDeque},
28 fs::File,
29 io::{self, BufRead, BufReader, Read, Write},
30 panic, thread,
31 };
32 use vmclient::{DeathReason, VmInstance};
33
34 const VMBASE_EXAMPLE_PATH: &str =
35 "/data/local/tmp/vmbase_example.integration_test/arm64/vmbase_example.bin";
36 const TEST_DISK_IMAGE_PATH: &str = "/data/local/tmp/vmbase_example.integration_test/test_disk.img";
37 const EMPTY_DISK_IMAGE_PATH: &str =
38 "/data/local/tmp/vmbase_example.integration_test/empty_disk.img";
39
40 /// Runs the vmbase_example VM as an unprotected VM via VirtualizationService.
41 #[test]
test_run_example_vm() -> Result<(), Error>42 fn test_run_example_vm() -> Result<(), Error> {
43 android_logger::init_once(
44 android_logger::Config::default()
45 .with_tag("vmbase")
46 .with_max_level(log::LevelFilter::Debug),
47 );
48
49 // Redirect panic messages to logcat.
50 panic::set_hook(Box::new(|panic_info| {
51 log::error!("{}", panic_info);
52 }));
53
54 // We need to start the thread pool for Binder to work properly, especially link_to_death.
55 ProcessState::start_thread_pool();
56
57 let virtmgr =
58 vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
59 let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
60
61 // Start example VM.
62 let bootloader = ParcelFileDescriptor::new(
63 File::open(VMBASE_EXAMPLE_PATH)
64 .with_context(|| format!("Failed to open VM image {}", VMBASE_EXAMPLE_PATH))?,
65 );
66
67 // Make file for test disk image.
68 let mut test_image = File::options()
69 .create(true)
70 .read(true)
71 .write(true)
72 .truncate(true)
73 .open(TEST_DISK_IMAGE_PATH)
74 .with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
75 // Write 4 sectors worth of 4-byte numbers counting up.
76 for i in 0u32..512 {
77 test_image.write_all(&i.to_le_bytes())?;
78 }
79 let test_image = ParcelFileDescriptor::new(test_image);
80 let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
81
82 // Make file for empty test disk image.
83 let empty_image = File::options()
84 .create(true)
85 .read(true)
86 .write(true)
87 .truncate(true)
88 .open(EMPTY_DISK_IMAGE_PATH)
89 .with_context(|| format!("Failed to open empty disk image {}", EMPTY_DISK_IMAGE_PATH))?;
90 let empty_image = ParcelFileDescriptor::new(empty_image);
91 let empty_disk_image =
92 DiskImage { image: Some(empty_image), writable: false, partitions: vec![] };
93
94 let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
95 name: String::from("VmBaseTest"),
96 kernel: None,
97 initrd: None,
98 params: None,
99 bootloader: Some(bootloader),
100 disks: vec![disk_image, empty_disk_image],
101 protectedVm: false,
102 memoryMib: 300,
103 cpuTopology: CpuTopology::ONE_CPU,
104 platformVersion: "~1.0".to_string(),
105 gdbPort: 0, // no gdb
106 ..Default::default()
107 });
108 let (handle, console) = android_log_fd()?;
109 let (mut log_reader, log_writer) = pipe()?;
110 let vm = VmInstance::create(
111 service.as_ref(),
112 &config,
113 Some(console),
114 /* consoleIn */ None,
115 Some(log_writer),
116 None,
117 )
118 .context("Failed to create VM")?;
119 vm.start().context("Failed to start VM")?;
120 info!("Started example VM.");
121
122 // Wait for VM to finish, and check that it shut down cleanly.
123 let death_reason = vm.wait_for_death();
124 assert_eq!(death_reason, DeathReason::Shutdown);
125 handle.join().unwrap();
126
127 // Check that the expected string was written to the log VirtIO console device.
128 let expected = "Hello VirtIO console\n";
129 let mut log_output = String::new();
130 assert_eq!(log_reader.read_to_string(&mut log_output)?, expected.len());
131 assert_eq!(log_output, expected);
132
133 Ok(())
134 }
135
android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error>136 fn android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error> {
137 let (reader, writer) = pipe()?;
138 let handle = thread::spawn(|| VmLogProcessor::new(reader).run().unwrap());
139 Ok((handle, writer))
140 }
141
pipe() -> io::Result<(File, File)>142 fn pipe() -> io::Result<(File, File)> {
143 let (reader_fd, writer_fd) = nix::unistd::pipe()?;
144 Ok((reader_fd.into(), writer_fd.into()))
145 }
146
147 struct VmLogProcessor {
148 reader: Option<File>,
149 expected: VecDeque<String>,
150 unexpected: HashSet<String>,
151 had_unexpected: bool,
152 }
153
154 impl VmLogProcessor {
messages() -> (VecDeque<String>, HashSet<String>)155 fn messages() -> (VecDeque<String>, HashSet<String>) {
156 let mut expected = VecDeque::new();
157 let mut unexpected = HashSet::new();
158 for log_lvl in ["[ERROR]", "[WARN]", "[INFO]", "[DEBUG]"] {
159 expected.push_back(format!("{log_lvl} Unsuppressed message"));
160 unexpected.insert(format!("{log_lvl} Suppressed message"));
161 }
162 (expected, unexpected)
163 }
164
new(reader: File) -> Self165 fn new(reader: File) -> Self {
166 let (expected, unexpected) = Self::messages();
167 Self { reader: Some(reader), expected, unexpected, had_unexpected: false }
168 }
169
verify(&mut self, msg: &str)170 fn verify(&mut self, msg: &str) {
171 if self.expected.front() == Some(&msg.to_owned()) {
172 self.expected.pop_front();
173 }
174 if !self.had_unexpected && self.unexpected.contains(msg) {
175 self.had_unexpected = true;
176 }
177 }
178
run(mut self) -> Result<(), &'static str>179 fn run(mut self) -> Result<(), &'static str> {
180 for line in BufReader::new(self.reader.take().unwrap()).lines() {
181 let msg = line.unwrap();
182 info!("{msg}");
183 self.verify(&msg);
184 }
185 if !self.expected.is_empty() {
186 Err("missing expected log message")
187 } else if self.had_unexpected {
188 Err("unexpected log message")
189 } else {
190 Ok(())
191 }
192 }
193 }
194