Spaces:
Paused
Paused
File size: 5,894 Bytes
13c712a 9ddd676 13c712a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | """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)
|