QEMU experiments. Not yet ready for prime time
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

113 lines
4.2 KiB

#!/usr/bin/env python3
import os
import time
import shlex
import shutil
import subprocess
import logging
def run_in_qemu (name, git_root, git_branch = 'master', arch = 'amd64', system = 'debian', user = 'user', password = 'user', timeout = 300):
""" Clone git_root and run command in qemu.
Arguments
---------
name : str
name used to create file name of disposable image. Should be unique across concurrently running instances on a single host.
git_root : str
GIT root to clone and mount into image /mnt.
git_branch : str
GIT branch to checkout (default: 'master').
arch : str
Architecture to emulate (default: 'amd64'). Must be a valid QEMU architecture string.
system : str
Operating system to boot (default: 'debian').
user : str
Username used for login (default: 'user').
password : str
Password used for login (default: 'user').
timeout : float
Maximum time for the VM to run (default: 300 seconds).
"""
IMAGE=f"{system}-{arch}.qcow2"
DISPOSABLE_IMAGE=f"{name}.qcow2"
logging.info(f"Clone {git_root}...")
if os.path.exists(f"{name}.shared"):
shutil.rmtree(f"{name}.shared")
os.mkdir(f"{name}.shared")
with open(f"{name}.shared/run.sh", "w") as f:
f.write(f"git clone --depth=1 --single-branch -b {git_branch} {git_root} src && bash -ex src/.codeberg/ci.sh");
logging.info(f"Create disposable image {DISPOSABLE_IMAGE}")
if os.path.exists(DISPOSABLE_IMAGE):
os.unlink(DISPOSABLE_IMAGE)
os.system(f"qemu-img create -f qcow2 -o backing_file={IMAGE} {DISPOSABLE_IMAGE}")
## TODO options for optimization:
##
## hotplug memory, autoextend to 4G: -m 1G,slots=3,maxmem=4G
##
## consider improving networking throughput via multiqueues:
## -netdev tap,id=guest0,queues=8,vhost=on -device virtio-net-pci,netdev=guest0,mq=on,vectors=10
## (in client enabled via: sudo ethtool -L eth0 combined 8)
##
## enable ssh port redirect: -net user,hostfwd=tcp::2222-:22 -net nic
## connect: ssh vmuser@localhost -p 2222
## maybe simpler: -redir tcp:2222::22
##
logging.info(f"Launch QEMU/KVM with {DISPOSABLE_IMAGE}")
cmd = f"qemu-system-x86_64 -nographic -m 256,maxmem=1G -hda {DISPOSABLE_IMAGE} -enable-kvm"
cmd += f" -virtfs local,path={name}.shared,mount_tag=shared,security_model=mapped-xattr,id=shared"
logging.info(f"Execute '{cmd}'")
args = shlex.split(cmd)
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
state = "wait_for_login"
done = False
stdout = b""
while True:
assert p.poll() is None, "premature exit"
stdout += p.stdout.read1()
if stdout.endswith(b"\n"):
print(stdout.decode(), end="", flush=True)
stdout = b""
if state == "wait_for_login" and stdout == b"debian login: ":
stdout = b""
logging.info(f"*** Logging in as {user}...")
p.stdin.write((user + "\n").encode())
p.stdin.flush()
state = "wait_for_password"
if state == "wait_for_password" and stdout == b"Password: ":
stdout = b""
logging.info(f"*** Enter password ...")
p.stdin.write((password + "\n").encode())
p.stdin.flush()
state = "wait_for_prompt"
if state == "wait_for_prompt" and stdout.endswith(b"$ "):
stdout = b""
logging.info(f"*** Run ...")
p.stdin.write(("/bin/bash -ex /mnt/run.sh &> /mnt/run.log ; echo 'DONE' ; sync ; sudo shutdown -h now\n").encode())
p.stdin.flush()
assert p.wait(timeout=timeout) == 0
break
finally:
p.kill()
logging.info(f"*** DONE. Output logfile is now in '{name}.shared/run.log'")
logging.info(f"*** Executed cmd was: '{cmd}'");
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging.DEBUG)
job = {
'name': 'test01',
'arch': 'amd64',
'system': 'debian',
'git_root': 'https://codeberg.org/hw/qemu-experiments.git',
'git_branch': 'master'
}
run_in_qemu(**job)