• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         CpuOptions::CpuOptions, CpuOptions::CpuTopology::CpuTopology, DiskImage::DiskImage,
20         VirtualMachineConfig::VirtualMachineConfig,
21         VirtualMachineRawConfig::VirtualMachineRawConfig,
22     },
23     binder::{ParcelFileDescriptor, ProcessState},
24 };
25 use anyhow::{Context, Error};
26 use log::info;
27 use std::{
28     collections::{HashSet, VecDeque},
29     fs::File,
30     io::{self, BufRead, BufReader, Read, Write},
31     thread,
32 };
33 use vmclient::{DeathReason, VmInstance};
34 
35 const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
36 const VMBASE_EXAMPLE_BIOS_PATH: &str = "vmbase_example_bios.bin";
37 const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
38 const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
39 
40 /// Runs the vmbase_example VM as an unprotected VM kernel via VirtualizationService.
41 #[test]
test_run_example_kernel_vm() -> Result<(), Error>42 fn test_run_example_kernel_vm() -> Result<(), Error> {
43     run_test(Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?), None)
44 }
45 
46 /// Runs the vmbase_example VM as an unprotected VM BIOS via VirtualizationService.
47 #[test]
test_run_example_bios_vm() -> Result<(), Error>48 fn test_run_example_bios_vm() -> Result<(), Error> {
49     run_test(None, Some(open_payload(VMBASE_EXAMPLE_BIOS_PATH)?))
50 }
51 
run_test( kernel: Option<ParcelFileDescriptor>, bootloader: Option<ParcelFileDescriptor>, ) -> Result<(), Error>52 fn run_test(
53     kernel: Option<ParcelFileDescriptor>,
54     bootloader: Option<ParcelFileDescriptor>,
55 ) -> Result<(), Error> {
56     android_logger::init_once(
57         android_logger::Config::default()
58             .with_tag("vmbase")
59             .with_max_level(log::LevelFilter::Debug),
60     );
61 
62     // We need to start the thread pool for Binder to work properly, especially link_to_death.
63     ProcessState::start_thread_pool();
64 
65     let virtmgr =
66         vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
67     let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
68 
69     // 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 
84     // 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 
96     let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
97         name: String::from("VmBaseTest"),
98         kernel,
99         initrd: None,
100         params: None,
101         bootloader,
102         disks: vec![disk_image, empty_disk_image],
103         protectedVm: false,
104         memoryMib: 300,
105         cpuOptions: CpuOptions { cpuTopology: CpuTopology::CpuCount(1) },
106         platformVersion: "~1.0".to_string(),
107         gdbPort: 0, // no gdb
108         ..Default::default()
109     });
110     let (handle, console) = android_log_fd()?;
111     let (mut log_reader, log_writer) = pipe()?;
112     let vm = VmInstance::create(
113         service.as_ref(),
114         &config,
115         Some(console),
116         /* consoleIn */ None,
117         Some(log_writer),
118         /* dump_dt */ None,
119     )
120     .context("Failed to create VM")?;
121     vm.start(None).context("Failed to start VM")?;
122     info!("Started example VM.");
123 
124     // Wait for VM to finish, and check that it shut down cleanly.
125     let death_reason = vm.wait_for_death();
126     assert_eq!(death_reason, DeathReason::Shutdown);
127     handle.join().unwrap();
128 
129     // Check that the expected string was written to the log VirtIO console device.
130     let expected = "Hello VirtIO console\n";
131     let mut log_output = String::new();
132     assert_eq!(log_reader.read_to_string(&mut log_output)?, expected.len());
133     assert_eq!(log_output, expected);
134 
135     Ok(())
136 }
137 
android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error>138 fn android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error> {
139     let (reader, writer) = pipe()?;
140     let handle = thread::spawn(|| VmLogProcessor::new(reader).run().unwrap());
141     Ok((handle, writer))
142 }
143 
pipe() -> io::Result<(File, File)>144 fn pipe() -> io::Result<(File, File)> {
145     let (reader_fd, writer_fd) = nix::unistd::pipe()?;
146     Ok((reader_fd.into(), writer_fd.into()))
147 }
148 
open_payload(path: &str) -> Result<ParcelFileDescriptor, Error>149 fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
150     let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
151     Ok(ParcelFileDescriptor::new(file))
152 }
153 
154 struct VmLogProcessor {
155     reader: Option<File>,
156     expected: VecDeque<String>,
157     unexpected: HashSet<String>,
158     had_unexpected: bool,
159 }
160 
161 impl VmLogProcessor {
messages() -> (VecDeque<String>, HashSet<String>)162     fn messages() -> (VecDeque<String>, HashSet<String>) {
163         let mut expected = VecDeque::new();
164         let mut unexpected = HashSet::new();
165         for log_lvl in ["[ERROR]", "[WARN]", "[INFO]", "[DEBUG]"] {
166             expected.push_back(format!("{log_lvl} Unsuppressed message"));
167             unexpected.insert(format!("{log_lvl} Suppressed message"));
168         }
169         (expected, unexpected)
170     }
171 
new(reader: File) -> Self172     fn new(reader: File) -> Self {
173         let (expected, unexpected) = Self::messages();
174         Self { reader: Some(reader), expected, unexpected, had_unexpected: false }
175     }
176 
verify(&mut self, msg: &str)177     fn verify(&mut self, msg: &str) {
178         if self.expected.front() == Some(&msg.to_owned()) {
179             self.expected.pop_front();
180         }
181         if !self.had_unexpected && self.unexpected.contains(msg) {
182             self.had_unexpected = true;
183         }
184     }
185 
run(mut self) -> Result<(), &'static str>186     fn run(mut self) -> Result<(), &'static str> {
187         for line in BufReader::new(self.reader.take().unwrap()).lines() {
188             let msg = line.unwrap();
189             info!("{msg}");
190             self.verify(&msg);
191         }
192         if !self.expected.is_empty() {
193             Err("missing expected log message")
194         } else if self.had_unexpected {
195             Err("unexpected log message")
196         } else {
197             Ok(())
198         }
199     }
200 }
201