clan-cli: implement waypipe backend
All checks were successful
checks-impure / test (pull_request) Successful in 1m23s
checks / test (pull_request) Successful in 2m41s

This commit is contained in:
Jörg Thalheim 2024-01-30 10:29:39 +07:00
parent 9fef40258a
commit c417157941

View File

@ -1,9 +1,15 @@
import argparse
import contextlib
import importlib
import json
import logging
import os
import random
import socket
import subprocess
import tempfile
import time
from collections.abc import Iterator
from dataclasses import dataclass, field
from pathlib import Path
from typing import IO
@ -19,21 +25,38 @@ from .inspect import VmConfig, inspect_vm
log = logging.getLogger(__name__)
def graphics_options(vm: VmConfig) -> list[str]:
common = ["-audio", "driver=pa,model=virtio"]
@dataclass
class GraphicOptions:
args: list[str]
vsock_cid: int | None = None
def graphics_options(vm: VmConfig) -> GraphicOptions:
common = [
"-audio",
"driver=pa,model=virtio",
]
if vm.wayland:
# FIXME: check for collisions
cid = random.randint(1, 2**32)
# fmt: off
return [
return GraphicOptions([
*common,
"-nographic",
"-vga", "none",
"-device", "virtio-gpu-rutabaga,gfxstream-vulkan=on,cross-domain=on,hostmem=4G,wsi=headless",
]
"-device", f"vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid={cid}",
# TODO: vgpu
#"-display", "egl-headless,gl=core",
#"-device", "virtio-vga,blob=true",
#"-device", "virtio-serial-pci",
#"-device", "vhost-user-vga,chardev=vgpu",
#"-chardev", "socket,id=vgpu,path=/tmp/vgpu.sock",
], cid)
# fmt: on
else:
# fmt: off
return [
return GraphicOptions([
*common,
"-vga", "none",
"-display", "gtk,gl=on",
@ -52,10 +75,16 @@ def graphics_options(vm: VmConfig) -> list[str]:
"-device", "pci-ohci,id=smartpass",
"-device", "usb-ccid",
"-chardev", "spicevmc,id=ccid,name=smartcard",
]
], None)
# fmt: on
@dataclass
class QemuCommand:
args: list[str]
vsock_cid: int | None = None
def qemu_command(
vm: VmConfig,
nixos_config: dict[str, str],
@ -63,7 +92,7 @@ def qemu_command(
secrets_dir: Path,
state_dir: Path,
disk_img: Path,
) -> list[str]:
) -> QemuCommand:
kernel_cmdline = [
(Path(nixos_config["toplevel"]) / "kernel-params").read_text(),
f'init={nixos_config["toplevel"]}/init',
@ -77,6 +106,8 @@ def qemu_command(
"qemu-kvm",
"-name", vm.machine_name,
"-m", f'{nixos_config["memorySize"]}M',
"-object", f"memory-backend-memfd,id=mem,size={nixos_config['memorySize']}M",
"-machine", "pc,memory-backend=mem,accel=kvm",
"-smp", str(nixos_config["cores"]),
"-cpu", "max",
"-enable-kvm",
@ -97,11 +128,14 @@ def qemu_command(
"-append", " ".join(kernel_cmdline),
] # fmt: on
vsock_cid = None
if vm.graphics:
command.extend(graphics_options(vm))
opts = graphics_options(vm)
vsock_cid = opts.vsock_cid
command.extend(opts.args)
else:
command.append("-nographic")
return command
return QemuCommand(command, vsock_cid=vsock_cid)
# TODO move this to the Machines class
@ -185,6 +219,45 @@ def prepare_disk(tmpdir: Path, log_fd: IO[str] | None) -> Path:
return disk_img
VMADDR_CID_HYPERVISOR = 2
def test_vsock_port(port: int) -> bool:
try:
s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
s.connect((VMADDR_CID_HYPERVISOR, port))
s.close()
return True
except OSError:
return False
@contextlib.contextmanager
def start_waypipe(cid: int | None, title_prefix: str) -> Iterator[None]:
if cid is None:
yield
return
waypipe = nix_shell(
["git+https://git.clan.lol/clan/clan-core#waypipe"],
[
"waypipe",
"--vsock",
"--socket",
f"s{cid}:3049",
"--title-prefix",
title_prefix,
"client",
],
)
with subprocess.Popen(waypipe) as proc:
try:
while not test_vsock_port(3049):
time.sleep(0.1)
yield
finally:
proc.kill()
def run_vm(
vm: VmConfig,
nix_options: list[str] = [],
@ -229,12 +302,13 @@ def run_vm(
"XDG_DATA_DIRS"
] = f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}"
run(
nix_shell(packages, qemu_cmd),
env=env,
log=Log.BOTH,
error_msg=f"Could not start vm {machine}",
)
with start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "):
run(
nix_shell(packages, qemu_cmd.args),
env=env,
log=Log.BOTH,
error_msg=f"Could not start vm {machine}",
)
@dataclass