blob: 41138815067ba6a7d0887473c93d5c4d0b6984f6 [file] [log] [blame]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Integration test for VMs on device.
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
CpuTopology::CpuTopology, DiskImage::DiskImage, VirtualMachineConfig::VirtualMachineConfig,
VirtualMachineRawConfig::VirtualMachineRawConfig,
},
binder::{ParcelFileDescriptor, ProcessState},
};
use anyhow::anyhow;
use anyhow::Context;
use anyhow::Error;
use log::error;
use log::info;
use std::fs::read_to_string;
use std::fs::File;
use std::io::Write;
use std::process::Command;
use vmclient::VmInstance;
const VMBASE_EXAMPLE_KERNEL_PATH: &str = "vmbase_example_kernel.bin";
const TEST_DISK_IMAGE_PATH: &str = "test_disk.img";
const EMPTY_DISK_IMAGE_PATH: &str = "empty_disk.img";
const GOLDEN_DEVICE_TREE: &str = "./goldens/dt_dump_golden.dts";
const GOLDEN_DEVICE_TREE_PROTECTED: &str = "./goldens/dt_dump_protected_golden.dts";
/// Runs an unprotected VM and validates it against a golden device tree.
#[test]
fn test_device_tree_compat() -> Result<(), Error> {
run_test(false, GOLDEN_DEVICE_TREE)
}
/// Runs a protected VM and validates it against a golden device tree.
#[test]
fn test_device_tree_protected_compat() -> Result<(), Error> {
run_test(true, GOLDEN_DEVICE_TREE_PROTECTED)
}
fn run_test(protected: bool, golden_dt: &str) -> Result<(), Error> {
let kernel = Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?);
android_logger::init_once(
android_logger::Config::default()
.with_tag("backcompat")
.with_max_level(log::LevelFilter::Debug),
);
// We need to start the thread pool for Binder to work properly, especially link_to_death.
ProcessState::start_thread_pool();
let virtmgr =
vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
let service = virtmgr.connect().context("Failed to connect to VirtualizationService")?;
// Make file for test disk image.
let mut test_image = File::options()
.create(true)
.read(true)
.write(true)
.truncate(true)
.open(TEST_DISK_IMAGE_PATH)
.with_context(|| format!("Failed to open test disk image {}", TEST_DISK_IMAGE_PATH))?;
// Write 4 sectors worth of 4-byte numbers counting up.
for i in 0u32..512 {
test_image.write_all(&i.to_le_bytes())?;
}
let test_image = ParcelFileDescriptor::new(test_image);
let disk_image = DiskImage { image: Some(test_image), writable: false, partitions: vec![] };
// Make file for empty test disk image.
let empty_image = File::options()
.create(true)
.read(true)
.write(true)
.truncate(true)
.open(EMPTY_DISK_IMAGE_PATH)
.with_context(|| format!("Failed to open empty disk image {}", EMPTY_DISK_IMAGE_PATH))?;
let empty_image = ParcelFileDescriptor::new(empty_image);
let empty_disk_image =
DiskImage { image: Some(empty_image), writable: false, partitions: vec![] };
let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
name: String::from("VmBaseTest"),
kernel,
disks: vec![disk_image, empty_disk_image],
protectedVm: protected,
memoryMib: 300,
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "~1.0".to_string(),
..Default::default()
});
let dump_dt = File::options()
.create(true)
.read(true)
.write(true)
.truncate(true)
.open("dump_dt.dtb")
.with_context(|| "Failed to open device tree dump file dump_dt.dtb")?;
let vm = VmInstance::create(
service.as_ref(),
&config,
None,
/* consoleIn */ None,
None,
Some(dump_dt),
None,
)
.context("Failed to create VM")?;
vm.start().context("Failed to start VM")?;
info!("Started example VM.");
// Wait for VM to finish
let _ = vm.wait_for_death();
if !Command::new("./dtc_static")
.arg("-I")
.arg("dts")
.arg("-O")
.arg("dtb")
.arg("-qqq")
.arg("-f")
.arg("-s")
.arg("-o")
.arg("dump_dt_golden.dtb")
.arg(golden_dt)
.output()?
.status
.success()
{
return Err(anyhow!("failed to execute dtc"));
}
let dtcompare_res = Command::new("./dtcompare")
.arg("--dt1")
.arg("dump_dt_golden.dtb")
.arg("--dt2")
.arg("dump_dt.dtb")
.arg("--ignore-path-value")
.arg("/chosen/kaslr-seed")
.arg("--ignore-path-value")
.arg("/chosen/rng-seed")
.arg("--ignore-path-value")
.arg("/avf/untrusted/instance-id")
.arg("--ignore-path-value")
.arg("/chosen/linuxinitrd-start")
.arg("--ignore-path-value")
.arg("/chosen/linuxinitrd-end")
.arg("--ignore-path-value")
.arg("/avf/secretkeeper_public_key")
.arg("--ignore-path")
.arg("/avf/name")
.output()
.context("failed to execute dtcompare")?;
if !dtcompare_res.status.success() {
if !Command::new("./dtc_static")
.arg("-I")
.arg("dtb")
.arg("-O")
.arg("dts")
.arg("-qqq")
.arg("-f")
.arg("-s")
.arg("-o")
.arg("dump_dt_failed.dts")
.arg("dump_dt.dtb")
.output()?
.status
.success()
{
return Err(anyhow!("failed to execute dtc"));
}
let dt2 = read_to_string("dump_dt_failed.dts")?;
error!(
"Device tree 2 does not match golden DT.\n
Device Tree 2: {}",
dt2
);
return Err(anyhow!(
"stdout: {:?}\n stderr: {:?}",
dtcompare_res.stdout,
dtcompare_res.stderr
));
}
Ok(())
}
fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
Ok(ParcelFileDescriptor::new(file))
}