move out vm logic out of controller

This commit is contained in:
Jörg Thalheim
2023-10-03 16:47:14 +02:00
parent 6de1aeebb9
commit c03effed54
10 changed files with 203 additions and 218 deletions

View File

@@ -1,26 +1,114 @@
import argparse
import asyncio
from typing import Any, Iterator
import json
import tempfile
from pathlib import Path
from typing import Iterator
from uuid import UUID
from fastapi.responses import StreamingResponse
from ..dirs import get_clan_flake_toplevel
from ..webui.routers import vms
from ..webui.schemas import VmConfig
from ..nix import nix_build, nix_shell
from ..task_manager import BaseTask, CmdState, get_task, register_task
from .inspect import VmConfig
def read_stream_response(stream: StreamingResponse) -> Iterator[Any]:
iterator = stream.body_iterator
while True:
try:
tem = asyncio.run(iterator.__anext__()) # type: ignore
except StopAsyncIteration:
break
yield tem
class BuildVmTask(BaseTask):
def __init__(self, uuid: UUID, vm: VmConfig) -> None:
super().__init__(uuid)
self.vm = vm
def get_vm_create_info(self, cmds: Iterator[CmdState]) -> dict:
clan_dir = self.vm.flake_url
machine = self.vm.flake_attr
cmd = next(cmds)
cmd.run(
nix_build(
[
# f'{clan_dir}#clanInternals.machines."{system}"."{machine}".config.clan.virtualisation.createJSON' # TODO use this
f'{clan_dir}#nixosConfigurations."{machine}".config.system.clan.vm.create'
]
)
)
vm_json = "".join(cmd.stdout)
self.log.debug(f"VM JSON path: {vm_json}")
with open(vm_json) as f:
return json.load(f)
def task_run(self) -> None:
cmds = self.register_cmds(4)
machine = self.vm.flake_attr
self.log.debug(f"Creating VM for {machine}")
# TODO: We should get this from the vm argument
vm_config = self.get_vm_create_info(cmds)
with tempfile.TemporaryDirectory() as tmpdir_:
xchg_dir = Path(tmpdir_) / "xchg"
xchg_dir.mkdir()
disk_img = f"{tmpdir_}/disk.img"
cmd = next(cmds)
cmd.run(
nix_shell(
["qemu"],
[
"qemu-img",
"create",
"-f",
"raw",
disk_img,
"1024M",
],
)
)
cmd = next(cmds)
cmd.run(
nix_shell(
["e2fsprogs"],
[
"mkfs.ext4",
"-L",
"nixos",
disk_img,
],
)
)
cmd = next(cmds)
cmd.run(
nix_shell(
["qemu"],
[
# fmt: off
"qemu-kvm",
"-name", machine,
"-m", f'{vm_config["memorySize"]}M',
"-smp", str(vm_config["cores"]),
"-device", "virtio-rng-pci",
"-net", "nic,netdev=user.0,model=virtio", "-netdev", "user,id=user.0",
"-virtfs", "local,path=/nix/store,security_model=none,mount_tag=nix-store",
"-virtfs", f"local,path={xchg_dir},security_model=none,mount_tag=shared",
"-virtfs", f"local,path={xchg_dir},security_model=none,mount_tag=xchg",
"-drive", f'cache=writeback,file={disk_img},format=raw,id=drive1,if=none,index=1,werror=report',
"-device", "virtio-blk-pci,bootindex=1,drive=drive1,serial=root",
"-device", "virtio-keyboard",
"-usb",
"-device", "usb-tablet,bus=usb-bus.0",
"-kernel", f'{vm_config["toplevel"]}/kernel',
"-initrd", vm_config["initrd"],
"-append", f'{(Path(vm_config["toplevel"]) / "kernel-params").read_text()} init={vm_config["toplevel"]}/init regInfo={vm_config["regInfo"]}/registration console=ttyS0,115200n8 console=tty0',
# fmt: on
],
)
)
def create(args: argparse.Namespace) -> None:
def create_vm(vm: VmConfig) -> UUID:
return register_task(BuildVmTask, vm)
def create_command(args: argparse.Namespace) -> None:
clan_dir = get_clan_flake_toplevel().as_posix()
vm = VmConfig(
flake_url=clan_dir,
@@ -30,17 +118,12 @@ def create(args: argparse.Namespace) -> None:
memory_size=0,
)
res = asyncio.run(vms.create_vm(vm))
print(res.json())
uuid = UUID(res.uuid)
stream = asyncio.run(vms.get_vm_logs(uuid))
for line in read_stream_response(stream):
uuid = create_vm(vm)
task = get_task(uuid)
for line in task.logs_iter():
print(line, end="")
print("")
def register_create_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument("machine", type=str)
parser.set_defaults(func=create)
parser.set_defaults(func=create_command)

View File

@@ -1,16 +1,42 @@
import argparse
import asyncio
import json
from pydantic import BaseModel
from ..async_cmd import run
from ..dirs import get_clan_flake_toplevel
from ..webui.routers import vms
from ..nix import nix_eval
def inspect(args: argparse.Namespace) -> None:
class VmConfig(BaseModel):
flake_url: str
flake_attr: str
cores: int
memory_size: int
graphics: bool
async def inspect_vm(flake_url: str, flake_attr: str) -> VmConfig:
cmd = nix_eval(
[
f"{flake_url}#nixosConfigurations.{json.dumps(flake_attr)}.config.system.clan.vm.config"
]
)
stdout = await run(cmd)
data = json.loads(stdout)
return VmConfig(flake_url=flake_url, flake_attr=flake_attr, **data)
def inspect_command(args: argparse.Namespace) -> None:
clan_dir = get_clan_flake_toplevel().as_posix()
res = asyncio.run(vms.inspect_vm(flake_url=clan_dir, flake_attr=args.machine))
print(res.json())
res = asyncio.run(inspect_vm(flake_url=clan_dir, flake_attr=args.machine))
print("Cores:", res.cores)
print("Memory size:", res.memory_size)
print("Graphics:", res.graphics)
def register_inspect_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument("machine", type=str)
parser.set_defaults(func=inspect)
parser.set_defaults(func=inspect_command)