Use the vm tool

MicrodroidHostTestCases now uses the vm tool to create the VM. It no
longer directly interacts with crosvm and mk_cdisk.

The READMD.md file is rewritten to fully reflect the recent changes, and
also to add description about building an app for microdroid.

Bug: 185891097
Test: atest MicrodroidHostTestCases
Change-Id: I5fdc854390fd362ebead22a4a36af75c30500a81
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index 247923d..adad01d 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -18,6 +18,10 @@
         <option name="force-root" value="true" />
     </target_preparer>
 
+    <!-- virtualizationservice doesn't have access to shell_data_file. Instead of giving it
+      a test-only permission, run it without selinux -->
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"/>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="MicrodroidTestApp.apk" />
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 5449af0..4aa8eb5 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -46,9 +46,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.zip.ZipFile;
 
@@ -56,7 +55,6 @@
 public class MicrodroidTestCase extends BaseHostJUnit4Test {
     private static final String TEST_ROOT = "/data/local/tmp/virt/";
     private static final String VIRT_APEX = "/apex/com.android.virt/";
-    private static final int TEST_VM_CID = 10;
     private static final int TEST_VM_ADB_PORT = 8000;
     private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
 
@@ -69,9 +67,8 @@
         final String apkName = "MicrodroidTestApp.apk";
         final String packageName = "com.android.microdroid.test";
         final String configPath = "assets/vm_config.json"; // path inside the APK
-        startMicrodroid(apkName, packageName, configPath);
-        waitForMicrodroidBoot(MICRODROID_BOOT_TIMEOUT_MINUTES);
-        adbConnectToMicrodroid();
+        final String cid = startMicrodroid(apkName, packageName, configPath);
+        adbConnectToMicrodroid(cid, MICRODROID_BOOT_TIMEOUT_MINUTES);
 
         // Check if it actually booted by reading a sysprop.
         assertThat(runOnMicrodroid("getprop", "ro.hardware"), is("microdroid"));
@@ -108,15 +105,12 @@
                 is("Hello Microdroid " + testLib + " arg1 arg2"));
 
         // Shutdown microdroid
-        runOnMicrodroid("reboot");
+        runOnAndroid(VIRT_APEX + "bin/vm", "stop", cid);
     }
 
     // Run an arbitrary command in the host side and returns the result
     private String runOnHost(String... cmd) {
-        final long timeout = 10000;
-        CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd);
-        assertThat(result.getStatus(), is(CommandStatus.SUCCESS));
-        return result.getStdout().trim();
+        return runOnHostWithTimeout(10000, cmd);
     }
 
     // Same as runOnHost, but failure is not an error
@@ -126,6 +120,14 @@
         return result.getStdout().trim();
     }
 
+    // Same as runOnHost, but with custom timeout
+    private String runOnHostWithTimeout(long timeoutMillis, String... cmd) {
+        assertTrue(timeoutMillis >= 0);
+        CommandResult result = RunUtil.getDefault().runTimedCmd(timeoutMillis, cmd);
+        assertThat(result.getStatus(), is(CommandStatus.SUCCESS));
+        return result.getStdout().trim();
+    }
+
     // Run a shell command on Android
     private String runOnAndroid(String... cmd) throws Exception {
         CommandResult result = getDevice().executeShellV2Command(join(cmd));
@@ -213,6 +215,9 @@
         runOnAndroid(mkPayload, payloadJson, payloadImg);
         assertThat(runOnAndroid("du", "-b", payloadImg), is(not("")));
 
+        // The generated files are owned by root. Allow the virtualizationservice to read them.
+        runOnAndroid("chmod", "go+r", TEST_ROOT + "payload*");
+
         return payloadImg;
     }
 
@@ -220,65 +225,64 @@
         return (new CompatibilityBuildHelper(getBuild())).getTestFile(name);
     }
 
-    private void startMicrodroid(String apkName, String packageName, String configPath)
+    private String startMicrodroid(String apkName, String packageName, String configPath)
             throws Exception {
         // Create payload.img
-        final String payloadImg = createPayloadImage(apkName, packageName, configPath);
+        createPayloadImage(apkName, packageName, configPath);
 
-        // Tools and executables
-        final String mkCdisk = VIRT_APEX + "bin/mk_cdisk";
-        final String crosvm = VIRT_APEX + "bin/crosvm";
+        // Run the VM
+        runOnAndroid("start", "virtualizationservice");
+        String ret =
+                runOnAndroid(
+                        VIRT_APEX + "bin/vm",
+                        "run",
+                        "--daemonize",
+                        VIRT_APEX + "etc/microdroid.json");
 
-        // Create os_composisite.img and env_composite.img
-        // TODO(jiyong): remove this when running a VM is done by `vm`
-        final String cdiskJson = VIRT_APEX + "etc/microdroid_cdisk.json";
-        final String cdiskEnvJson = VIRT_APEX + "etc/microdroid_cdisk_env.json";
-        final String osImg = TEST_ROOT + "os_composite.img";
-        final String envImg = TEST_ROOT + "env_composite.img";
-        final String bootloader = VIRT_APEX + "etc/microdroid_bootloader";
-        runOnAndroid(mkCdisk, cdiskJson, osImg);
-        runOnAndroid(mkCdisk, cdiskEnvJson, envImg);
-
-        // Start microdroid using crosvm
-        // TODO(jiyong): do this via the `vm` command
-        ExecutorService executor = Executors.newFixedThreadPool(1);
-        executor.execute(
-                () -> {
-                    try {
-                        runOnAndroid(
-                                crosvm,
-                                "run",
-                                "--cid=" + TEST_VM_CID,
-                                "--disable-sandbox",
-                                "--bios=" + bootloader,
-                                "--serial=type=syslog",
-                                "--disk=" + osImg,
-                                "--disk=" + envImg,
-                                "--disk=" + payloadImg,
-                                "&");
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
-    }
-
-    private void waitForMicrodroidBoot(long timeoutMinutes) throws Exception {
-        final String pattern = "load_persist_props_action";
-        getDevice()
-                .executeShellV2Command(
-                        "logcat --regex=\"" + pattern + "\" -m 1",
-                        timeoutMinutes,
-                        TimeUnit.MINUTES);
+        // Retrieve the CID from the vm tool output
+        Pattern pattern = Pattern.compile("with CID (\\d+)");
+        Matcher matcher = pattern.matcher(ret);
+        assertTrue(matcher.find());
+        return matcher.group(1);
     }
 
     // Establish an adb connection to microdroid by letting Android forward the connection to
-    // microdroid.
-    private void adbConnectToMicrodroid() {
+    // microdroid. Wait until the connection is established and microdroid is booted.
+    private void adbConnectToMicrodroid(String cid, long timeoutMinutes) throws Exception {
+        long start = System.currentTimeMillis();
+        long timeoutMillis = timeoutMinutes * 60 * 1000;
+        long elapsed = 0;
+
         final String serial = getDevice().getSerialNumber();
         final String from = "tcp:" + TEST_VM_ADB_PORT;
-        final String to = "vsock:" + TEST_VM_CID + ":5555";
+        final String to = "vsock:" + cid + ":5555";
         runOnHost("adb", "-s", serial, "forward", from, to);
-        runOnHost("adb", "connect", MICRODROID_SERIAL);
+
+        boolean disconnected = true;
+        while (disconnected) {
+            elapsed = System.currentTimeMillis() - start;
+            timeoutMillis -= elapsed;
+            start = System.currentTimeMillis();
+            String ret = runOnHostWithTimeout(timeoutMillis, "adb", "connect", MICRODROID_SERIAL);
+            disconnected = ret.equals("failed to connect to " + MICRODROID_SERIAL);
+            if (disconnected) {
+                // adb demands us to disconnect if the prior connection was a failure.
+                runOnHost("adb", "disconnect", MICRODROID_SERIAL);
+            }
+        }
+
+        elapsed = System.currentTimeMillis() - start;
+        timeoutMillis -= elapsed;
+        runOnHostWithTimeout(timeoutMillis, "adb", "-s", MICRODROID_SERIAL, "wait-for-device");
+
+        boolean dataAvailable = false;
+        while (!dataAvailable && timeoutMillis >= 0) {
+            elapsed = System.currentTimeMillis() - start;
+            timeoutMillis -= elapsed;
+            start = System.currentTimeMillis();
+            final String checkCmd = "if [ -d /data/local/tmp ]; then echo 1; fi";
+            dataAvailable = runOnMicrodroid(checkCmd).equals("1");
+        }
     }
 
     private void skipIfFail(String command) throws Exception {
@@ -316,7 +320,9 @@
         // disconnect from microdroid
         tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL);
 
-        // kill stale crosvm processes
+        // kill stale VMs and directories
         tryRunOnAndroid("killall", "crosvm");
+        tryRunOnAndroid("rm", "-rf", "/data/misc/virtualizationservice/*");
+        tryRunOnAndroid("stop", "virtualizationservice");
     }
 }