• 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         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     os::unix::io::FromRawFd,
31     panic, thread,
32 };
33 use vmclient::{DeathReason, VmInstance};
34 
35 const VMBASE_EXAMPLE_PATH: &str =
36     "/data/local/tmp/vmbase_example.integration_test/arm64/vmbase_example.bin";
37 const TEST_DISK_IMAGE_PATH: &str = "/data/local/tmp/vmbase_example.integration_test/test_disk.img";
38 
39 /// Runs the vmbase_example VM as an unprotected VM via VirtualizationService.
40 #[test]
test_run_example_vm() -> Result<(), Error>41 fn test_run_example_vm() -> Result<(), Error> {
42     android_logger::init_once(
43         android_logger::Config::default().with_tag("vmbase").with_min_level(log::Level::Debug),
44     );
45 
46     // Redirect panic messages to logcat.
47     panic::set_hook(Box::new(|panic_info| {
48         log::error!("{}", panic_info);
49     }));
50 
51     // We need to start the thread pool for Binder to work properly, especially link_to_death.
52     ProcessState::start_thread_pool();
53 
54     let virtmgr =
55         vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
56     let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
57 
58     // Start example VM.
59     let bootloader = ParcelFileDescriptor::new(
60         File::open(VMBASE_EXAMPLE_PATH)
61             .with_context(|| format!("Failed to open VM image {}", VMBASE_EXAMPLE_PATH))?,
62     );
63 
64     // Make file for test disk image.
65     let mut test_image = File::options()
66         .create(true)
67         .read(true)
68         .write(true)
69         .truncate(true)
70         .open(TEST_DISK_IMAGE_PATH)
71         .with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
72     // Write 4 sectors worth of 4-byte numbers counting up.
73     for i in 0u32..512 {
74         test_image.write_all(&i.to_le_bytes())?;
75     }
76     let test_image = ParcelFileDescriptor::new(test_image);
77     let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
78 
79     let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
80         name: String::from("VmBaseTest"),
81         kernel: None,
82         initrd: None,
83         params: None,
84         bootloader: Some(bootloader),
85         disks: vec![disk_image],
86         protectedVm: false,
87         memoryMib: 300,
88         cpuTopology: CpuTopology::ONE_CPU,
89         platformVersion: "~1.0".to_string(),
90         taskProfiles: vec![],
91         gdbPort: 0, // no gdb
92     });
93     let (handle, console) = android_log_fd()?;
94     let (mut log_reader, log_writer) = pipe()?;
95     let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log_writer), None)
96         .context("Failed to create VM")?;
97     vm.start().context("Failed to start VM")?;
98     info!("Started example VM.");
99 
100     // Wait for VM to finish, and check that it shut down cleanly.
101     let death_reason = vm.wait_for_death();
102     assert_eq!(death_reason, DeathReason::Shutdown);
103     handle.join().unwrap();
104 
105     // Check that the expected string was written to the log VirtIO console device.
106     let expected = "Hello VirtIO console\n";
107     let mut log_output = String::new();
108     assert_eq!(log_reader.read_to_string(&mut log_output)?, expected.len());
109     assert_eq!(log_output, expected);
110 
111     Ok(())
112 }
113 
android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error>114 fn android_log_fd() -> Result<(thread::JoinHandle<()>, File), io::Error> {
115     let (reader, writer) = pipe()?;
116     let handle = thread::spawn(|| VmLogProcessor::new(reader).run().unwrap());
117     Ok((handle, writer))
118 }
119 
pipe() -> io::Result<(File, File)>120 fn pipe() -> io::Result<(File, File)> {
121     let (reader_fd, writer_fd) = nix::unistd::pipe()?;
122 
123     // SAFETY: These are new FDs with no previous owner.
124     let reader = unsafe { File::from_raw_fd(reader_fd) };
125     let writer = unsafe { File::from_raw_fd(writer_fd) };
126 
127     Ok((reader, writer))
128 }
129 
130 struct VmLogProcessor {
131     reader: Option<File>,
132     expected: VecDeque<String>,
133     unexpected: HashSet<String>,
134     had_unexpected: bool,
135 }
136 
137 impl VmLogProcessor {
messages() -> (VecDeque<String>, HashSet<String>)138     fn messages() -> (VecDeque<String>, HashSet<String>) {
139         let mut expected = VecDeque::new();
140         let mut unexpected = HashSet::new();
141         for log_lvl in ["[ERROR]", "[WARN]", "[INFO]", "[DEBUG]"] {
142             expected.push_back(format!("{log_lvl} Unsuppressed message"));
143             unexpected.insert(format!("{log_lvl} Suppressed message"));
144         }
145         (expected, unexpected)
146     }
147 
new(reader: File) -> Self148     fn new(reader: File) -> Self {
149         let (expected, unexpected) = Self::messages();
150         Self { reader: Some(reader), expected, unexpected, had_unexpected: false }
151     }
152 
verify(&mut self, msg: &str)153     fn verify(&mut self, msg: &str) {
154         if self.expected.front() == Some(&msg.to_owned()) {
155             self.expected.pop_front();
156         }
157         if !self.had_unexpected && self.unexpected.contains(msg) {
158             self.had_unexpected = true;
159         }
160     }
161 
run(mut self) -> Result<(), &'static str>162     fn run(mut self) -> Result<(), &'static str> {
163         for line in BufReader::new(self.reader.take().unwrap()).lines() {
164             let msg = line.unwrap();
165             info!("{msg}");
166             self.verify(&msg);
167         }
168         if !self.expected.is_empty() {
169             Err("missing expected log message")
170         } else if self.had_unexpected {
171             Err("unexpected log message")
172         } else {
173             Ok(())
174         }
175     }
176 }
177