testvm 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #!/usr/bin/env python3
  2. # Copyright 2021 The ChromiumOS Authors
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import shutil
  6. from typing import Iterable, Optional
  7. from impl.common import run_commands, argh, console, strip_ansi_escape_sequences
  8. from impl import testvm
  9. from impl.testvm import Arch, VmState
  10. USAGE = """Manages VMs for testing crosvm.
  11. Can run an x86_64 and an aarch64 vm via `./tools/x86vm` and `./tools/aarch64vm`.
  12. Both are a wrapper around `./tools/testvm --arch=x86_64/aarch64`.
  13. The easiest way to use the VM is:
  14. $ ./tools/aarch64vm ssh
  15. Which will initialize and boot the VM, then wait for SSH to be available and
  16. opens an SSH session. The VM will stay alive between calls.
  17. Available commands are:
  18. - up: Start the VM if it is not already running.
  19. - stop: Gracefully stop the VM
  20. - kill: Send SIGKILL to the VM
  21. - clean: Stop the VM and delete all images
  22. - logs: Print logs of the VM console
  23. All of these can be called on `./tools/x86vm` or `./tools/aarch64vm`, but also on
  24. `tools/testvm` to apply to both VMs.
  25. """
  26. def cli_shorthand(arch: Arch):
  27. if arch == "x86_64":
  28. return "tools/x86vm"
  29. elif arch == "aarch64":
  30. return "tools/aarch64vm"
  31. else:
  32. raise Exception(f"Unknown architecture: {arch}")
  33. def arch_or_all(arch: Optional[Arch]):
  34. return (arch,) if arch else testvm.ARCH_OPTIONS
  35. ARCHS = testvm.ARCH_OPTIONS
  36. @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS)
  37. def up(arch_list: Iterable[Arch] = [], reset: bool = False, wait: bool = False, timeout: int = 120):
  38. "Start the VM if it's not already running."
  39. for arch in arch_list:
  40. testvm.up(arch, reset, wait, timeout)
  41. @argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
  42. def run(arch: Arch = "x86_64", reset: bool = False):
  43. "Run the VM in foreground for debugging purposes."
  44. if testvm.is_running(arch):
  45. raise Exception("VM is already running")
  46. testvm.build_if_needed(arch, reset)
  47. testvm.run_qemu(
  48. arch,
  49. testvm.rootfs_img_path(arch),
  50. background=False,
  51. )
  52. @argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
  53. def shell(arch: Arch = "x86_64", timeout: int = 120):
  54. "Starts an interactive shell via SSH, will ensure the VM is running."
  55. testvm.up(arch, wait=True, timeout=timeout)
  56. testvm.ssh_exec(arch)
  57. @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS)
  58. def stop(arch_list: Iterable[Arch] = []):
  59. "Gracefully stops the running VM."
  60. for arch in arch_list:
  61. if not testvm.is_running(arch):
  62. print(f"{arch} VM is not running")
  63. break
  64. console.print(f"Stopping {arch} VM")
  65. testvm.ssh_exec(arch, "sudo poweroff")
  66. @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS)
  67. def kill(arch_list: Iterable[Arch] = []):
  68. "Kills the running VM with SIGKILL."
  69. for arch in arch_list:
  70. if not testvm.is_running(arch):
  71. console.print(f"{arch} VM is not running")
  72. break
  73. console.print(f"Killing {arch} VM process")
  74. testvm.kill_vm(arch)
  75. print()
  76. @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS)
  77. def clean(arch_list: Iterable[Arch] = []):
  78. "Stops the VM or VMs and deletes all data."
  79. for arch in arch_list:
  80. if testvm.is_running(arch):
  81. kill(arch)
  82. if testvm.data_dir(arch).exists():
  83. console.print("Cleaning data directory", testvm.data_dir(arch))
  84. shutil.rmtree(testvm.data_dir(arch))
  85. print()
  86. def vm_status(arch: Arch):
  87. def cli_tip(*args: str):
  88. return f"[green][bold]{cli_shorthand(arch)} {' '.join(args)}[/bold][/green]"
  89. vm = f"{arch} VM"
  90. port = f"[blue]{testvm.SSH_PORTS[arch]}[/blue]"
  91. state = testvm.state(arch)
  92. if state == VmState.REACHABLE:
  93. console.print(f"{vm} is [green]reachable[/green] on port {port}")
  94. console.print(f"Start a shell with {cli_tip('shell')}")
  95. elif state == VmState.STOPPED:
  96. console.print(f"{vm} is [red]stopped[/red]")
  97. console.print(f"Start the VM with {cli_tip('up')}")
  98. else:
  99. console.print(f"{vm} is running but [red]not reachable[/red] on port {port}")
  100. console.print(f"Recent logs:")
  101. logs(arch, 10, style="light_slate_grey")
  102. console.print(f"See all logs with {cli_tip('logs')}")
  103. @argh.arg("--arch-list", "--arch", nargs="*", type=str, default=ARCHS, choices=ARCHS)
  104. def status(arch_list: Iterable[Arch] = []):
  105. for arch in arch_list:
  106. vm_status(arch)
  107. print()
  108. @argh.arg("--arch", required=True, choices=testvm.ARCH_OPTIONS)
  109. def logs(arch: Arch = "x86_64", n: int = 0, style: Optional[str] = None):
  110. log_lines = testvm.log_path(arch).read_text().splitlines()
  111. if n > 0 and len(log_lines) > n:
  112. log_lines = log_lines[-n:]
  113. for line in log_lines:
  114. if style:
  115. console.print(
  116. strip_ansi_escape_sequences(line), style=style, markup=False, highlight=False
  117. )
  118. else:
  119. print(line)
  120. if __name__ == "__main__":
  121. run_commands(up, run, shell, stop, kill, clean, status, logs, usage=USAGE, default_fn=status)