summaryrefslogtreecommitdiffstats
path: root/classes
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-12 18:30:46 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-26 01:05:01 +0000
commit90ed995a14e9ba69240cc8cab42070eeef77b1a9 (patch)
treec1194771ee4c6ca7a09cf841ce2313b02400cdea /classes
parentac150976f5c892d3c4c919dcc2e0b15bc3761e02 (diff)
downloadmeta-virtualization-90ed995a14e9ba69240cc8cab42070eeef77b1a9.tar.gz
xen-guest-bundle: add guest import system for 3rd-party images
Add extensible import system to convert fetched source formats (tarballs, qcow2, etc.) into Xen-ready disk images at build time. Built-in import types: - rootfs_dir: extracted directory → ext4 via mkfs.ext4 -d - qcow2: QCOW2 → raw via qemu-img convert - ext4/raw: copy passthrough Per-guest varflags control the import: XEN_GUEST_SOURCE_TYPE[guest] = "rootfs_dir" XEN_GUEST_SOURCE_FILE[guest] = "alpine-rootfs" XEN_GUEST_IMAGE_SIZE[guest] = "128" Also adds three kernel modes for resolve_bundle_kernel(): - (not set): shared host kernel from DEPLOY_DIR_IMAGE - "path": custom kernel, checks UNPACKDIR then DEPLOY_DIR_IMAGE - "none": HVM guest, omits kernel= from config Native tool dependencies and fakeroot are resolved automatically at parse time. External guests emit a single license warning at do_compile time (prefunc, not parse-time). Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'classes')
-rw-r--r--classes/xen-guest-bundle.bbclass272
1 files changed, 254 insertions, 18 deletions
diff --git a/classes/xen-guest-bundle.bbclass b/classes/xen-guest-bundle.bbclass
index 0e061bfb..3fe7754e 100644
--- a/classes/xen-guest-bundle.bbclass
+++ b/classes/xen-guest-bundle.bbclass
@@ -103,6 +103,14 @@
103# Explicit rootfs/kernel paths (for external/3rd-party guests): 103# Explicit rootfs/kernel paths (for external/3rd-party guests):
104# XEN_GUEST_ROOTFS[my-vendor-guest] = "vendor-rootfs.ext4" 104# XEN_GUEST_ROOTFS[my-vendor-guest] = "vendor-rootfs.ext4"
105# XEN_GUEST_KERNEL[my-vendor-guest] = "vendor-kernel" 105# XEN_GUEST_KERNEL[my-vendor-guest] = "vendor-kernel"
106# XEN_GUEST_KERNEL[my-hvm-guest] = "none" # HVM: no kernel
107#
108# 3rd-party guest import (convert fetched sources to Xen-ready images):
109# XEN_GUEST_SOURCE_TYPE[guest] = "rootfs_dir" # import handler type
110# XEN_GUEST_SOURCE_FILE[guest] = "alpine-rootfs" # file/dir in UNPACKDIR
111# XEN_GUEST_IMAGE_SIZE[guest] = "128" # target image MB
112#
113# Built-in import types: rootfs_dir, qcow2, ext4, raw
106# 114#
107# =========================================================================== 115# ===========================================================================
108# Integration with xen-guest-cross-install.bbclass 116# Integration with xen-guest-cross-install.bbclass
@@ -124,6 +132,28 @@ XEN_GUEST_EXTRA_DEFAULT ?= "root=/dev/xvda ro ip=dhcp"
124XEN_GUEST_DISK_DEVICE_DEFAULT ?= "xvda" 132XEN_GUEST_DISK_DEVICE_DEFAULT ?= "xvda"
125 133
126# =========================================================================== 134# ===========================================================================
135# Import system for 3rd-party guests
136# ===========================================================================
137#
138# Convert fetched source formats (tarballs, qcow2, etc.) into Xen-ready disk
139# images using extensible named handlers. Shell functions named
140# xen_guest_import_<type>() are dispatched at build time.
141#
142# Per-guest varflags:
143# XEN_GUEST_SOURCE_TYPE[guest] = "rootfs_dir" # import handler type
144# XEN_GUEST_SOURCE_FILE[guest] = "alpine-rootfs" # file/dir in UNPACKDIR
145# XEN_GUEST_IMAGE_SIZE[guest] = "128" # target image MB
146#
147# Built-in import types: rootfs_dir, qcow2, ext4, raw
148# Extensible: any class/recipe/bbappend can add xen_guest_import_<type>()
149
150XEN_GUEST_IMAGE_SIZE_DEFAULT ?= "256"
151XEN_GUEST_IMPORT_DEPENDS_rootfs_dir = "e2fsprogs-native:do_populate_sysroot"
152XEN_GUEST_IMPORT_DEPENDS_qcow2 = "qemu-system-native:do_populate_sysroot"
153XEN_GUEST_IMPORT_DEPENDS_ext4 = ""
154XEN_GUEST_IMPORT_DEPENDS_raw = ""
155
156# ===========================================================================
127# Parse-time dependency generation 157# Parse-time dependency generation
128# =========================================================================== 158# ===========================================================================
129 159
@@ -134,6 +164,7 @@ python __anonymous() {
134 164
135 processed = [] 165 processed = []
136 deps = "" 166 deps = ""
167 external_guests = []
137 168
138 for entry in bundles: 169 for entry in bundles:
139 parts = entry.split(':') 170 parts = entry.split(':')
@@ -145,6 +176,8 @@ python __anonymous() {
145 # Generate dependency on guest recipe (unless external) 176 # Generate dependency on guest recipe (unless external)
146 if not is_external: 177 if not is_external:
147 deps += " %s:do_image_complete" % guest_name 178 deps += " %s:do_image_complete" % guest_name
179 else:
180 external_guests.append(guest_name)
148 181
149 # Store processed entry: guest_name:autostart_flag 182 # Store processed entry: guest_name:autostart_flag
150 autostart_flag = "autostart" if is_autostart else "" 183 autostart_flag = "autostart" if is_autostart else ""
@@ -153,6 +186,9 @@ python __anonymous() {
153 if deps: 186 if deps:
154 d.appendVarFlag('do_compile', 'depends', deps) 187 d.appendVarFlag('do_compile', 'depends', deps)
155 188
189 if external_guests:
190 d.setVar('_XEN_GUEST_EXTERNAL_NAMES', ' '.join(external_guests))
191
156 d.setVar('_PROCESSED_XEN_BUNDLES', ' '.join(processed)) 192 d.setVar('_PROCESSED_XEN_BUNDLES', ' '.join(processed))
157 193
158 # Build config file map from varflags 194 # Build config file map from varflags
@@ -190,6 +226,47 @@ python __anonymous() {
190 param_mappings.append("%s=%s" % (guest_name, params)) 226 param_mappings.append("%s=%s" % (guest_name, params))
191 227
192 d.setVar('_XEN_GUEST_PARAMS_MAP', ';'.join(param_mappings)) 228 d.setVar('_XEN_GUEST_PARAMS_MAP', ';'.join(param_mappings))
229
230 # Build import map from varflags and resolve dependencies
231 # Format: guest=type|file|size;guest2=...
232 import_mappings = []
233 import_types_used = set()
234 needs_shared_kernel = False
235
236 for entry in bundles:
237 guest_name = entry.split(':')[0]
238 source_type = d.getVarFlag('XEN_GUEST_SOURCE_TYPE', guest_name)
239 if source_type:
240 source_file = d.getVarFlag('XEN_GUEST_SOURCE_FILE', guest_name) or ""
241 image_size = d.getVarFlag('XEN_GUEST_IMAGE_SIZE', guest_name) or d.getVar('XEN_GUEST_IMAGE_SIZE_DEFAULT')
242 import_mappings.append("%s=%s|%s|%s" % (guest_name, source_type, source_file, image_size))
243 import_types_used.add(source_type)
244
245 # Determine if this guest needs the shared kernel
246 kernel_flag = d.getVarFlag('XEN_GUEST_KERNEL', guest_name)
247 if not kernel_flag or kernel_flag != "none":
248 # No explicit kernel or not "none" → may need shared kernel
249 if not kernel_flag:
250 needs_shared_kernel = True
251
252 d.setVar('_XEN_GUEST_IMPORT_MAP', ';'.join(import_mappings))
253
254 # Add native tool dependencies for import types used
255 import_deps = ""
256 for itype in import_types_used:
257 dep = d.getVar('XEN_GUEST_IMPORT_DEPENDS_%s' % itype)
258 if dep:
259 import_deps += " %s" % dep
260 if import_deps:
261 d.appendVarFlag('do_compile', 'depends', import_deps)
262
263 # rootfs_dir needs fakeroot for mkfs.ext4 -d ownership
264 if 'rootfs_dir' in import_types_used:
265 d.setVarFlag('do_compile', 'fakeroot', '1')
266
267 # Auto-add virtual/kernel dependency if any guest uses shared kernel
268 if needs_shared_kernel:
269 d.appendVarFlag('do_compile', 'depends', ' virtual/kernel:do_deploy')
193} 270}
194 271
195S = "${UNPACKDIR}/sources" 272S = "${UNPACKDIR}/sources"
@@ -198,6 +275,113 @@ B = "${WORKDIR}/build"
198do_patch[noexec] = "1" 275do_patch[noexec] = "1"
199do_configure[noexec] = "1" 276do_configure[noexec] = "1"
200 277
278python xen_guest_external_license_warn() {
279 names = d.getVar('_XEN_GUEST_EXTERNAL_NAMES')
280 if names:
281 bb.warn("Bundling external guest image(s): %s\n"
282 "Ensure you have rights to redistribute these images.\n"
283 "Check the guest license terms before distribution." % names)
284}
285do_compile[prefuncs] += "xen_guest_external_license_warn"
286
287# ===========================================================================
288# Import handlers for 3rd-party guest formats
289# ===========================================================================
290# Shell functions named xen_guest_import_<type>(source, output, size_mb).
291# Extensible: recipes/bbappends can define additional handlers.
292
293# rootfs_dir: extracted rootfs directory → ext4 image
294xen_guest_import_rootfs_dir() {
295 local source_path="$1"
296 local output_path="$2"
297 local size_mb="$3"
298
299 if [ ! -d "$source_path" ]; then
300 bbfatal "rootfs_dir import: source '$source_path' is not a directory"
301 fi
302
303 bbnote "rootfs_dir import: creating ${size_mb}MB ext4 from $source_path"
304
305 # Create sparse file and format with directory contents
306 dd if=/dev/zero of="$output_path" bs=1M count=0 seek="$size_mb"
307 mkfs.ext4 -F -d "$source_path" "$output_path"
308}
309
310# qcow2: QCOW2 disk image → raw image
311xen_guest_import_qcow2() {
312 local source_path="$1"
313 local output_path="$2"
314 local size_mb="$3"
315
316 if [ ! -f "$source_path" ]; then
317 bbfatal "qcow2 import: source '$source_path' not found"
318 fi
319
320 bbnote "qcow2 import: converting $source_path to raw"
321 qemu-img convert -f qcow2 -O raw "$source_path" "$output_path"
322}
323
324# ext4: ext4 image → copy
325xen_guest_import_ext4() {
326 local source_path="$1"
327 local output_path="$2"
328 local size_mb="$3"
329
330 if [ ! -f "$source_path" ]; then
331 bbfatal "ext4 import: source '$source_path' not found"
332 fi
333
334 bbnote "ext4 import: copying $source_path"
335 cp "$source_path" "$output_path"
336}
337
338# raw: raw disk image → copy
339xen_guest_import_raw() {
340 local source_path="$1"
341 local output_path="$2"
342 local size_mb="$3"
343
344 if [ ! -f "$source_path" ]; then
345 bbfatal "raw import: source '$source_path' not found"
346 fi
347
348 bbnote "raw import: copying $source_path"
349 cp "$source_path" "$output_path"
350}
351
352# Resolve import source for a guest from _XEN_GUEST_IMPORT_MAP
353# Returns: type|source_path|size_mb or empty if guest has no import
354resolve_import_source() {
355 local guest="$1"
356 local import_map="${_XEN_GUEST_IMPORT_MAP}"
357
358 local entry=$(echo "$import_map" | tr ';' '\n' | grep "^${guest}=")
359 if [ -z "$entry" ]; then
360 return 1
361 fi
362
363 local info=$(echo "$entry" | cut -d= -f2-)
364 local source_type=$(echo "$info" | cut -d'|' -f1)
365 local source_file=$(echo "$info" | cut -d'|' -f2)
366 local size_mb=$(echo "$info" | cut -d'|' -f3)
367
368 # Resolve source path
369 local source_path=""
370 if [ -n "$source_file" ]; then
371 if [ -e "${UNPACKDIR}/$source_file" ]; then
372 source_path="${UNPACKDIR}/$source_file"
373 elif [ -e "$source_file" ]; then
374 source_path="$source_file"
375 fi
376 fi
377
378 if [ -z "$source_path" ]; then
379 bbfatal "Import source '$source_file' not found for guest '$guest'"
380 fi
381
382 echo "${source_type}|${source_path}|${size_mb}"
383}
384
201# =========================================================================== 385# ===========================================================================
202# do_compile: resolve guests and generate configs 386# do_compile: resolve guests and generate configs
203# =========================================================================== 387# ===========================================================================
@@ -226,23 +410,48 @@ do_compile() {
226 410
227 bbnote "Processing guest: $guest_name (autostart=$autostart_flag)" 411 bbnote "Processing guest: $guest_name (autostart=$autostart_flag)"
228 412
229 # Resolve rootfs 413 # Resolve rootfs - check import system first, then DEPLOY_DIR_IMAGE
230 rootfs_path=$(resolve_bundle_rootfs "$guest_name") 414 import_info=""
231 if [ -z "$rootfs_path" ]; then 415 if echo "${_XEN_GUEST_IMPORT_MAP}" | tr ';' '\n' | grep -q "^${guest_name}="; then
232 bbfatal "Cannot resolve rootfs for guest '$guest_name'" 416 import_info=$(resolve_import_source "$guest_name")
233 fi 417 fi
234 rootfs_basename=$(basename "$rootfs_path")
235 418
236 # Resolve kernel 419 if [ -n "$import_info" ]; then
237 kernel_path=$(resolve_bundle_kernel "$guest_name") 420 # Import path: convert fetched source to disk image
238 if [ -z "$kernel_path" ]; then 421 local import_type=$(echo "$import_info" | cut -d'|' -f1)
239 bbfatal "Cannot resolve kernel for guest '$guest_name'" 422 local import_source=$(echo "$import_info" | cut -d'|' -f2)
423 local import_size=$(echo "$import_info" | cut -d'|' -f3)
424
425 rootfs_basename="${guest_name}.img"
426 local output_path="${B}/images/${rootfs_basename}"
427
428 bbnote "Importing guest '$guest_name': type=$import_type source=$import_source size=${import_size}MB"
429
430 # Static dispatch - BitBake needs to see function names to include them
431 case "$import_type" in
432 rootfs_dir) xen_guest_import_rootfs_dir "$import_source" "$output_path" "$import_size" ;;
433 qcow2) xen_guest_import_qcow2 "$import_source" "$output_path" "$import_size" ;;
434 ext4) xen_guest_import_ext4 "$import_source" "$output_path" "$import_size" ;;
435 raw) xen_guest_import_raw "$import_source" "$output_path" "$import_size" ;;
436 *) bbfatal "Unknown import type '$import_type' for guest '$guest_name'" ;;
437 esac
438 else
439 # Standard path: resolve from DEPLOY_DIR_IMAGE
440 rootfs_path=$(resolve_bundle_rootfs "$guest_name")
441 if [ -z "$rootfs_path" ]; then
442 bbfatal "Cannot resolve rootfs for guest '$guest_name'"
443 fi
444 rootfs_basename=$(basename "$rootfs_path")
445 cp "$(readlink -f "$rootfs_path")" "${B}/images/${rootfs_basename}"
240 fi 446 fi
241 kernel_basename=$(basename "$kernel_path")
242 447
243 # Copy to build dir (readlink -f resolves versioned symlinks) 448 # Resolve kernel (supports shared, custom, and HVM/none modes)
244 cp "$(readlink -f "$rootfs_path")" "${B}/images/${rootfs_basename}" 449 kernel_path=$(resolve_bundle_kernel "$guest_name")
245 cp "$(readlink -f "$kernel_path")" "${B}/images/${kernel_basename}" 450 kernel_basename=""
451 if [ -n "$kernel_path" ]; then
452 kernel_basename=$(basename "$kernel_path")
453 cp "$(readlink -f "$kernel_path")" "${B}/images/${kernel_basename}"
454 fi
246 455
247 # Generate or install config 456 # Generate or install config
248 config_map="${_XEN_GUEST_CONFIG_FILE_MAP}" 457 config_map="${_XEN_GUEST_CONFIG_FILE_MAP}"
@@ -309,29 +518,48 @@ resolve_bundle_rootfs() {
309 return 1 518 return 1
310} 519}
311 520
312# Resolve guest kernel path from DEPLOY_DIR_IMAGE 521# Resolve guest kernel path
522# Three modes:
523# 1. XEN_GUEST_KERNEL[guest] = "none" → HVM mode, return empty (no kernel)
524# 2. XEN_GUEST_KERNEL[guest] = "<path>" → check UNPACKDIR then DEPLOY_DIR_IMAGE
525# 3. (not set) → shared kernel from DEPLOY_DIR_IMAGE/${KERNEL_IMAGETYPE}
313resolve_bundle_kernel() { 526resolve_bundle_kernel() {
314 local guest="$1" 527 local guest="$1"
315 local params_map="${_XEN_GUEST_PARAMS_MAP}" 528 local params_map="${_XEN_GUEST_PARAMS_MAP}"
316 529
317 # Check for explicit kernel override (field 7) 530 # Check for explicit kernel override (field 8)
318 local guest_params=$(echo "$params_map" | tr ';' '\n' | grep "^${guest}=" | cut -d= -f2-) 531 local guest_params=$(echo "$params_map" | tr ';' '\n' | grep "^${guest}=" | cut -d= -f2-)
319 local override="" 532 local override=""
320 if [ -n "$guest_params" ]; then 533 if [ -n "$guest_params" ]; then
321 override=$(echo "$guest_params" | cut -d'|' -f8) 534 override=$(echo "$guest_params" | cut -d'|' -f8)
322 fi 535 fi
323 536
537 # Mode 1: HVM - no kernel needed
538 if [ "$override" = "none" ]; then
539 bbnote "Guest '$guest' uses HVM mode (no kernel)"
540 return 0
541 fi
542
543 # Mode 2: explicit kernel path
324 if [ -n "$override" ]; then 544 if [ -n "$override" ]; then
325 local path="${DEPLOY_DIR_IMAGE}/$override" 545 # Check UNPACKDIR first (for fetched/custom kernels)
546 local path="${UNPACKDIR}/$override"
326 if [ -e "$path" ]; then 547 if [ -e "$path" ]; then
327 echo "$path" 548 echo "$path"
328 return 0 549 return 0
329 fi 550 fi
330 bbwarn "XEN_GUEST_KERNEL override '$override' not found at $path" 551
552 # Then check DEPLOY_DIR_IMAGE
553 path="${DEPLOY_DIR_IMAGE}/$override"
554 if [ -e "$path" ]; then
555 echo "$path"
556 return 0
557 fi
558 bbwarn "XEN_GUEST_KERNEL override '$override' not found in UNPACKDIR or DEPLOY_DIR_IMAGE"
331 return 1 559 return 1
332 fi 560 fi
333 561
334 # Default: shared kernel (same MACHINE) 562 # Mode 3: shared kernel (same MACHINE)
335 local path="${DEPLOY_DIR_IMAGE}/${KERNEL_IMAGETYPE}" 563 local path="${DEPLOY_DIR_IMAGE}/${KERNEL_IMAGETYPE}"
336 if [ -e "$path" ]; then 564 if [ -e "$path" ]; then
337 echo "$path" 565 echo "$path"
@@ -343,6 +571,8 @@ resolve_bundle_kernel() {
343} 571}
344 572
345# Generate a Xen guest configuration file with final target paths 573# Generate a Xen guest configuration file with final target paths
574# If kernel_basename is empty (HVM mode), kernel= and extra= lines are omitted.
575# HVM guests should use XEN_GUEST_CONFIG_FILE for full control.
346generate_bundle_config() { 576generate_bundle_config() {
347 local guest="$1" 577 local guest="$1"
348 local rootfs_basename="$2" 578 local rootfs_basename="$2"
@@ -366,9 +596,15 @@ memory = $memory
366vcpus = $vcpus 596vcpus = $vcpus
367disk = ['file:/var/lib/xen/images/$rootfs_basename,$disk_device,rw'] 597disk = ['file:/var/lib/xen/images/$rootfs_basename,$disk_device,rw']
368vif = ['$vif'] 598vif = ['$vif']
599EOF
600
601 # PV guests get kernel + extra; HVM guests omit these
602 if [ -n "$kernel_basename" ]; then
603 cat >> "$outfile" << EOF
369kernel = "/var/lib/xen/images/$kernel_basename" 604kernel = "/var/lib/xen/images/$kernel_basename"
370extra = "$extra" 605extra = "$extra"
371EOF 606EOF
607 fi
372} 608}
373 609
374# =========================================================================== 610# ===========================================================================