diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-19 19:07:17 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-26 01:05:01 +0000 |
| commit | 5c84f660f7f717765b5662fd69c52cacf0ab64d9 (patch) | |
| tree | 6bf52fd570442bc2885684f832c28e42870af3d8 /recipes-containers/vcontainer/files | |
| parent | 9377aede3157a3e7b702dc389c15f27523b673e7 (diff) | |
| download | meta-virtualization-5c84f660f7f717765b5662fd69c52cacf0ab64d9.tar.gz | |
vcontainer: add QEMU hypervisor backend and register in recipes
Add vrunner-backend-qemu.sh implementing the hypervisor interface for
QEMU (arch setup, KVM detection, disk/network/9p options, VM lifecycle,
QMP control). Register backend scripts in vcontainer-native and
vcontainer-tarball recipes so they are available in both build-time
and standalone tarball contexts.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers/vcontainer/files')
| -rw-r--r-- | recipes-containers/vcontainer/files/vrunner-backend-qemu.sh | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh b/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh new file mode 100644 index 00000000..87054876 --- /dev/null +++ b/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh | |||
| @@ -0,0 +1,260 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 3 | # | ||
| 4 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | # | ||
| 6 | # vrunner-backend-qemu.sh | ||
| 7 | # QEMU hypervisor backend for vrunner.sh | ||
| 8 | # | ||
| 9 | # This backend implements the hypervisor interface for QEMU. | ||
| 10 | # It is sourced by vrunner.sh when VCONTAINER_HYPERVISOR=qemu. | ||
| 11 | # | ||
| 12 | # Backend interface functions: | ||
| 13 | # hv_setup_arch() - Set arch-specific QEMU command and machine type | ||
| 14 | # hv_check_accel() - Detect KVM acceleration | ||
| 15 | # hv_build_disk_opts() - Build QEMU disk drive options | ||
| 16 | # hv_build_network_opts() - Build QEMU network options | ||
| 17 | # hv_build_9p_opts() - Build QEMU virtio-9p options | ||
| 18 | # hv_build_daemon_opts() - Build QEMU daemon mode options (serial, QMP) | ||
| 19 | # hv_build_vm_cmd() - Assemble final QEMU command line | ||
| 20 | # hv_start_vm_background() - Start QEMU in background, capture PID | ||
| 21 | # hv_start_vm_foreground() - Start QEMU in foreground (interactive) | ||
| 22 | # hv_is_vm_running() - Check if QEMU process is alive | ||
| 23 | # hv_wait_vm_exit() - Wait for QEMU to exit | ||
| 24 | # hv_stop_vm() - Graceful shutdown via QMP | ||
| 25 | # hv_destroy_vm() - Force kill QEMU process | ||
| 26 | # hv_get_vm_id() - Return QEMU PID | ||
| 27 | # hv_setup_port_forwards() - Port forwards via QEMU hostfwd (built into netdev) | ||
| 28 | # hv_cleanup_port_forwards() - No-op for QEMU (forwards die with process) | ||
| 29 | # hv_idle_shutdown() - Send QMP quit for idle timeout | ||
| 30 | # hv_get_console_device() - Return arch-specific console device name | ||
| 31 | |||
| 32 | # ============================================================================ | ||
| 33 | # Architecture Setup | ||
| 34 | # ============================================================================ | ||
| 35 | |||
| 36 | hv_setup_arch() { | ||
| 37 | case "$TARGET_ARCH" in | ||
| 38 | aarch64) | ||
| 39 | KERNEL_IMAGE="$BLOB_DIR/aarch64/Image" | ||
| 40 | INITRAMFS="$BLOB_DIR/aarch64/initramfs.cpio.gz" | ||
| 41 | ROOTFS_IMG="$BLOB_DIR/aarch64/rootfs.img" | ||
| 42 | HV_CMD="qemu-system-aarch64" | ||
| 43 | HV_MACHINE="-M virt -cpu cortex-a57" | ||
| 44 | HV_CONSOLE="ttyAMA0" | ||
| 45 | ;; | ||
| 46 | x86_64) | ||
| 47 | KERNEL_IMAGE="$BLOB_DIR/x86_64/bzImage" | ||
| 48 | INITRAMFS="$BLOB_DIR/x86_64/initramfs.cpio.gz" | ||
| 49 | ROOTFS_IMG="$BLOB_DIR/x86_64/rootfs.img" | ||
| 50 | HV_CMD="qemu-system-x86_64" | ||
| 51 | HV_MACHINE="-M q35 -cpu Skylake-Client" | ||
| 52 | HV_CONSOLE="ttyS0" | ||
| 53 | ;; | ||
| 54 | *) | ||
| 55 | log "ERROR" "Unsupported architecture: $TARGET_ARCH" | ||
| 56 | exit 1 | ||
| 57 | ;; | ||
| 58 | esac | ||
| 59 | } | ||
| 60 | |||
| 61 | hv_check_accel() { | ||
| 62 | USE_KVM="false" | ||
| 63 | if [ "$DISABLE_KVM" = "true" ]; then | ||
| 64 | log "DEBUG" "KVM disabled by --no-kvm flag" | ||
| 65 | return | ||
| 66 | fi | ||
| 67 | |||
| 68 | HOST_ARCH=$(uname -m) | ||
| 69 | if [ "$HOST_ARCH" = "$TARGET_ARCH" ] || \ | ||
| 70 | { [ "$HOST_ARCH" = "x86_64" ] && [ "$TARGET_ARCH" = "x86_64" ]; }; then | ||
| 71 | if [ -w /dev/kvm ]; then | ||
| 72 | USE_KVM="true" | ||
| 73 | case "$TARGET_ARCH" in | ||
| 74 | x86_64) HV_MACHINE="-M q35 -cpu host" ;; | ||
| 75 | aarch64) HV_MACHINE="-M virt -cpu host" ;; | ||
| 76 | esac | ||
| 77 | log "INFO" "KVM acceleration enabled" | ||
| 78 | else | ||
| 79 | log "DEBUG" "KVM not available (no write access to /dev/kvm)" | ||
| 80 | fi | ||
| 81 | fi | ||
| 82 | } | ||
| 83 | |||
| 84 | hv_find_command() { | ||
| 85 | if ! command -v "$HV_CMD" >/dev/null 2>&1; then | ||
| 86 | for path in \ | ||
| 87 | "${STAGING_BINDIR_NATIVE:-}" \ | ||
| 88 | "/usr/bin"; do | ||
| 89 | if [ -n "$path" ] && [ -x "$path/$HV_CMD" ]; then | ||
| 90 | HV_CMD="$path/$HV_CMD" | ||
| 91 | break | ||
| 92 | fi | ||
| 93 | done | ||
| 94 | fi | ||
| 95 | |||
| 96 | if ! command -v "$HV_CMD" >/dev/null 2>&1 && [ ! -x "$HV_CMD" ]; then | ||
| 97 | log "ERROR" "QEMU not found: $HV_CMD" | ||
| 98 | exit 1 | ||
| 99 | fi | ||
| 100 | log "DEBUG" "Using QEMU: $HV_CMD" | ||
| 101 | } | ||
| 102 | |||
| 103 | hv_get_console_device() { | ||
| 104 | echo "$HV_CONSOLE" | ||
| 105 | } | ||
| 106 | |||
| 107 | # ============================================================================ | ||
| 108 | # VM Configuration Building | ||
| 109 | # ============================================================================ | ||
| 110 | |||
| 111 | hv_build_disk_opts() { | ||
| 112 | # Rootfs (read-only) | ||
| 113 | HV_DISK_OPTS="-drive file=$ROOTFS_IMG,if=virtio,format=raw,readonly=on" | ||
| 114 | |||
| 115 | # Input disk (if any) | ||
| 116 | if [ -n "$DISK_OPTS" ]; then | ||
| 117 | HV_DISK_OPTS="$HV_DISK_OPTS $DISK_OPTS" | ||
| 118 | fi | ||
| 119 | |||
| 120 | # State disk (if any) | ||
| 121 | if [ -n "$STATE_DISK_OPTS" ]; then | ||
| 122 | HV_DISK_OPTS="$HV_DISK_OPTS $STATE_DISK_OPTS" | ||
| 123 | fi | ||
| 124 | } | ||
| 125 | |||
| 126 | hv_build_network_opts() { | ||
| 127 | HV_NET_OPTS="" | ||
| 128 | if [ "$NETWORK" = "true" ]; then | ||
| 129 | NETDEV_OPTS="user,id=net0" | ||
| 130 | |||
| 131 | # Add port forwards | ||
| 132 | for pf in "${PORT_FORWARDS[@]}"; do | ||
| 133 | HOST_PORT="${pf%%:*}" | ||
| 134 | CONTAINER_PART="${pf#*:}" | ||
| 135 | CONTAINER_PORT="${CONTAINER_PART%%/*}" | ||
| 136 | if [[ "$CONTAINER_PART" == */* ]]; then | ||
| 137 | PROTOCOL="${CONTAINER_PART##*/}" | ||
| 138 | else | ||
| 139 | PROTOCOL="tcp" | ||
| 140 | fi | ||
| 141 | NETDEV_OPTS="$NETDEV_OPTS,hostfwd=$PROTOCOL::$HOST_PORT-:$HOST_PORT" | ||
| 142 | log "INFO" "Port forward: host:$HOST_PORT -> VM:$HOST_PORT (Docker maps to container:$CONTAINER_PORT)" | ||
| 143 | done | ||
| 144 | |||
| 145 | HV_NET_OPTS="-netdev $NETDEV_OPTS -device virtio-net-pci,netdev=net0" | ||
| 146 | else | ||
| 147 | HV_NET_OPTS="-nic none" | ||
| 148 | fi | ||
| 149 | } | ||
| 150 | |||
| 151 | hv_build_9p_opts() { | ||
| 152 | local share_dir="$1" | ||
| 153 | local share_tag="$2" | ||
| 154 | local extra_opts="${3:-}" | ||
| 155 | HV_OPTS="$HV_OPTS -virtfs local,path=$share_dir,mount_tag=$share_tag,security_model=none${extra_opts:+,$extra_opts},id=$share_tag" | ||
| 156 | } | ||
| 157 | |||
| 158 | hv_build_daemon_opts() { | ||
| 159 | HV_DAEMON_OPTS="" | ||
| 160 | |||
| 161 | # virtio-serial for command channel | ||
| 162 | HV_DAEMON_OPTS="$HV_DAEMON_OPTS -chardev socket,id=vdkr,path=$DAEMON_SOCKET,server=on,wait=off" | ||
| 163 | HV_DAEMON_OPTS="$HV_DAEMON_OPTS -device virtio-serial-pci" | ||
| 164 | HV_DAEMON_OPTS="$HV_DAEMON_OPTS -device virtserialport,chardev=vdkr,name=vdkr" | ||
| 165 | |||
| 166 | # QMP socket for dynamic control | ||
| 167 | QMP_SOCKET="$DAEMON_SOCKET_DIR/qmp.sock" | ||
| 168 | HV_DAEMON_OPTS="$HV_DAEMON_OPTS -qmp unix:$QMP_SOCKET,server,nowait" | ||
| 169 | } | ||
| 170 | |||
| 171 | hv_build_vm_cmd() { | ||
| 172 | HV_OPTS="$HV_MACHINE -nographic -smp 2 -m 2048 -no-reboot" | ||
| 173 | if [ "$USE_KVM" = "true" ]; then | ||
| 174 | HV_OPTS="$HV_OPTS -enable-kvm" | ||
| 175 | fi | ||
| 176 | HV_OPTS="$HV_OPTS -kernel $KERNEL_IMAGE" | ||
| 177 | HV_OPTS="$HV_OPTS -initrd $INITRAMFS" | ||
| 178 | HV_OPTS="$HV_OPTS $HV_DISK_OPTS" | ||
| 179 | HV_OPTS="$HV_OPTS $HV_NET_OPTS" | ||
| 180 | } | ||
| 181 | |||
| 182 | # ============================================================================ | ||
| 183 | # VM Lifecycle | ||
| 184 | # ============================================================================ | ||
| 185 | |||
| 186 | hv_start_vm_background() { | ||
| 187 | local kernel_append="$1" | ||
| 188 | local log_file="$2" | ||
| 189 | local timeout_val="$3" | ||
| 190 | |||
| 191 | if [ -n "$timeout_val" ]; then | ||
| 192 | timeout $timeout_val $HV_CMD $HV_OPTS -append "$kernel_append" > "$log_file" 2>&1 & | ||
| 193 | else | ||
| 194 | $HV_CMD $HV_OPTS -append "$kernel_append" > "$log_file" 2>&1 & | ||
| 195 | fi | ||
| 196 | HV_VM_PID=$! | ||
| 197 | } | ||
| 198 | |||
| 199 | hv_start_vm_foreground() { | ||
| 200 | local kernel_append="$1" | ||
| 201 | $HV_CMD $HV_OPTS -append "$kernel_append" | ||
| 202 | } | ||
| 203 | |||
| 204 | hv_is_vm_running() { | ||
| 205 | [ -n "$HV_VM_PID" ] && [ -d "/proc/$HV_VM_PID" ] | ||
| 206 | } | ||
| 207 | |||
| 208 | hv_wait_vm_exit() { | ||
| 209 | local timeout="${1:-30}" | ||
| 210 | for i in $(seq 1 "$timeout"); do | ||
| 211 | hv_is_vm_running || return 0 | ||
| 212 | sleep 1 | ||
| 213 | done | ||
| 214 | return 1 | ||
| 215 | } | ||
| 216 | |||
| 217 | hv_stop_vm() { | ||
| 218 | if [ -n "$HV_VM_PID" ] && kill -0 "$HV_VM_PID" 2>/dev/null; then | ||
| 219 | log "WARN" "QEMU still running, forcing termination..." | ||
| 220 | kill $HV_VM_PID 2>/dev/null || true | ||
| 221 | wait $HV_VM_PID 2>/dev/null || true | ||
| 222 | fi | ||
| 223 | } | ||
| 224 | |||
| 225 | hv_destroy_vm() { | ||
| 226 | if [ -n "$HV_VM_PID" ]; then | ||
| 227 | kill -9 $HV_VM_PID 2>/dev/null || true | ||
| 228 | wait $HV_VM_PID 2>/dev/null || true | ||
| 229 | fi | ||
| 230 | } | ||
| 231 | |||
| 232 | hv_get_vm_id() { | ||
| 233 | echo "$HV_VM_PID" | ||
| 234 | } | ||
| 235 | |||
| 236 | # ============================================================================ | ||
| 237 | # Port Forwarding (handled by QEMU hostfwd, no separate setup needed) | ||
| 238 | # ============================================================================ | ||
| 239 | |||
| 240 | hv_setup_port_forwards() { | ||
| 241 | # QEMU port forwards are built into the -netdev hostfwd= options | ||
| 242 | # Nothing extra to do at runtime | ||
| 243 | : | ||
| 244 | } | ||
| 245 | |||
| 246 | hv_cleanup_port_forwards() { | ||
| 247 | # QEMU port forwards die with the process | ||
| 248 | : | ||
| 249 | } | ||
| 250 | |||
| 251 | # ============================================================================ | ||
| 252 | # Idle Timeout / QMP Control | ||
| 253 | # ============================================================================ | ||
| 254 | |||
| 255 | hv_idle_shutdown() { | ||
| 256 | if [ -S "$QMP_SOCKET" ]; then | ||
| 257 | echo '{"execute":"qmp_capabilities"}{"execute":"quit"}' | \ | ||
| 258 | socat - "UNIX-CONNECT:$QMP_SOCKET" >/dev/null 2>&1 || true | ||
| 259 | fi | ||
| 260 | } | ||
