blob: 81812cdbc938e39dd9638aeb6c7ce7042bf0e340 [file] [log] [blame]
Andrew Walbran94bbf2f2022-05-12 18:35:42 +00001// 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
17use android_system_virtualizationservice::{
18 aidl::android::system::virtualizationservice::{
Elie Kheirallahb4b2f242025-01-23 03:38:07 +000019 CpuOptions::CpuOptions, CpuOptions::CpuTopology::CpuTopology, DiskImage::DiskImage,
20 VirtualMachineConfig::VirtualMachineConfig,
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000021 VirtualMachineRawConfig::VirtualMachineRawConfig,
22 },
23 binder::{ParcelFileDescriptor, ProcessState},
24};
25use anyhow::{Context, Error};
26use log::info;
27use std::{
Jakob Vukalovicef996292023-04-13 14:28:34 +000028 collections::{HashSet, VecDeque},
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000029 fs::File,
Andrew Walbran8d05dae2023-03-22 16:42:55 +000030 io::{self, BufRead, BufReader, Read, Write},
Frederick Mayled1c5a052024-12-16 15:42:59 -080031 thread,
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000032};
33use vmclient::{DeathReason, VmInstance};
34
Pierre-Clément Tosi48ac6a92024-08-14 00:42:05 +010035const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
Pierre-Clément Tosie0d68402024-08-14 00:27:47 +010036const VMBASE_EXAMPLE_BIOS_PATH: &str = "vmbase_example_bios.bin";
Nikita Putikhincf9c24e2024-07-16 12:42:14 +020037const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
38const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000039
Pierre-Clément Tosi48ac6a92024-08-14 00:42:05 +010040/// Runs the vmbase_example VM as an unprotected VM kernel via VirtualizationService.
41#[test]
42fn test_run_example_kernel_vm() -> Result<(), Error> {
43 run_test(Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?), None)
44}
45
Pierre-Clément Tosie0d68402024-08-14 00:27:47 +010046/// Runs the vmbase_example VM as an unprotected VM BIOS via VirtualizationService.
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000047#[test]
Pierre-Clément Tosie0d68402024-08-14 00:27:47 +010048fn test_run_example_bios_vm() -> Result<(), Error> {
Pierre-Clément Tosi48ac6a92024-08-14 00:42:05 +010049 run_test(None, Some(open_payload(VMBASE_EXAMPLE_BIOS_PATH)?))
Pierre-Clément Tosie0d68402024-08-14 00:27:47 +010050}
51
Pierre-Clément Tosi48ac6a92024-08-14 00:42:05 +010052fn run_test(
53 kernel: Option<ParcelFileDescriptor>,
54 bootloader: Option<ParcelFileDescriptor>,
55) -> Result<(), Error> {
Pierre-Clément Tosi0d1aed02022-11-17 17:06:28 +000056 android_logger::init_once(
Jeff Vander Stoepd9dda0c2024-02-07 14:27:06 +010057 android_logger::Config::default()
58 .with_tag("vmbase")
59 .with_max_level(log::LevelFilter::Debug),
Pierre-Clément Tosi0d1aed02022-11-17 17:06:28 +000060 );
61
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000062 // We need to start the thread pool for Binder to work properly, especially link_to_death.
63 ProcessState::start_thread_pool();
64
David Brazdil4b4c5102022-12-19 22:56:20 +000065 let virtmgr =
66 vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
67 let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000068
Andrew Walbranb713baa2022-12-07 14:34:49 +000069 // Make file for test disk image.
70 let mut test_image = File::options()
71 .create(true)
72 .read(true)
73 .write(true)
74 .truncate(true)
75 .open(TEST_DISK_IMAGE_PATH)
76 .with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
77 // Write 4 sectors worth of 4-byte numbers counting up.
78 for i in 0u32..512 {
79 test_image.write_all(&i.to_le_bytes())?;
80 }
81 let test_image = ParcelFileDescriptor::new(test_image);
82 let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
83
Andrew Walbran6ac174e2023-06-23 14:58:51 +000084 // Make file for empty test disk image.
85 let empty_image = File::options()
86 .create(true)
87 .read(true)
88 .write(true)
89 .truncate(true)
90 .open(EMPTY_DISK_IMAGE_PATH)
91 .with_context(|| format!("Failed to open empty disk image {}", EMPTY_DISK_IMAGE_PATH))?;
92 let empty_image = ParcelFileDescriptor::new(empty_image);
93 let empty_disk_image =
94 DiskImage { image: Some(empty_image), writable: false, partitions: vec![] };
95
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000096 let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
Seungjae Yoo62085c02022-08-12 04:44:52 +000097 name: String::from("VmBaseTest"),
Pierre-Clément Tosi48ac6a92024-08-14 00:42:05 +010098 kernel,
Andrew Walbran94bbf2f2022-05-12 18:35:42 +000099 initrd: None,
100 params: None,
Pierre-Clément Tosie0d68402024-08-14 00:27:47 +0100101 bootloader,
Andrew Walbran6ac174e2023-06-23 14:58:51 +0000102 disks: vec![disk_image, empty_disk_image],
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000103 protectedVm: false,
104 memoryMib: 300,
Elie Kheirallahb4b2f242025-01-23 03:38:07 +0000105 cpuOptions: CpuOptions { cpuTopology: CpuTopology::CpuCount(1) },
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000106 platformVersion: "~1.0".to_string(),
Nikita Ioffe5776f082023-02-10 21:38:26 +0000107 gdbPort: 0, // no gdb
Inseob Kim6ef80972023-07-20 17:23:36 +0900108 ..Default::default()
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000109 });
Jakob Vukalovicef996292023-04-13 14:28:34 +0000110 let (handle, console) = android_log_fd()?;
Andrew Walbran8d05dae2023-03-22 16:42:55 +0000111 let (mut log_reader, log_writer) = pipe()?;
Jiyong Parke6fb1672023-06-26 16:45:55 +0900112 let vm = VmInstance::create(
113 service.as_ref(),
114 &config,
115 Some(console),
116 /* consoleIn */ None,
117 Some(log_writer),
Elie Kheirallah5c807a22024-09-23 20:40:42 +0000118 /* dump_dt */ None,
Chaitanya Cheemala (xWF)3da8a162025-01-21 08:57:09 -0800119 None,
Jiyong Parke6fb1672023-06-26 16:45:55 +0900120 )
121 .context("Failed to create VM")?;
Chaitanya Cheemala (xWF)3da8a162025-01-21 08:57:09 -0800122 vm.start().context("Failed to start VM")?;
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000123 info!("Started example VM.");
124
125 // Wait for VM to finish, and check that it shut down cleanly.
126 let death_reason = vm.wait_for_death();
127 assert_eq!(death_reason, DeathReason::Shutdown);
Jakob Vukalovicef996292023-04-13 14:28:34 +0000128 handle.join().unwrap();
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000129
Andrew Walbran8d05dae2023-03-22 16:42:55 +0000130 // Check that the expected string was written to the log VirtIO console device.
131 let expected = "Hello VirtIO console\n";
132 let mut log_output = String::new();
133 assert_eq!(log_reader.read_to_string(&mut log_output)?, expected.len());
134 assert_eq!(log_output, expected);
135
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000136 Ok(())
137}
138
Jakob Vukalovicef996292023-04-13 14:28:34 +0000139fn android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error> {
Andrew Walbran8d05dae2023-03-22 16:42:55 +0000140 let (reader, writer) = pipe()?;
Jakob Vukalovicef996292023-04-13 14:28:34 +0000141 let handle = thread::spawn(|| VmLogProcessor::new(reader).run().unwrap());
142 Ok((handle, writer))
Andrew Walbran94bbf2f2022-05-12 18:35:42 +0000143}
Andrew Walbran8d05dae2023-03-22 16:42:55 +0000144
145fn pipe() -> io::Result<(File, File)> {
146 let (reader_fd, writer_fd) = nix::unistd::pipe()?;
Frederick Maylefbbcfcd2024-04-08 16:31:54 -0700147 Ok((reader_fd.into(), writer_fd.into()))
Andrew Walbran8d05dae2023-03-22 16:42:55 +0000148}
Jakob Vukalovicef996292023-04-13 14:28:34 +0000149
Pierre-Clément Tosie0d68402024-08-14 00:27:47 +0100150fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
151 let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
152 Ok(ParcelFileDescriptor::new(file))
153}
154
Jakob Vukalovicef996292023-04-13 14:28:34 +0000155struct VmLogProcessor {
156 reader: Option<File>,
157 expected: VecDeque<String>,
158 unexpected: HashSet<String>,
159 had_unexpected: bool,
160}
161
162impl VmLogProcessor {
163 fn messages() -> (VecDeque<String>, HashSet<String>) {
164 let mut expected = VecDeque::new();
165 let mut unexpected = HashSet::new();
166 for log_lvl in ["[ERROR]", "[WARN]", "[INFO]", "[DEBUG]"] {
167 expected.push_back(format!("{log_lvl} Unsuppressed message"));
168 unexpected.insert(format!("{log_lvl} Suppressed message"));
169 }
170 (expected, unexpected)
171 }
172
173 fn new(reader: File) -> Self {
174 let (expected, unexpected) = Self::messages();
175 Self { reader: Some(reader), expected, unexpected, had_unexpected: false }
176 }
177
178 fn verify(&mut self, msg: &str) {
179 if self.expected.front() == Some(&msg.to_owned()) {
180 self.expected.pop_front();
181 }
182 if !self.had_unexpected && self.unexpected.contains(msg) {
183 self.had_unexpected = true;
184 }
185 }
186
187 fn run(mut self) -> Result<(), &'static str> {
188 for line in BufReader::new(self.reader.take().unwrap()).lines() {
189 let msg = line.unwrap();
190 info!("{msg}");
191 self.verify(&msg);
192 }
193 if !self.expected.is_empty() {
194 Err("missing expected log message")
195 } else if self.had_unexpected {
196 Err("unexpected log message")
197 } else {
198 Ok(())
199 }
200 }
201}