imkrish
phase4/5: qemu TCG boot + ssh into VM
13c712a
Raw History Blame Contribute Delete
5.89 kB
"""Phase 4/5: boot a real Linux VM under QEMU-TCG inside the HF Space container,
then SSH from the container into the VM. Progress -> stdout (hf spaces logs)."""
import os, subprocess, sys, threading, time, socket, textwrap, shutil
T0 = time.time()
LOG = []
IMG_URL = ("https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/"
"nocloud_alpine-3.22.4-x86_64-bios-cloudinit-r0.qcow2")
WORK = "/tmp/vm"
QCOW = WORK + "/alpine.qcow2"
SEED = WORK + "/seed.iso"
KEY = WORK + "/id_ed25519"
SERIAL = WORK + "/serial.log"
def log(msg):
line = "[t+%6.1fs] %s" % (time.time() - T0, msg)
LOG.append(line)
print(line, flush=True)
def sh(cmd, timeout=1800, quiet=False):
log("$ " + cmd)
r = subprocess.run(["bash", "-lc", cmd], capture_output=True, text=True, timeout=timeout)
t = (r.stdout + r.stderr).strip()
if t and not quiet:
tail = "\n".join(t.splitlines()[-25:])
LOG.append(tail); print(tail, flush=True)
if r.returncode:
log("[exit %d]" % r.returncode)
return r.returncode, t
def port_open(port, host="127.0.0.1"):
s = socket.socket(); s.settimeout(2)
try:
return s.connect_ex((host, port)) == 0
finally:
s.close()
def run():
os.makedirs(WORK, exist_ok=True)
log("### PHASE 4a: install QEMU ###")
sh("apt-get update -qq", timeout=900, quiet=True)
rc, _ = sh("DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends "
"qemu-system-x86 qemu-utils cloud-image-utils openssh-client", timeout=1800, quiet=True)
if rc:
log("!!! apt install FAILED — Phase 4 BLOCKED"); return
sh("qemu-system-x86_64 --version | head -2")
sh("qemu-system-x86_64 -accel help")
log("### PHASE 4b: confirm KVM accel is refused, TCG is available ###")
sh("qemu-system-x86_64 -accel kvm -m 128 -display none -no-reboot -kernel /dev/null 2>&1 | head -5 || true")
log("### PHASE 4c: fetch Alpine nocloud image ###")
if not os.path.exists(QCOW):
rc, _ = sh("curl -fsSL -o %s '%s'" % (QCOW, IMG_URL), timeout=1800, quiet=True)
if rc:
log("!!! image download FAILED"); return
sh("ls -lh %s && qemu-img info %s" % (QCOW, QCOW))
sh("qemu-img resize %s 4G" % QCOW)
log("### PHASE 5a: cloud-init seed (ssh key injection) ###")
sh("rm -f %s %s.pub && ssh-keygen -t ed25519 -N '' -f %s -q" % (KEY, KEY, KEY))
pub = open(KEY + ".pub").read().strip()
open(WORK + "/user-data", "w").write(textwrap.dedent("""\
#cloud-config
disable_root: false
ssh_pwauth: false
users:
- default
- name: root
ssh_authorized_keys:
- %s
ssh_authorized_keys:
- %s
runcmd:
- [ sh, -c, "echo '@@@ CLOUDINIT_DONE @@@' > /dev/console" ]
""") % (pub, pub))
open(WORK + "/meta-data", "w").write("instance-id: hfvm-001\nlocal-hostname: hfvm\n")
sh("cloud-localds %s %s/user-data %s/meta-data && ls -lh %s" % (SEED, WORK, WORK, SEED))
log("### PHASE 4d: BOOT VM (QEMU TCG, 2 vCPU / 1536 MB) ###")
qemu = ("qemu-system-x86_64 -accel tcg,thread=multi -smp 2 -m 1536 "
"-drive file=%s,if=virtio,format=qcow2 "
"-drive file=%s,if=virtio,format=raw,readonly=on "
"-netdev user,id=n0,hostfwd=tcp:127.0.0.1:2222-:22 "
"-device virtio-net-pci,netdev=n0 "
"-nographic -no-reboot" % (QCOW, SEED))
log("$ " + qemu)
sf = open(SERIAL, "wb")
proc = subprocess.Popen(["bash", "-lc", qemu], stdin=subprocess.DEVNULL,
stdout=sf, stderr=subprocess.STDOUT)
boot_t0 = time.time()
log("### PHASE 5b: wait for guest sshd on 127.0.0.1:2222 ###")
up = False
for i in range(120): # up to 20 min
if proc.poll() is not None:
log("!!! QEMU exited early rc=%s" % proc.returncode); break
if port_open(2222):
up = True
log(">>> GUEST SSH PORT OPEN after %.1fs of TCG boot" % (time.time() - boot_t0))
break
if i % 6 == 0:
try:
tail = subprocess.run(["tail", "-c", "1200", SERIAL],
capture_output=True, text=True).stdout
log("serial tail: " + tail.replace("\r", "")[-700:])
except Exception:
pass
time.sleep(10)
if not up:
log("!!! guest sshd never came up")
else:
log("### PHASE 5c: SSH container -> VM ###")
ssh = ("ssh -i %s -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
"-o ConnectTimeout=15 -o BatchMode=yes" % KEY)
for user in ("root", "alpine"):
rc, out = sh("%s %s@127.0.0.1 'echo ===IN-VM===; uname -a; id; "
"cat /etc/os-release | head -2; free -m | head -2; "
"df -h / | tail -1; ip -4 addr show eth0 | grep inet' 2>&1" % (ssh, user),
timeout=120)
if rc == 0:
log(">>> PHASE 5 PASS: SSH into VM succeeded as %s" % user)
break
else:
log("!!! SSH into VM failed for all users")
log("### benchmark: TCG throughput inside VM ###")
sh("%s root@127.0.0.1 'time dd if=/dev/zero bs=1M count=64 2>&1 | tail -3' 2>&1" % ssh, timeout=180)
log("### serial console (last 4000 bytes) ###")
sh("tail -c 4000 %s | tr -d '\\r'" % SERIAL)
log("### DONE ###")
try:
proc.terminate()
except Exception:
pass
threading.Thread(target=lambda: (run() if True else None), daemon=True).start()
import gradio as gr
gr.Interface(lambda: "\n".join(LOG[-400:]), None,
gr.Textbox(lines=40, label="phase 4/5 log"),
title="QEMU-in-HF-Space experiment").launch(server_name="0.0.0.0", server_port=7860)