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