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)