testvm: Refactor to use argh
The code has been written before we used argh. The CLI is specified in tools/testvm. The implementation remains in impl/testvm.py so it can be shared with run_tests.py for automatically managing the VM when running tests. BUG=b:275717759 TEST=x86vm run/up/wait/stop TEST=run_tests --dut=vm Change-Id: I2bdb554e7ed17a9f397fd8fb02f754e4ec1e52d7 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4390729 Commit-Queue: Dennis Kempin <denniskempin@google.com> Reviewed-by: Zihan Chen <zihanchen@google.com>
This commit is contained in:
parent
86df65f6d2
commit
6edc4e86d2
6 changed files with 109 additions and 151 deletions
|
|
@ -3,8 +3,10 @@
|
|||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
from impl.testvm import main
|
||||
import sys
|
||||
from impl.common import TOOLS_ROOT, cmd
|
||||
|
||||
if __name__ == "__main__":
|
||||
main("aarch64", sys.argv[1:])
|
||||
sys.exit(
|
||||
cmd(sys.executable, TOOLS_ROOT / "testvm", *sys.argv[1:], "--arch=aarch64").fg(check=False)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ import rich.live
|
|||
import rich.spinner
|
||||
import rich.text
|
||||
|
||||
# Hack: argh does not support type annotations. This prevents type errors.
|
||||
argh: Any # type: ignore
|
||||
|
||||
# File where to store http headers for gcloud authentication
|
||||
AUTH_HEADERS_FILE = Path(gettempdir()) / f"crosvm_gcloud_auth_headers_{getpass.getuser()}"
|
||||
|
|
|
|||
|
|
@ -2,65 +2,24 @@
|
|||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request as request
|
||||
import typing
|
||||
from contextlib import closing
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Literal, Optional, Tuple
|
||||
|
||||
from .common import CACHE_DIR, download_file
|
||||
|
||||
USAGE = """%(prog)s {command} [options]
|
||||
|
||||
Manages VMs for testing crosvm.
|
||||
|
||||
Can run an x86_64 and an aarch64 vm via `./tools/x86vm` and `./tools/aarch64vm`.
|
||||
The VM image will be downloaded and initialized on first use.
|
||||
|
||||
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.
|
||||
|
||||
Alternatively, you can set up an SSH config to connect to the VM. First ensure
|
||||
the VM ready:
|
||||
|
||||
$ ./tools/aarch64vm wait
|
||||
|
||||
Then append the VMs ssh config to your SSH config:
|
||||
|
||||
$ ./tools/aarch64vm ssh_config >> ~/.ssh/config
|
||||
|
||||
And connect as usual:
|
||||
|
||||
$ ssh crosvm_$ARCH
|
||||
|
||||
Commands:
|
||||
|
||||
build: Download base image and create rootfs overlay.
|
||||
up: Ensure that the VM is running in the background.
|
||||
run: Run the VM in the foreground process for debugging.
|
||||
wait: Boot the VM if it's offline and wait until it's available.
|
||||
ssh: SSH into the VM. Boot and wait if it's not available.
|
||||
ssh_config: Prints the ssh config needed to connnect to the VM.
|
||||
stop: Gracefully shutdown the VM.
|
||||
kill: Kill the QEMU process. Might damage the image file.
|
||||
clean: Stop all VMs and delete all data.
|
||||
"""
|
||||
|
||||
KVM_SUPPORT = os.access("/dev/kvm", os.W_OK)
|
||||
|
||||
Arch = Literal["x86_64", "aarch64"]
|
||||
ARCH_OPTIONS = typing.get_args(Arch)
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent.resolve()
|
||||
SRC_DIR = SCRIPT_DIR.joinpath("testvm")
|
||||
|
|
@ -302,10 +261,12 @@ def is_ssh_port_available(arch: Arch):
|
|||
return sock.connect_ex(("127.0.0.1", SSH_PORTS[arch])) != 0
|
||||
|
||||
|
||||
def up(arch: Arch):
|
||||
def up(arch: Arch, reset: bool = False):
|
||||
"Start the VM if it's not already running."
|
||||
if is_running(arch):
|
||||
return
|
||||
|
||||
build_if_needed(arch, reset)
|
||||
print("Booting VM...")
|
||||
run_qemu(
|
||||
arch,
|
||||
|
|
@ -314,20 +275,10 @@ def up(arch: Arch):
|
|||
)
|
||||
|
||||
|
||||
def run(arch: Arch):
|
||||
if is_running(arch):
|
||||
raise Exception("VM is already running")
|
||||
run_qemu(
|
||||
arch,
|
||||
rootfs_img_path(arch),
|
||||
background=False,
|
||||
)
|
||||
|
||||
|
||||
def wait(arch: Arch, timeout: int = 120):
|
||||
if not is_running(arch):
|
||||
up(arch)
|
||||
elif ping_vm(arch):
|
||||
"Blocks until the VM is ready to use."
|
||||
up(arch)
|
||||
if ping_vm(arch):
|
||||
return
|
||||
|
||||
print("Waiting for VM")
|
||||
|
|
@ -340,92 +291,3 @@ def wait(arch: Arch, timeout: int = 120):
|
|||
print()
|
||||
return
|
||||
raise Exception("Timeout while waiting for VM")
|
||||
|
||||
|
||||
def ssh(arch: Arch, timeout: int):
|
||||
wait(arch, timeout)
|
||||
ssh_exec(arch)
|
||||
|
||||
|
||||
def ssh_config(arch: Arch):
|
||||
print(f"Host crosvm_{arch}")
|
||||
print(f" HostName localhost")
|
||||
for opt, value in ssh_opts(arch).items():
|
||||
print(f" {opt} {value}")
|
||||
|
||||
|
||||
def stop(arch: Arch):
|
||||
if not is_running(arch):
|
||||
print("VM is not running.")
|
||||
return
|
||||
ssh_exec(arch, "sudo poweroff")
|
||||
|
||||
|
||||
def kill(arch: Arch):
|
||||
if not is_running(arch):
|
||||
print("VM is not running.")
|
||||
return
|
||||
kill_vm(arch)
|
||||
|
||||
|
||||
def clean(arch: Arch):
|
||||
if is_running(arch):
|
||||
kill(arch)
|
||||
if data_dir(arch).exists():
|
||||
shutil.rmtree(data_dir(arch))
|
||||
|
||||
|
||||
def main(arch: Arch, argv: List[str]):
|
||||
COMMANDS = [
|
||||
"build",
|
||||
"up",
|
||||
"run",
|
||||
"wait",
|
||||
"ssh",
|
||||
"ssh_config",
|
||||
"stop",
|
||||
"kill",
|
||||
"clean",
|
||||
]
|
||||
parser = argparse.ArgumentParser(usage=USAGE)
|
||||
parser.add_argument("command", choices=COMMANDS)
|
||||
parser.add_argument(
|
||||
"--reset",
|
||||
action="store_true",
|
||||
help="Reset VM image to a fresh state. Removes all user modifications.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
type=int,
|
||||
default=60,
|
||||
help="Timeout in seconds while waiting for the VM to come up.",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.command == "clean":
|
||||
clean(arch)
|
||||
return
|
||||
|
||||
if args.command == "ssh_config":
|
||||
ssh_config(arch)
|
||||
return
|
||||
|
||||
# Ensure the images are built regardless of which command we execute.
|
||||
build_if_needed(arch, reset=args.reset)
|
||||
|
||||
if args.command == "build":
|
||||
return # Nothing left to do.
|
||||
elif args.command == "run":
|
||||
run(arch)
|
||||
elif args.command == "up":
|
||||
up(arch)
|
||||
elif args.command == "ssh":
|
||||
ssh(arch, args.timeout)
|
||||
elif args.command == "stop":
|
||||
stop(arch)
|
||||
elif args.command == "kill":
|
||||
kill(arch)
|
||||
elif args.command == "wait":
|
||||
wait(arch, args.timeout)
|
||||
else:
|
||||
print(f"Unknown command {args.command}")
|
||||
|
|
|
|||
|
|
@ -287,7 +287,6 @@ def main(
|
|||
if dut == "host":
|
||||
check_host_prerequisites(run_root_tests)
|
||||
if dut == "vm":
|
||||
testvm.build_if_needed(get_vm_arch(triple))
|
||||
testvm.up(get_vm_arch(triple))
|
||||
|
||||
nextest_args = [
|
||||
|
|
|
|||
91
tools/testvm
Executable file
91
tools/testvm
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
#!/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 impl.common import run_commands, argh
|
||||
from impl import testvm
|
||||
|
||||
USAGE = """Manages VMs for testing crosvm.
|
||||
|
||||
Can run an x86_64 and an aarch64 vm via `./tools/x86vm` and `./tools/aarch64vm`.
|
||||
The VM image will be downloaded and initialized on first use.
|
||||
|
||||
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.
|
||||
|
||||
Alternatively, you can set up an SSH config to connect to the VM. First ensure
|
||||
the VM ready:
|
||||
|
||||
$ ./tools/aarch64vm wait
|
||||
"""
|
||||
|
||||
|
||||
@argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
|
||||
def up(arch: testvm.Arch = "x86_64", reset: bool = False):
|
||||
"Start the VM if it's not already running."
|
||||
testvm.up(arch, reset)
|
||||
|
||||
|
||||
@argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
|
||||
def run(arch: testvm.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 wait(arch: testvm.Arch = "x86_64", timeout: int = 120):
|
||||
"Blocks until the VM is ready to use."
|
||||
testvm.wait(arch, timeout)
|
||||
|
||||
|
||||
@argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
|
||||
def ssh(arch: testvm.Arch = "x86_64", timeout: int = 120):
|
||||
"Starts an interactive shell via SSH, will ensure the VM is running."
|
||||
testvm.up(arch)
|
||||
wait(arch, timeout)
|
||||
testvm.ssh_exec(arch)
|
||||
|
||||
|
||||
@argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
|
||||
def stop(arch: testvm.Arch = "x86_64"):
|
||||
"Gracefully stops the running VM."
|
||||
if not testvm.is_running(arch):
|
||||
print("VM is not running.")
|
||||
return
|
||||
testvm.ssh_exec(arch, "sudo poweroff")
|
||||
|
||||
|
||||
@argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
|
||||
def kill(arch: testvm.Arch = "x86_64"):
|
||||
"Kills the running VM with SIGKILL."
|
||||
if not testvm.is_running(arch):
|
||||
print("VM is not running.")
|
||||
return
|
||||
testvm.kill_vm(arch)
|
||||
|
||||
|
||||
@argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
|
||||
def clean(arch: testvm.Arch = "x86_64"):
|
||||
"Stops the VM and deletes all data."
|
||||
if testvm.is_running(arch):
|
||||
kill(arch)
|
||||
if testvm.data_dir(arch).exists():
|
||||
shutil.rmtree(testvm.data_dir(arch))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_commands(up, run, wait, ssh, stop, kill, clean, usage=USAGE)
|
||||
|
|
@ -3,8 +3,10 @@
|
|||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
from impl.testvm import main
|
||||
import sys
|
||||
from impl.common import TOOLS_ROOT, cmd
|
||||
|
||||
if __name__ == "__main__":
|
||||
main("x86_64", sys.argv[1:])
|
||||
sys.exit(
|
||||
cmd(sys.executable, TOOLS_ROOT / "testvm", *sys.argv[1:], "--arch=x86_64").fg(check=False)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue