xref: /aosp_15_r20/external/crosvm/tools/impl/testvm.py (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2021 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker
5*bb4ee6a4SAndroid Build Coastguard Workerfrom enum import Enum
6*bb4ee6a4SAndroid Build Coastguard Workerimport json
7*bb4ee6a4SAndroid Build Coastguard Workerimport os
8*bb4ee6a4SAndroid Build Coastguard Workerimport socket
9*bb4ee6a4SAndroid Build Coastguard Workerimport subprocess
10*bb4ee6a4SAndroid Build Coastguard Workerimport sys
11*bb4ee6a4SAndroid Build Coastguard Workerimport time
12*bb4ee6a4SAndroid Build Coastguard Workerimport typing
13*bb4ee6a4SAndroid Build Coastguard Workerfrom contextlib import closing
14*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path
15*bb4ee6a4SAndroid Build Coastguard Workerfrom random import randrange
16*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import Dict, List, Literal, Optional, Tuple
17*bb4ee6a4SAndroid Build Coastguard Worker
18*bb4ee6a4SAndroid Build Coastguard Workerfrom .common import CACHE_DIR, download_file, cmd, rich, console
19*bb4ee6a4SAndroid Build Coastguard Worker
20*bb4ee6a4SAndroid Build Coastguard WorkerKVM_SUPPORT = os.access("/dev/kvm", os.W_OK)
21*bb4ee6a4SAndroid Build Coastguard Worker
22*bb4ee6a4SAndroid Build Coastguard WorkerArch = Literal["x86_64", "aarch64"]
23*bb4ee6a4SAndroid Build Coastguard WorkerARCH_OPTIONS = typing.cast(Tuple[Arch], typing.get_args(Arch))
24*bb4ee6a4SAndroid Build Coastguard Worker
25*bb4ee6a4SAndroid Build Coastguard Worker
26*bb4ee6a4SAndroid Build Coastguard WorkerSCRIPT_DIR = Path(__file__).parent.resolve()
27*bb4ee6a4SAndroid Build Coastguard WorkerSRC_DIR = SCRIPT_DIR.joinpath("testvm")
28*bb4ee6a4SAndroid Build Coastguard WorkerID_RSA = SRC_DIR.joinpath("id_rsa")
29*bb4ee6a4SAndroid Build Coastguard WorkerBASE_IMG_VERSION = open(SRC_DIR.joinpath("version"), "r").read().strip()
30*bb4ee6a4SAndroid Build Coastguard Worker
31*bb4ee6a4SAndroid Build Coastguard WorkerIMAGE_DIR_URL = "https://storage.googleapis.com/crosvm/testvm"
32*bb4ee6a4SAndroid Build Coastguard Worker
33*bb4ee6a4SAndroid Build Coastguard Worker
34*bb4ee6a4SAndroid Build Coastguard Workerdef cargo_target_dir():
35*bb4ee6a4SAndroid Build Coastguard Worker    # Do not call cargo if we have the environment variable specified. This
36*bb4ee6a4SAndroid Build Coastguard Worker    # allows the script to be used when cargo is not available but the target
37*bb4ee6a4SAndroid Build Coastguard Worker    # dir is known.
38*bb4ee6a4SAndroid Build Coastguard Worker    env_target = os.environ.get("CARGO_TARGET_DIR")
39*bb4ee6a4SAndroid Build Coastguard Worker    if env_target:
40*bb4ee6a4SAndroid Build Coastguard Worker        return Path(env_target)
41*bb4ee6a4SAndroid Build Coastguard Worker    text = subprocess.run(
42*bb4ee6a4SAndroid Build Coastguard Worker        ["cargo", "metadata", "--no-deps", "--format-version=1"],
43*bb4ee6a4SAndroid Build Coastguard Worker        check=True,
44*bb4ee6a4SAndroid Build Coastguard Worker        capture_output=True,
45*bb4ee6a4SAndroid Build Coastguard Worker        text=True,
46*bb4ee6a4SAndroid Build Coastguard Worker    ).stdout
47*bb4ee6a4SAndroid Build Coastguard Worker    metadata = json.loads(text)
48*bb4ee6a4SAndroid Build Coastguard Worker    return Path(metadata["target_directory"])
49*bb4ee6a4SAndroid Build Coastguard Worker
50*bb4ee6a4SAndroid Build Coastguard Worker
51*bb4ee6a4SAndroid Build Coastguard Workerdef data_dir(arch: Arch):
52*bb4ee6a4SAndroid Build Coastguard Worker    return CACHE_DIR.joinpath("crosvm_tools").joinpath(arch)
53*bb4ee6a4SAndroid Build Coastguard Worker
54*bb4ee6a4SAndroid Build Coastguard Worker
55*bb4ee6a4SAndroid Build Coastguard Workerdef pid_path(arch: Arch):
56*bb4ee6a4SAndroid Build Coastguard Worker    return data_dir(arch).joinpath("pid")
57*bb4ee6a4SAndroid Build Coastguard Worker
58*bb4ee6a4SAndroid Build Coastguard Worker
59*bb4ee6a4SAndroid Build Coastguard Workerdef ssh_port_path(arch: Arch):
60*bb4ee6a4SAndroid Build Coastguard Worker    return data_dir(arch).joinpath("ssh_port")
61*bb4ee6a4SAndroid Build Coastguard Worker
62*bb4ee6a4SAndroid Build Coastguard Worker
63*bb4ee6a4SAndroid Build Coastguard Workerdef log_path(arch: Arch):
64*bb4ee6a4SAndroid Build Coastguard Worker    return data_dir(arch).joinpath("vm_log")
65*bb4ee6a4SAndroid Build Coastguard Worker
66*bb4ee6a4SAndroid Build Coastguard Worker
67*bb4ee6a4SAndroid Build Coastguard Workerdef base_img_name(arch: Arch):
68*bb4ee6a4SAndroid Build Coastguard Worker    return f"base-{arch}-{BASE_IMG_VERSION}.qcow2"
69*bb4ee6a4SAndroid Build Coastguard Worker
70*bb4ee6a4SAndroid Build Coastguard Worker
71*bb4ee6a4SAndroid Build Coastguard Workerdef base_img_url(arch: Arch):
72*bb4ee6a4SAndroid Build Coastguard Worker    return f"{IMAGE_DIR_URL}/{base_img_name(arch)}"
73*bb4ee6a4SAndroid Build Coastguard Worker
74*bb4ee6a4SAndroid Build Coastguard Worker
75*bb4ee6a4SAndroid Build Coastguard Workerdef base_img_path(arch: Arch):
76*bb4ee6a4SAndroid Build Coastguard Worker    return data_dir(arch).joinpath(base_img_name(arch))
77*bb4ee6a4SAndroid Build Coastguard Worker
78*bb4ee6a4SAndroid Build Coastguard Worker
79*bb4ee6a4SAndroid Build Coastguard Workerdef rootfs_img_path(arch: Arch):
80*bb4ee6a4SAndroid Build Coastguard Worker    return data_dir(arch).joinpath(f"rootfs-{arch}-{BASE_IMG_VERSION}.qcow2")
81*bb4ee6a4SAndroid Build Coastguard Worker
82*bb4ee6a4SAndroid Build Coastguard Worker
83*bb4ee6a4SAndroid Build Coastguard Workerdef ssh_port(arch: Arch) -> int:
84*bb4ee6a4SAndroid Build Coastguard Worker    # Default to fixed ports used by VMs started by previous versions of this script.
85*bb4ee6a4SAndroid Build Coastguard Worker    # TODO(b/275717656): Remove after a while
86*bb4ee6a4SAndroid Build Coastguard Worker    if not ssh_port_path(arch).exists():
87*bb4ee6a4SAndroid Build Coastguard Worker        return SSH_PORTS[arch]
88*bb4ee6a4SAndroid Build Coastguard Worker    return int(ssh_port_path(arch).read_text())
89*bb4ee6a4SAndroid Build Coastguard Worker
90*bb4ee6a4SAndroid Build Coastguard Worker
91*bb4ee6a4SAndroid Build Coastguard Workerssh = cmd("ssh")
92*bb4ee6a4SAndroid Build Coastguard Workerqemu_img = cmd("qemu-img")
93*bb4ee6a4SAndroid Build Coastguard Worker
94*bb4ee6a4SAndroid Build Coastguard Worker# List of ports to use for SSH for each architecture
95*bb4ee6a4SAndroid Build Coastguard Worker# TODO(b/275717656): Remove after a while
96*bb4ee6a4SAndroid Build Coastguard WorkerSSH_PORTS: Dict[Arch, int] = {
97*bb4ee6a4SAndroid Build Coastguard Worker    "x86_64": 9000,
98*bb4ee6a4SAndroid Build Coastguard Worker    "aarch64": 9001,
99*bb4ee6a4SAndroid Build Coastguard Worker}
100*bb4ee6a4SAndroid Build Coastguard Worker
101*bb4ee6a4SAndroid Build Coastguard Worker# QEMU arguments shared by all architectures
102*bb4ee6a4SAndroid Build Coastguard WorkerSHARED_ARGS: List[str] = [
103*bb4ee6a4SAndroid Build Coastguard Worker    "-display none",
104*bb4ee6a4SAndroid Build Coastguard Worker    "-device virtio-net-pci,netdev=net0",
105*bb4ee6a4SAndroid Build Coastguard Worker    "-smp 8",
106*bb4ee6a4SAndroid Build Coastguard Worker    "-m 4G",
107*bb4ee6a4SAndroid Build Coastguard Worker]
108*bb4ee6a4SAndroid Build Coastguard Worker
109*bb4ee6a4SAndroid Build Coastguard Worker# QEMU command for each architecture
110*bb4ee6a4SAndroid Build Coastguard WorkerARCH_TO_QEMU: Dict[Arch, cmd] = {
111*bb4ee6a4SAndroid Build Coastguard Worker    "x86_64": cmd(
112*bb4ee6a4SAndroid Build Coastguard Worker        "qemu-system-x86_64",
113*bb4ee6a4SAndroid Build Coastguard Worker        "-cpu host",
114*bb4ee6a4SAndroid Build Coastguard Worker        "-enable-kvm" if KVM_SUPPORT else None,
115*bb4ee6a4SAndroid Build Coastguard Worker        *SHARED_ARGS,
116*bb4ee6a4SAndroid Build Coastguard Worker    ),
117*bb4ee6a4SAndroid Build Coastguard Worker    "aarch64": cmd(
118*bb4ee6a4SAndroid Build Coastguard Worker        "qemu-system-aarch64",
119*bb4ee6a4SAndroid Build Coastguard Worker        "-M virt",
120*bb4ee6a4SAndroid Build Coastguard Worker        "-machine virt,virtualization=true,gic-version=3",
121*bb4ee6a4SAndroid Build Coastguard Worker        "-cpu cortex-a57",
122*bb4ee6a4SAndroid Build Coastguard Worker        "-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd",
123*bb4ee6a4SAndroid Build Coastguard Worker        *SHARED_ARGS,
124*bb4ee6a4SAndroid Build Coastguard Worker    ),
125*bb4ee6a4SAndroid Build Coastguard Worker}
126*bb4ee6a4SAndroid Build Coastguard Worker
127*bb4ee6a4SAndroid Build Coastguard Worker
128*bb4ee6a4SAndroid Build Coastguard Workerdef ssh_opts(arch: Arch) -> Dict[str, str]:
129*bb4ee6a4SAndroid Build Coastguard Worker    return {
130*bb4ee6a4SAndroid Build Coastguard Worker        "Port": str(ssh_port(arch)),
131*bb4ee6a4SAndroid Build Coastguard Worker        "User": "crosvm",
132*bb4ee6a4SAndroid Build Coastguard Worker        "StrictHostKeyChecking": "no",
133*bb4ee6a4SAndroid Build Coastguard Worker        "UserKnownHostsFile": "/dev/null",
134*bb4ee6a4SAndroid Build Coastguard Worker        "LogLevel": "ERROR",
135*bb4ee6a4SAndroid Build Coastguard Worker        "IdentityFile": str(ID_RSA),
136*bb4ee6a4SAndroid Build Coastguard Worker    }
137*bb4ee6a4SAndroid Build Coastguard Worker
138*bb4ee6a4SAndroid Build Coastguard Worker
139*bb4ee6a4SAndroid Build Coastguard Workerdef ssh_cmd_args(arch: Arch):
140*bb4ee6a4SAndroid Build Coastguard Worker    return [f"-o{k}={v}" for k, v in ssh_opts(arch).items()]
141*bb4ee6a4SAndroid Build Coastguard Worker
142*bb4ee6a4SAndroid Build Coastguard Worker
143*bb4ee6a4SAndroid Build Coastguard Workerdef ssh_exec(arch: Arch, cmd: Optional[str] = None):
144*bb4ee6a4SAndroid Build Coastguard Worker    os.chmod(ID_RSA, 0o600)
145*bb4ee6a4SAndroid Build Coastguard Worker    ssh.with_args(
146*bb4ee6a4SAndroid Build Coastguard Worker        "localhost",
147*bb4ee6a4SAndroid Build Coastguard Worker        *ssh_cmd_args(arch),
148*bb4ee6a4SAndroid Build Coastguard Worker        *(["-T", cmd] if cmd else []),
149*bb4ee6a4SAndroid Build Coastguard Worker    ).fg(check=False)
150*bb4ee6a4SAndroid Build Coastguard Worker
151*bb4ee6a4SAndroid Build Coastguard Worker
152*bb4ee6a4SAndroid Build Coastguard Workerdef ping_vm(arch: Arch):
153*bb4ee6a4SAndroid Build Coastguard Worker    os.chmod(ID_RSA, 0o600)
154*bb4ee6a4SAndroid Build Coastguard Worker    return ssh(
155*bb4ee6a4SAndroid Build Coastguard Worker        "localhost",
156*bb4ee6a4SAndroid Build Coastguard Worker        *ssh_cmd_args(arch),
157*bb4ee6a4SAndroid Build Coastguard Worker        "-oConnectTimeout=1",
158*bb4ee6a4SAndroid Build Coastguard Worker        "-T exit",
159*bb4ee6a4SAndroid Build Coastguard Worker    ).success()
160*bb4ee6a4SAndroid Build Coastguard Worker
161*bb4ee6a4SAndroid Build Coastguard Worker
162*bb4ee6a4SAndroid Build Coastguard Workerdef write_pid_file(arch: Arch, pid: int):
163*bb4ee6a4SAndroid Build Coastguard Worker    with open(pid_path(arch), "w") as pid_file:
164*bb4ee6a4SAndroid Build Coastguard Worker        pid_file.write(str(pid))
165*bb4ee6a4SAndroid Build Coastguard Worker
166*bb4ee6a4SAndroid Build Coastguard Worker
167*bb4ee6a4SAndroid Build Coastguard Workerdef read_pid_file(arch: Arch):
168*bb4ee6a4SAndroid Build Coastguard Worker    if not pid_path(arch).exists():
169*bb4ee6a4SAndroid Build Coastguard Worker        return None
170*bb4ee6a4SAndroid Build Coastguard Worker
171*bb4ee6a4SAndroid Build Coastguard Worker    with open(pid_path(arch), "r") as pid_file:
172*bb4ee6a4SAndroid Build Coastguard Worker        return int(pid_file.read())
173*bb4ee6a4SAndroid Build Coastguard Worker
174*bb4ee6a4SAndroid Build Coastguard Worker
175*bb4ee6a4SAndroid Build Coastguard Workerdef is_port_available(port: int):
176*bb4ee6a4SAndroid Build Coastguard Worker    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
177*bb4ee6a4SAndroid Build Coastguard Worker        return sock.connect_ex(("127.0.0.1", port)) != 0
178*bb4ee6a4SAndroid Build Coastguard Worker
179*bb4ee6a4SAndroid Build Coastguard Worker
180*bb4ee6a4SAndroid Build Coastguard Workerdef pick_ssh_port():
181*bb4ee6a4SAndroid Build Coastguard Worker    for _ in range(5):
182*bb4ee6a4SAndroid Build Coastguard Worker        port = randrange(1024, 32768)
183*bb4ee6a4SAndroid Build Coastguard Worker        if is_port_available(port):
184*bb4ee6a4SAndroid Build Coastguard Worker            return port
185*bb4ee6a4SAndroid Build Coastguard Worker    raise Exception("Could not find a free port")
186*bb4ee6a4SAndroid Build Coastguard Worker
187*bb4ee6a4SAndroid Build Coastguard Worker
188*bb4ee6a4SAndroid Build Coastguard Workerdef run_qemu(
189*bb4ee6a4SAndroid Build Coastguard Worker    arch: Arch,
190*bb4ee6a4SAndroid Build Coastguard Worker    hda: Path,
191*bb4ee6a4SAndroid Build Coastguard Worker    background: bool = False,
192*bb4ee6a4SAndroid Build Coastguard Worker):
193*bb4ee6a4SAndroid Build Coastguard Worker    port = pick_ssh_port()
194*bb4ee6a4SAndroid Build Coastguard Worker
195*bb4ee6a4SAndroid Build Coastguard Worker    qemu = ARCH_TO_QEMU[arch]
196*bb4ee6a4SAndroid Build Coastguard Worker    if background:
197*bb4ee6a4SAndroid Build Coastguard Worker        serial = f"file:{data_dir(arch).joinpath('vm_log')}"
198*bb4ee6a4SAndroid Build Coastguard Worker    else:
199*bb4ee6a4SAndroid Build Coastguard Worker        serial = "stdio"
200*bb4ee6a4SAndroid Build Coastguard Worker
201*bb4ee6a4SAndroid Build Coastguard Worker    console.print(f"Booting {arch} VM with disk", hda)
202*bb4ee6a4SAndroid Build Coastguard Worker    command = qemu.with_args(
203*bb4ee6a4SAndroid Build Coastguard Worker        f"-hda {hda}",
204*bb4ee6a4SAndroid Build Coastguard Worker        f"-serial {serial}",
205*bb4ee6a4SAndroid Build Coastguard Worker        f"-netdev user,id=net0,hostfwd=tcp::{port}-:22",
206*bb4ee6a4SAndroid Build Coastguard Worker    )
207*bb4ee6a4SAndroid Build Coastguard Worker    if background:
208*bb4ee6a4SAndroid Build Coastguard Worker        # Start qemu in a new session so it can outlive this process.
209*bb4ee6a4SAndroid Build Coastguard Worker        process = command.popen(
210*bb4ee6a4SAndroid Build Coastguard Worker            start_new_session=background, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
211*bb4ee6a4SAndroid Build Coastguard Worker        )
212*bb4ee6a4SAndroid Build Coastguard Worker
213*bb4ee6a4SAndroid Build Coastguard Worker        # Wait for 1s to see if the qemu is staying alive.
214*bb4ee6a4SAndroid Build Coastguard Worker        assert process.stdout
215*bb4ee6a4SAndroid Build Coastguard Worker        for _ in range(10):
216*bb4ee6a4SAndroid Build Coastguard Worker            if process.poll() is not None:
217*bb4ee6a4SAndroid Build Coastguard Worker                sys.stdout.write(process.stdout.read())
218*bb4ee6a4SAndroid Build Coastguard Worker                print(f"'{command}' exited with code {process.returncode}")
219*bb4ee6a4SAndroid Build Coastguard Worker                sys.exit(process.returncode)
220*bb4ee6a4SAndroid Build Coastguard Worker            time.sleep(0.1)
221*bb4ee6a4SAndroid Build Coastguard Worker
222*bb4ee6a4SAndroid Build Coastguard Worker        # Print any warnings qemu might produce.
223*bb4ee6a4SAndroid Build Coastguard Worker        sys.stdout.write(process.stdout.read(0))
224*bb4ee6a4SAndroid Build Coastguard Worker        sys.stdout.flush()
225*bb4ee6a4SAndroid Build Coastguard Worker        process.stdout.close()
226*bb4ee6a4SAndroid Build Coastguard Worker
227*bb4ee6a4SAndroid Build Coastguard Worker        # Save port and pid so we can manage the process later.
228*bb4ee6a4SAndroid Build Coastguard Worker        ssh_port_path(arch).write_text(str(port))
229*bb4ee6a4SAndroid Build Coastguard Worker        write_pid_file(arch, process.pid)
230*bb4ee6a4SAndroid Build Coastguard Worker    else:
231*bb4ee6a4SAndroid Build Coastguard Worker        command.fg()
232*bb4ee6a4SAndroid Build Coastguard Worker
233*bb4ee6a4SAndroid Build Coastguard Worker
234*bb4ee6a4SAndroid Build Coastguard Workerdef run_vm(arch: Arch, background: bool = False):
235*bb4ee6a4SAndroid Build Coastguard Worker    run_qemu(
236*bb4ee6a4SAndroid Build Coastguard Worker        arch,
237*bb4ee6a4SAndroid Build Coastguard Worker        rootfs_img_path(arch),
238*bb4ee6a4SAndroid Build Coastguard Worker        background=background,
239*bb4ee6a4SAndroid Build Coastguard Worker    )
240*bb4ee6a4SAndroid Build Coastguard Worker
241*bb4ee6a4SAndroid Build Coastguard Worker
242*bb4ee6a4SAndroid Build Coastguard Workerdef is_running(arch: Arch):
243*bb4ee6a4SAndroid Build Coastguard Worker    pid = read_pid_file(arch)
244*bb4ee6a4SAndroid Build Coastguard Worker    if pid is None:
245*bb4ee6a4SAndroid Build Coastguard Worker        return False
246*bb4ee6a4SAndroid Build Coastguard Worker
247*bb4ee6a4SAndroid Build Coastguard Worker    # Send signal 0 to check if the process is alive
248*bb4ee6a4SAndroid Build Coastguard Worker    try:
249*bb4ee6a4SAndroid Build Coastguard Worker        os.kill(pid, 0)
250*bb4ee6a4SAndroid Build Coastguard Worker    except OSError:
251*bb4ee6a4SAndroid Build Coastguard Worker        return False
252*bb4ee6a4SAndroid Build Coastguard Worker    return True
253*bb4ee6a4SAndroid Build Coastguard Worker
254*bb4ee6a4SAndroid Build Coastguard Worker
255*bb4ee6a4SAndroid Build Coastguard Workerdef kill_vm(arch: Arch):
256*bb4ee6a4SAndroid Build Coastguard Worker    pid = read_pid_file(arch)
257*bb4ee6a4SAndroid Build Coastguard Worker    if pid:
258*bb4ee6a4SAndroid Build Coastguard Worker        try:
259*bb4ee6a4SAndroid Build Coastguard Worker            os.kill(pid, 9)
260*bb4ee6a4SAndroid Build Coastguard Worker            # Ping with signal 0 until we get an OSError indicating the process has shutdown.
261*bb4ee6a4SAndroid Build Coastguard Worker            while True:
262*bb4ee6a4SAndroid Build Coastguard Worker                os.kill(pid, 0)
263*bb4ee6a4SAndroid Build Coastguard Worker        except OSError:
264*bb4ee6a4SAndroid Build Coastguard Worker            return
265*bb4ee6a4SAndroid Build Coastguard Worker
266*bb4ee6a4SAndroid Build Coastguard Worker
267*bb4ee6a4SAndroid Build Coastguard Workerdef build_if_needed(arch: Arch, reset: bool = False):
268*bb4ee6a4SAndroid Build Coastguard Worker    if reset and is_running(arch):
269*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Killing existing {arch} VM to perform reset...")
270*bb4ee6a4SAndroid Build Coastguard Worker        kill_vm(arch)
271*bb4ee6a4SAndroid Build Coastguard Worker        time.sleep(1)
272*bb4ee6a4SAndroid Build Coastguard Worker
273*bb4ee6a4SAndroid Build Coastguard Worker    data_dir(arch).mkdir(parents=True, exist_ok=True)
274*bb4ee6a4SAndroid Build Coastguard Worker
275*bb4ee6a4SAndroid Build Coastguard Worker    base_img = base_img_path(arch)
276*bb4ee6a4SAndroid Build Coastguard Worker    if not base_img.exists():
277*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Downloading {arch} base image ({base_img_url(arch)})...")
278*bb4ee6a4SAndroid Build Coastguard Worker        download_file(base_img_url(arch), base_img_path(arch))
279*bb4ee6a4SAndroid Build Coastguard Worker
280*bb4ee6a4SAndroid Build Coastguard Worker    rootfs_img = rootfs_img_path(arch)
281*bb4ee6a4SAndroid Build Coastguard Worker    if not rootfs_img.exists() or reset:
282*bb4ee6a4SAndroid Build Coastguard Worker        # The rootfs is backed by the base image generated above. So we can
283*bb4ee6a4SAndroid Build Coastguard Worker        # easily reset to a clean VM by rebuilding an empty rootfs image.
284*bb4ee6a4SAndroid Build Coastguard Worker        print(f"Creating {arch} rootfs overlay...")
285*bb4ee6a4SAndroid Build Coastguard Worker        qemu_img.with_args(
286*bb4ee6a4SAndroid Build Coastguard Worker            "create",
287*bb4ee6a4SAndroid Build Coastguard Worker            "-f qcow2",
288*bb4ee6a4SAndroid Build Coastguard Worker            "-F qcow2",
289*bb4ee6a4SAndroid Build Coastguard Worker            f"-b {base_img}",
290*bb4ee6a4SAndroid Build Coastguard Worker            rootfs_img,
291*bb4ee6a4SAndroid Build Coastguard Worker            "8G",
292*bb4ee6a4SAndroid Build Coastguard Worker        ).fg(quiet=True)
293*bb4ee6a4SAndroid Build Coastguard Worker
294*bb4ee6a4SAndroid Build Coastguard Worker
295*bb4ee6a4SAndroid Build Coastguard Workerdef up(arch: Arch, reset: bool = False, wait: bool = False, timeout: int = 120):
296*bb4ee6a4SAndroid Build Coastguard Worker    "Starts the test vm if it's not already running. Optionally wait for it to be reachable."
297*bb4ee6a4SAndroid Build Coastguard Worker
298*bb4ee6a4SAndroid Build Coastguard Worker    # Try waiting for the running VM, if it does not become reachable, kill it.
299*bb4ee6a4SAndroid Build Coastguard Worker    if is_running(arch):
300*bb4ee6a4SAndroid Build Coastguard Worker        if not wait:
301*bb4ee6a4SAndroid Build Coastguard Worker            console.print(f"{arch} VM is running on port {ssh_port(arch)}")
302*bb4ee6a4SAndroid Build Coastguard Worker            return
303*bb4ee6a4SAndroid Build Coastguard Worker        if not wait_until_reachable(arch, timeout):
304*bb4ee6a4SAndroid Build Coastguard Worker            if is_running(arch):
305*bb4ee6a4SAndroid Build Coastguard Worker                print(f"{arch} VM is not reachable. Restarting it.")
306*bb4ee6a4SAndroid Build Coastguard Worker                kill_vm(arch)
307*bb4ee6a4SAndroid Build Coastguard Worker            else:
308*bb4ee6a4SAndroid Build Coastguard Worker                print(f"{arch} VM stopped. Starting it again.")
309*bb4ee6a4SAndroid Build Coastguard Worker        else:
310*bb4ee6a4SAndroid Build Coastguard Worker            console.print(f"{arch} VM is running on port {ssh_port(arch)}")
311*bb4ee6a4SAndroid Build Coastguard Worker            return
312*bb4ee6a4SAndroid Build Coastguard Worker
313*bb4ee6a4SAndroid Build Coastguard Worker    build_if_needed(arch, reset)
314*bb4ee6a4SAndroid Build Coastguard Worker    run_qemu(
315*bb4ee6a4SAndroid Build Coastguard Worker        arch,
316*bb4ee6a4SAndroid Build Coastguard Worker        rootfs_img_path(arch),
317*bb4ee6a4SAndroid Build Coastguard Worker        background=True,
318*bb4ee6a4SAndroid Build Coastguard Worker    )
319*bb4ee6a4SAndroid Build Coastguard Worker
320*bb4ee6a4SAndroid Build Coastguard Worker    if wait:
321*bb4ee6a4SAndroid Build Coastguard Worker        if wait_until_reachable(arch, timeout):
322*bb4ee6a4SAndroid Build Coastguard Worker            console.print(f"{arch} VM is running on port {ssh_port(arch)}")
323*bb4ee6a4SAndroid Build Coastguard Worker        else:
324*bb4ee6a4SAndroid Build Coastguard Worker            raise Exception(f"Waiting for {arch} VM timed out.")
325*bb4ee6a4SAndroid Build Coastguard Worker
326*bb4ee6a4SAndroid Build Coastguard Worker
327*bb4ee6a4SAndroid Build Coastguard Workerdef wait_until_reachable(arch: Arch, timeout: int = 120):
328*bb4ee6a4SAndroid Build Coastguard Worker    "Blocks until the VM is ready to use."
329*bb4ee6a4SAndroid Build Coastguard Worker    if not is_running(arch):
330*bb4ee6a4SAndroid Build Coastguard Worker        return False
331*bb4ee6a4SAndroid Build Coastguard Worker    if ping_vm(arch):
332*bb4ee6a4SAndroid Build Coastguard Worker        return True
333*bb4ee6a4SAndroid Build Coastguard Worker
334*bb4ee6a4SAndroid Build Coastguard Worker    with rich.live.Live(
335*bb4ee6a4SAndroid Build Coastguard Worker        rich.spinner.Spinner("point", f"Waiting for {arch} VM to become reachable...")
336*bb4ee6a4SAndroid Build Coastguard Worker    ):
337*bb4ee6a4SAndroid Build Coastguard Worker        start_time = time.time()
338*bb4ee6a4SAndroid Build Coastguard Worker        while (time.time() - start_time) < timeout:
339*bb4ee6a4SAndroid Build Coastguard Worker            if not is_running(arch):
340*bb4ee6a4SAndroid Build Coastguard Worker                return False
341*bb4ee6a4SAndroid Build Coastguard Worker            if ping_vm(arch):
342*bb4ee6a4SAndroid Build Coastguard Worker                return True
343*bb4ee6a4SAndroid Build Coastguard Worker    return False
344*bb4ee6a4SAndroid Build Coastguard Worker
345*bb4ee6a4SAndroid Build Coastguard Worker
346*bb4ee6a4SAndroid Build Coastguard Workerclass VmState(Enum):
347*bb4ee6a4SAndroid Build Coastguard Worker    REACHABLE = "Reachable"
348*bb4ee6a4SAndroid Build Coastguard Worker    RUNNING_NOT_REACHABLE = "Running, but not reachable"
349*bb4ee6a4SAndroid Build Coastguard Worker    STOPPED = "Stopped"
350*bb4ee6a4SAndroid Build Coastguard Worker
351*bb4ee6a4SAndroid Build Coastguard Worker
352*bb4ee6a4SAndroid Build Coastguard Workerdef state(arch: Arch):
353*bb4ee6a4SAndroid Build Coastguard Worker    if is_running(arch):
354*bb4ee6a4SAndroid Build Coastguard Worker        if ping_vm(arch):
355*bb4ee6a4SAndroid Build Coastguard Worker            return VmState.REACHABLE
356*bb4ee6a4SAndroid Build Coastguard Worker        else:
357*bb4ee6a4SAndroid Build Coastguard Worker            return VmState.RUNNING_NOT_REACHABLE
358*bb4ee6a4SAndroid Build Coastguard Worker    else:
359*bb4ee6a4SAndroid Build Coastguard Worker        return VmState.STOPPED
360