1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.virt.test;
18 
19 import static org.hamcrest.CoreMatchers.is;
20 import static org.hamcrest.CoreMatchers.not;
21 import static org.junit.Assert.assertThat;
22 
23 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
24 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
25 import com.android.tradefed.util.CommandResult;
26 import com.android.tradefed.util.FileUtil;
27 import com.android.tradefed.util.RunUtil;
28 
29 import org.junit.After;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 
34 import java.io.File;
35 import java.io.FileWriter;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.Executors;
41 
42 @RunWith(DeviceJUnit4ClassRunner.class)
43 public class MicrodroidTestCase extends BaseHostJUnit4Test {
44     private static final String TEST_ROOT = "/data/local/tmp/virt/";
45     private static final String VIRT_APEX = "/apex/com.android.virt/";
46     private static final int TEST_VM_CID = 5;
47     private static final int TEST_VM_ADB_PORT = 8000;
48     private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
49     private static final long MICRODROID_BOOT_TIMEOUT_MILLIS = 15000;
50 
executeCommand(String cmd)51     private String executeCommand(String cmd) {
52         final long defaultCommandTimeoutMillis = 1000; // 1 sec
53         return executeCommand(defaultCommandTimeoutMillis, cmd);
54     }
55 
executeCommand(long timeout, String cmd)56     private String executeCommand(long timeout, String cmd) {
57         CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd.split(" "));
58         return result.getStdout().trim(); // remove the trailing whitespace including newline
59     }
60 
61     @Test
testMicrodroidBoots()62     public void testMicrodroidBoots() throws Exception {
63         // Prepare input files
64         String prepareImagesCmd =
65                 String.format(
66                         "mkdir -p %s; cd %s; "
67                                 + "cp %setc/microdroid_bootloader bootloader && "
68                                 + "cp %setc/fs/*.img . && "
69                                 + "cp %setc/uboot_env.img . && "
70                                 + "dd if=/dev/zero of=misc.img bs=4k count=256 && "
71                                 + "dd if=/dev/zero of=userdata.img bs=1 count=0 seek=4G && "
72                                 + "mkfs.ext4 userdata.img",
73                         TEST_ROOT, TEST_ROOT, VIRT_APEX, VIRT_APEX, VIRT_APEX);
74         getDevice().executeShellCommand(prepareImagesCmd);
75 
76         // Create os_composite.img, env_composite.img, userdata.img, and payload.img
77         String makeOsCompositeCmd =
78                 String.format(
79                         "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk.json os_composite.img",
80                         TEST_ROOT, VIRT_APEX, VIRT_APEX);
81         getDevice().executeShellCommand(makeOsCompositeCmd);
82         String makeEnvCompositeCmd =
83                 String.format(
84                         "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk_env.json env_composite.img",
85                         TEST_ROOT, VIRT_APEX, VIRT_APEX);
86         getDevice().executeShellCommand(makeEnvCompositeCmd);
87         String makeDataCompositeCmd =
88                 String.format(
89                         "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk_userdata.json"
90                                 + " userdata_composite.img",
91                         TEST_ROOT, VIRT_APEX, VIRT_APEX);
92         getDevice().executeShellCommand(makeDataCompositeCmd);
93         String makeDataCompositeQcow2Cmd =
94                 String.format(
95                         "cd %s; %sbin/crosvm create_qcow2 --backing_file=userdata_composite.img"
96                                 + " userdata_composite.qcow2",
97                         TEST_ROOT, VIRT_APEX);
98         getDevice().executeShellCommand(makeDataCompositeQcow2Cmd);
99         String makePayloadCompositeCmd =
100                 String.format(
101                         "cd %s; %sbin/mk_payload %setc/microdroid_payload.json payload.img",
102                         TEST_ROOT, VIRT_APEX, VIRT_APEX);
103         getDevice().executeShellCommand(makePayloadCompositeCmd);
104 
105         // Make sure that the composite images are created
106         final List<String> compositeImages =
107                 new ArrayList<>(
108                         Arrays.asList(
109                                 TEST_ROOT + "/os_composite.img",
110                                 TEST_ROOT + "/env_composite.img",
111                                 TEST_ROOT + "/userdata_composite.qcow2",
112                                 TEST_ROOT + "/payload.img"));
113         CommandResult result =
114                 getDevice().executeShellV2Command("du -b " + String.join(" ", compositeImages));
115         assertThat(result.getExitCode(), is(0));
116         assertThat(result.getStdout(), is(not("")));
117 
118         // Start microdroid using crosvm
119         ExecutorService executor = Executors.newFixedThreadPool(1);
120         String runMicrodroidCmd =
121                 String.format(
122                         "cd %s; %sbin/crosvm run --cid=%d --disable-sandbox --bios=bootloader"
123                                 + " --serial=type=syslog --disk=os_composite.img"
124                                 + " --disk=env_composite.img --disk=payload.img"
125                                 + " --rwdisk=userdata_composite.qcow2",
126                         TEST_ROOT, VIRT_APEX, TEST_VM_CID);
127         executor.execute(
128                 () -> {
129                     try {
130                         getDevice().executeShellV2Command(runMicrodroidCmd);
131                     } catch (Exception e) {
132                         throw new RuntimeException(e);
133                     }
134                 });
135         // .. and wait for microdroid to boot
136         // TODO(jiyong): don't wait too long. We can wait less by monitoring log from microdroid
137         Thread.sleep(MICRODROID_BOOT_TIMEOUT_MILLIS);
138 
139         // Connect to microdroid and read a system property from there
140         executeCommand(
141                 "adb -s "
142                         + getDevice().getSerialNumber()
143                         + " forward tcp:"
144                         + TEST_VM_ADB_PORT
145                         + " vsock:"
146                         + TEST_VM_CID
147                         + ":5555");
148         executeCommand("adb connect " + MICRODROID_SERIAL);
149         String prop = executeCommand("adb -s " + MICRODROID_SERIAL + " shell getprop ro.hardware");
150         assertThat(prop, is("microdroid"));
151 
152         // Test writing to /data partition
153         File tmpFile = FileUtil.createTempFile("test", ".txt");
154         tmpFile.deleteOnExit();
155         FileWriter writer = new FileWriter(tmpFile);
156         writer.write("MicrodroidTest");
157         writer.close();
158 
159         executeCommand(
160                 "adb -s "
161                         + MICRODROID_SERIAL
162                         + " push "
163                         + tmpFile.getPath()
164                         + " /data/local/tmp/test.txt");
165         String catResult =
166                 executeCommand(
167                         "adb -s " + MICRODROID_SERIAL + " shell cat /data/local/tmp/test.txt");
168         assertThat(catResult, is("MicrodroidTest"));
169 
170         // Shutdown microdroid
171         executeCommand("adb -s localhost:" + TEST_VM_ADB_PORT + " shell reboot");
172     }
173 
174     @Before
setUp()175     public void setUp() throws Exception {
176         // delete the test root
177         getDevice().executeShellCommand("rm -rf " + TEST_ROOT);
178 
179         // disconnect from microdroid
180         executeCommand("adb disconnect " + MICRODROID_SERIAL);
181     }
182 
183     @After
shutdown()184     public void shutdown() throws Exception {
185         // disconnect from microdroid
186         executeCommand("adb disconnect " + MICRODROID_SERIAL);
187 
188         // kill stale crosvm processes
189         getDevice().executeShellV2Command("killall crosvm");
190     }
191 }
192