#!/usr/bin/env python3 # Copyright 2021 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import shutil from typing import Iterable, Optional from impl.common import run_commands, argh, console, strip_ansi_escape_sequences from impl import testvm from impl.testvm import Arch, VmState USAGE = """Manages VMs for testing crosvm. Can run an x86_64 and an aarch64 vm via `./tools/x86vm` and `./tools/aarch64vm`. Both are a wrapper around `./tools/testvm --arch=x86_64/aarch64`. The easiest way to use the VM is: $ ./tools/aarch64vm ssh Which will initialize and boot the VM, then wait for SSH to be available and opens an SSH session. The VM will stay alive between calls. Available commands are: - up: Start the VM if it is not already running. - stop: Gracefully stop the VM - kill: Send SIGKILL to the VM - clean: Stop the VM and delete all images - logs: Print logs of the VM console All of these can be called on `./tools/x86vm` or `./tools/aarch64vm`, but also on `tools/testvm` to apply to both VMs. """ def cli_shorthand(arch: Arch): if arch == "x86_64": return "tools/x86vm" elif arch == "aarch64": return "tools/aarch64vm" else: raise Exception(f"Unknown architecture: {arch}") def arch_or_all(arch: Optional[Arch]): return (arch,) if arch else testvm.ARCH_OPTIONS ARCHS = testvm.ARCH_OPTIONS @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS) def up(arch_list: Iterable[Arch] = [], reset: bool = False, wait: bool = False, timeout: int = 120): "Start the VM if it's not already running." for arch in arch_list: testvm.up(arch, reset, wait, timeout) @argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS) def run(arch: Arch = "x86_64", reset: bool = False): "Run the VM in foreground for debugging purposes." if testvm.is_running(arch): raise Exception("VM is already running") testvm.build_if_needed(arch, reset) testvm.run_qemu( arch, testvm.rootfs_img_path(arch), background=False, ) @argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS) def shell(arch: Arch = "x86_64", timeout: int = 120): "Starts an interactive shell via SSH, will ensure the VM is running." testvm.up(arch, wait=True, timeout=timeout) testvm.ssh_exec(arch) @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS) def stop(arch_list: Iterable[Arch] = []): "Gracefully stops the running VM." for arch in arch_list: if not testvm.is_running(arch): print(f"{arch} VM is not running") break console.print(f"Stopping {arch} VM") testvm.ssh_exec(arch, "sudo poweroff") @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS) def kill(arch_list: Iterable[Arch] = []): "Kills the running VM with SIGKILL." for arch in arch_list: if not testvm.is_running(arch): console.print(f"{arch} VM is not running") break console.print(f"Killing {arch} VM process") testvm.kill_vm(arch) print() @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS) def clean(arch_list: Iterable[Arch] = []): "Stops the VM or VMs and deletes all data." for arch in arch_list: if testvm.is_running(arch): kill(arch) if testvm.data_dir(arch).exists(): console.print("Cleaning data directory", testvm.data_dir(arch)) shutil.rmtree(testvm.data_dir(arch)) print() def vm_status(arch: Arch): def cli_tip(*args: str): return f"[green][bold]{cli_shorthand(arch)} {' '.join(args)}[/bold][/green]" vm = f"{arch} VM" port = f"[blue]{testvm.SSH_PORTS[arch]}[/blue]" state = testvm.state(arch) if state == VmState.REACHABLE: console.print(f"{vm} is [green]reachable[/green] on port {port}") console.print(f"Start a shell with {cli_tip('shell')}") elif state == VmState.STOPPED: console.print(f"{vm} is [red]stopped[/red]") console.print(f"Start the VM with {cli_tip('up')}") else: console.print(f"{vm} is running but [red]not reachable[/red] on port {port}") console.print(f"Recent logs:") logs(arch, 10, style="light_slate_grey") console.print(f"See all logs with {cli_tip('logs')}") @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS) def status(arch_list: Iterable[Arch] = []): for arch in arch_list: vm_status(arch) print() @argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS) def logs(arch: Arch = "x86_64", n: int = 0, style: Optional[str] = None): log_lines = testvm.log_path(arch).read_text().splitlines() if n > 0 and len(log_lines) > n: log_lines = log_lines[-n:] for line in log_lines: if style: console.print( strip_ansi_escape_sequences(line), style=style, markup=False, highlight=False ) else: print(line) if __name__ == "__main__": run_commands(up, run, shell, stop, kill, clean, status, logs, usage=USAGE, default_fn=status)