Add test APIs for Microdroid GKI
As we'll run MicrodroidTests on all supported GKIs, this adds test APIs
to get a list of available OSes and to run a specific microdroid GKI.
MicrodroidTests will use the API to retrieve a list of available GKIs
and run tests with such GKIs.
Bug: 302465542
Test: atest MicrodroidTests
Change-Id: I35bb602975776396445f96154e7be3891580e91d
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index 12c099d..34837a3 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -7,17 +7,20 @@
}
public final class VirtualMachineConfig {
+ method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull public String getOs();
method @Nullable public String getPayloadConfigPath();
method public boolean isVmConsoleInputSupported();
}
public static final class VirtualMachineConfig.Builder {
+ method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setOs(@NonNull String);
method @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadConfigPath(@NonNull String);
method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull @RequiresPermission(android.system.virtualmachine.VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) public android.system.virtualmachine.VirtualMachineConfig.Builder setVendorDiskImage(@NonNull java.io.File);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmConsoleInputSupported(boolean);
}
public class VirtualMachineManager {
+ method @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES") @NonNull public java.util.List<java.lang.String> getSupportedOSList() throws android.system.virtualmachine.VirtualMachineException;
method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
field public static final String FEATURE_DICE_CHANGES = "com.android.kvm.DICE_CHANGES";
field public static final String FEATURE_MULTI_TENANT = "com.android.kvm.MULTI_TENANT";
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index cc8f65b..cdc8f02 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -66,7 +66,7 @@
private static String[] EMPTY_STRING_ARRAY = {};
// These define the schema of the config file persisted on disk.
- private static final int VERSION = 6;
+ private static final int VERSION = 7;
private static final String KEY_VERSION = "version";
private static final String KEY_PACKAGENAME = "packageName";
private static final String KEY_APKPATH = "apkPath";
@@ -80,6 +80,7 @@
private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
private static final String KEY_VM_CONSOLE_INPUT_SUPPORTED = "vmConsoleInputSupported";
private static final String KEY_VENDOR_DISK_IMAGE_PATH = "vendorDiskImagePath";
+ private static final String KEY_OS = "os";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -173,6 +174,8 @@
@Nullable private final File mVendorDiskImage;
+ private final String mOs;
+
private VirtualMachineConfig(
@Nullable String packageName,
@Nullable String apkPath,
@@ -185,7 +188,8 @@
long encryptedStorageBytes,
boolean vmOutputCaptured,
boolean vmConsoleInputSupported,
- @Nullable File vendorDiskImage) {
+ @Nullable File vendorDiskImage,
+ @NonNull String os) {
// This is only called from Builder.build(); the builder handles parameter validation.
mPackageName = packageName;
mApkPath = apkPath;
@@ -199,6 +203,7 @@
mVmOutputCaptured = vmOutputCaptured;
mVmConsoleInputSupported = vmConsoleInputSupported;
mVendorDiskImage = vendorDiskImage;
+ mOs = os;
}
/** Loads a config from a file. */
@@ -280,6 +285,11 @@
builder.setVendorDiskImage(new File(vendorDiskImagePath));
}
+ String os = b.getString(KEY_OS);
+ if (os != null) {
+ builder.setOs(os);
+ }
+
return builder.build();
}
@@ -318,6 +328,7 @@
if (mVendorDiskImage != null) {
b.putString(KEY_VENDOR_DISK_IMAGE_PATH, mVendorDiskImage.getAbsolutePath());
}
+ b.putString(KEY_OS, mOs);
b.writeToStream(output);
}
@@ -447,6 +458,19 @@
}
/**
+ * Returns the OS of the VM.
+ *
+ * @see Builder#setOs
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
+ @NonNull
+ public String getOs() {
+ return mOs;
+ }
+
+ /**
* Tests if this config is compatible with other config. Being compatible means that the configs
* can be interchangeably used for the same virtual machine; they do not change the VM identity
* or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes
@@ -469,7 +493,8 @@
&& Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
&& Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName)
&& Objects.equals(this.mPackageName, other.mPackageName)
- && Objects.equals(this.mApkPath, other.mApkPath);
+ && Objects.equals(this.mApkPath, other.mApkPath)
+ && Objects.equals(this.mOs, other.mOs);
}
/**
@@ -493,6 +518,7 @@
if (mPayloadBinaryName != null) {
VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig();
payloadConfig.payloadBinaryName = mPayloadBinaryName;
+ payloadConfig.osName = mOs;
vsConfig.payload =
VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig);
} else {
@@ -591,6 +617,8 @@
*/
@SystemApi
public static final class Builder {
+ private final String DEFAULT_OS = "microdroid";
+
@Nullable private final String mPackageName;
@Nullable private String mApkPath;
@Nullable private String mPayloadConfigPath;
@@ -604,6 +632,7 @@
private boolean mVmOutputCaptured = false;
private boolean mVmConsoleInputSupported = false;
@Nullable private File mVendorDiskImage;
+ private String mOs = DEFAULT_OS;
/**
* Creates a builder for the given context.
@@ -678,7 +707,8 @@
mEncryptedStorageBytes,
mVmOutputCaptured,
mVmConsoleInputSupported,
- mVendorDiskImage);
+ mVendorDiskImage,
+ mOs);
}
/**
@@ -910,5 +940,20 @@
mVendorDiskImage = vendorDiskImage;
return this;
}
+
+ /**
+ * Sets an OS for the VM. Defaults to {@code "microdroid"}.
+ *
+ * <p>See {@link VirtualMachineManager#getSupportedOSList} for available OS names.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
+ @NonNull
+ public Builder setOs(@NonNull String os) {
+ mOs = requireNonNull(os, "os must not be null");
+ return this;
+ }
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index a4927db..2802659 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -18,6 +18,7 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,6 +40,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
/**
@@ -318,6 +321,25 @@
}
/**
+ * Returns a list of supported OS names.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi("RELEASE_AVF_ENABLE_VENDOR_MODULES")
+ @NonNull
+ public List<String> getSupportedOSList() throws VirtualMachineException {
+ synchronized (sCreateLock) {
+ VirtualizationService service = VirtualizationService.getInstance();
+ try {
+ return Arrays.asList(service.getBinder().getSupportedOSList());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /**
* Returns {@code true} if given {@code featureName} is enabled.
*
* @hide
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 1fa0976..2fa1190 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -1094,8 +1094,15 @@
return parseStringArrayFieldsFromVmInfo("Assignable devices: ");
}
+ private List<String> getSupportedOSList() throws Exception {
+ return parseStringArrayFieldsFromVmInfo("Available OS list: ");
+ }
+
private List<String> getSupportedGKIVersions() throws Exception {
- return parseStringArrayFieldsFromVmInfo("Available gki versions: ");
+ return getSupportedOSList().stream()
+ .filter(os -> os.startsWith("microdroid_gki-"))
+ .map(os -> os.replaceFirst("^microdroid_gki-", ""))
+ .collect(Collectors.toList());
}
private TestDevice getAndroidDevice() {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 2367707..c90fb5c 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -486,6 +486,7 @@
assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
assertThat(minimal.getEncryptedStorageBytes()).isEqualTo(0);
assertThat(minimal.isVmOutputCaptured()).isEqualTo(false);
+ assertThat(minimal.getOs()).isEqualTo("microdroid");
// Maximal has everything that can be set to some non-default value. (And has different
// values than minimal for the required fields.)
@@ -511,10 +512,20 @@
assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
assertThat(maximal.getEncryptedStorageBytes()).isEqualTo(1_000_000);
assertThat(maximal.isVmOutputCaptured()).isEqualTo(true);
+ assertThat(maximal.getOs()).isEqualTo("microdroid");
assertThat(minimal.isCompatibleWith(maximal)).isFalse();
assertThat(minimal.isCompatibleWith(minimal)).isTrue();
assertThat(maximal.isCompatibleWith(maximal)).isTrue();
+
+ VirtualMachineConfig os =
+ newVmConfigBuilder()
+ .setPayloadBinaryName("binary.so")
+ .setOs("microdroid_gki-android14-6.1")
+ .build();
+ assertThat(os.getPayloadBinaryName()).isEqualTo("binary.so");
+ assertThat(os.getOs()).isEqualTo("microdroid_gki-android14-6.1");
+ assertThat(os.isCompatibleWith(minimal)).isFalse();
}
@Test
@@ -626,6 +637,11 @@
.setProtectedVm(isProtectedVm())
.setPayloadBinaryName("binary.so");
assertConfigCompatible(currentContextConfig, otherContextBuilder).isFalse();
+
+ VirtualMachineConfig microdroidOsConfig = newBaselineBuilder().setOs("microdroid").build();
+ VirtualMachineConfig.Builder otherOsBuilder =
+ newBaselineBuilder().setOs("microdroid_gki-android14-6.1");
+ assertConfigCompatible(microdroidOsConfig, otherOsBuilder).isFalse();
}
private VirtualMachineConfig.Builder newBaselineBuilder() {
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 60c94fc..f58e999 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -38,6 +38,7 @@
"libclap",
"libcommand_fds",
"libdisk",
+ "libglob",
"libhex",
"libhypervisor_props",
"liblazy_static",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 12b8f88..2603e77 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -69,12 +69,12 @@
IntoBinderResult,
};
use disk::QcowFile;
+use glob::glob;
use lazy_static::lazy_static;
use libfdt::Fdt;
use log::{debug, error, info, warn};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::unistd::pipe;
-use regex::Regex;
use rpcbinder::RpcServer;
use rustutils::system_properties;
use semver::VersionReq;
@@ -83,6 +83,7 @@
use std::ffi::{CStr, CString};
use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
+use std::iter;
use std::num::{NonZeroU16, NonZeroU32};
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::unix::raw::pid_t;
@@ -126,8 +127,8 @@
pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
wait_for_interface(BINDER_SERVICE_IDENTIFIER)
.expect("Could not connect to VirtualizationServiceInternal");
- static ref MICRODROID_GKI_OS_NAME_PATTERN: Regex =
- Regex::new(r"^microdroid_gki-android\d+-\d+\.\d+$").expect("Failed to construct Regex");
+ static ref SUPPORTED_OS_NAMES: HashSet<String> =
+ get_supported_os_names().expect("Failed to get list of supported os names");
}
fn create_or_update_idsig_file(
@@ -289,6 +290,11 @@
GLOBAL_SERVICE.getAssignableDevices()
}
+ /// Get a list of supported OSes.
+ fn getSupportedOSList(&self) -> binder::Result<Vec<String>> {
+ Ok(Vec::from_iter(SUPPORTED_OS_NAMES.iter().cloned()))
+ }
+
/// Returns whether given feature is enabled
fn isFeatureEnabled(&self, feature: &str) -> binder::Result<bool> {
check_manage_access()?;
@@ -728,14 +734,32 @@
}
}
-fn is_valid_os(os_name: &str) -> bool {
- if os_name == MICRODROID_OS_NAME {
- true
- } else if cfg!(vendor_modules) && MICRODROID_GKI_OS_NAME_PATTERN.is_match(os_name) {
- PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name)).exists()
- } else {
- false
+fn extract_os_name_from_config_path(config: &Path) -> Option<String> {
+ if config.extension()?.to_str()? != "json" {
+ return None;
}
+
+ Some(config.with_extension("").file_name()?.to_str()?.to_owned())
+}
+
+fn extract_os_names_from_configs(config_glob_pattern: &str) -> Result<HashSet<String>> {
+ let configs = glob(config_glob_pattern)?.collect::<Result<Vec<_>, _>>()?;
+ let os_names =
+ configs.iter().filter_map(|x| extract_os_name_from_config_path(x)).collect::<HashSet<_>>();
+
+ Ok(os_names)
+}
+
+fn get_supported_os_names() -> Result<HashSet<String>> {
+ if !cfg!(vendor_modules) {
+ return Ok(iter::once(MICRODROID_OS_NAME.to_owned()).collect());
+ }
+
+ extract_os_names_from_configs("/apex/com.android.virt/etc/microdroid*.json")
+}
+
+fn is_valid_os(os_name: &str) -> bool {
+ SUPPORTED_OS_NAMES.contains(os_name)
}
fn load_app_config(
@@ -1593,6 +1617,72 @@
tmp_dir.close()?;
Ok(())
}
+
+ fn test_extract_os_name_from_config_path(
+ path: &Path,
+ expected_result: Option<&str>,
+ ) -> Result<()> {
+ let result = extract_os_name_from_config_path(path);
+ if result.as_deref() != expected_result {
+ bail!("Expected {:?} but was {:?}", expected_result, &result)
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_os_name_from_microdroid_config() -> Result<()> {
+ test_extract_os_name_from_config_path(
+ Path::new("/apex/com.android.virt/etc/microdroid.json"),
+ Some("microdroid"),
+ )
+ }
+
+ #[test]
+ fn test_extract_os_name_from_microdroid_gki_config() -> Result<()> {
+ test_extract_os_name_from_config_path(
+ Path::new("/apex/com.android.virt/etc/microdroid_gki-android14-6.1.json"),
+ Some("microdroid_gki-android14-6.1"),
+ )
+ }
+
+ #[test]
+ fn test_extract_os_name_from_invalid_path() -> Result<()> {
+ test_extract_os_name_from_config_path(
+ Path::new("/apex/com.android.virt/etc/microdroid.img"),
+ None,
+ )
+ }
+
+ #[test]
+ fn test_extract_os_name_from_configs() -> Result<()> {
+ let tmp_dir = tempfile::TempDir::new()?;
+ let tmp_dir_path = tmp_dir.path().to_owned();
+
+ let mut os_names: HashSet<String> = HashSet::new();
+ os_names.insert("microdroid".to_owned());
+ os_names.insert("microdroid_gki-android14-6.1".to_owned());
+ os_names.insert("microdroid_gki-android15-6.1".to_owned());
+
+ // config files
+ for os_name in &os_names {
+ std::fs::write(tmp_dir_path.join(os_name.to_owned() + ".json"), b"")?;
+ }
+
+ // fake files not related to configs
+ std::fs::write(tmp_dir_path.join("microdroid_super.img"), b"")?;
+ std::fs::write(tmp_dir_path.join("microdroid_foobar.apk"), b"")?;
+
+ let glob_pattern = match tmp_dir_path.join("microdroid*.json").to_str() {
+ Some(s) => s.to_owned(),
+ None => bail!("tmp_dir_path {:?} is not UTF-8", tmp_dir_path),
+ };
+
+ let result = extract_os_names_from_configs(&glob_pattern)?;
+ if result != os_names {
+ bail!("Expected {:?} but was {:?}", os_names, result);
+ }
+ Ok(())
+ }
}
struct SecretkeeperProxy(Strong<dyn ISecretkeeper>);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index d6a1299..92a5812 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -66,6 +66,11 @@
*/
AssignableDevice[] getAssignableDevices();
+ /**
+ * Get a list of supported OSes.
+ */
+ String[] getSupportedOSList();
+
/** Returns whether given feature is enabled. */
boolean isFeatureEnabled(in String feature);
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 9a92f13..5c07eed 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -27,7 +27,6 @@
use clap::{Args, Parser};
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
-use glob::glob;
use run::{command_run, command_run_app, command_run_microdroid};
use std::num::NonZeroU16;
use std::path::{Path, PathBuf};
@@ -316,12 +315,6 @@
Ok(())
}
-fn extract_gki_version(gki_config: &Path) -> Option<&str> {
- let name = gki_config.file_name()?;
- let name_str = name.to_str()?;
- name_str.strip_prefix("microdroid_gki-")?.strip_suffix(".json")
-}
-
/// Print information about supported VM types.
fn command_info() -> Result<(), Error> {
let non_protected_vm_supported = hypervisor_props::is_vm_supported()?;
@@ -361,11 +354,8 @@
let devices = devices.into_iter().map(|x| x.node).collect::<Vec<_>>();
println!("Assignable devices: {}", serde_json::to_string(&devices)?);
- let gki_configs =
- glob("/apex/com.android.virt/etc/microdroid_gki-*.json")?.collect::<Result<Vec<_>, _>>()?;
- let gki_versions =
- gki_configs.iter().filter_map(|x| extract_gki_version(x)).collect::<Vec<_>>();
- println!("Available gki versions: {}", serde_json::to_string(&gki_versions)?);
+ let os_list = get_service()?.getSupportedOSList()?;
+ println!("Available OS list: {}", serde_json::to_string(&os_list)?);
Ok(())
}