diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-19 22:39:14 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-26 01:05:01 +0000 |
| commit | 6d94266a86246ac23abdbc28ac3b5c6e522fb748 (patch) | |
| tree | f3c14a73c232c1393d491b7f5d1cec318e12e285 | |
| parent | 5c891aab54f1432f2afe75bf0dee0027fbb2e4bd (diff) | |
| download | meta-virtualization-6d94266a86246ac23abdbc28ac3b5c6e522fb748.tar.gz | |
xen: add configuration tests and update documentation
Add TestXenImageMinimalX86Config test class verifying:
- QB_CPU_KVM host passthrough for Xen CPUID filtering
- QB_MEM_VALUE override (not QB_MEM which can't override bbclass)
- dom0_mem in both QB_XEN_CMDLINE_EXTRA and WKS syslinux config
- vgabios SAVANNAH_GNU_MIRROR usage
Update Alpine recipe tests for per-arch checksums (name=${ALPINE_ARCH})
and S variable. Add qemux86-64 build and boot section to README-xen.md.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
| -rw-r--r-- | recipes-extended/images/README-xen.md | 35 | ||||
| -rw-r--r-- | tests/test_xen_guest_bundle.py | 129 |
2 files changed, 156 insertions, 8 deletions
diff --git a/recipes-extended/images/README-xen.md b/recipes-extended/images/README-xen.md index f16aa743..e5b4ca4a 100644 --- a/recipes-extended/images/README-xen.md +++ b/recipes-extended/images/README-xen.md | |||
| @@ -165,6 +165,8 @@ Guests can be launched after boot with: xl create -c /etc/xen/<guest>.cfg | |||
| 165 | Build and boot | 165 | Build and boot |
| 166 | -------------- | 166 | -------------- |
| 167 | 167 | ||
| 168 | ### qemuarm64 | ||
| 169 | |||
| 168 | Using a reference qemuarm64 MACHINE, the following are the commands | 170 | Using a reference qemuarm64 MACHINE, the following are the commands |
| 169 | to build and boot a guest. | 171 | to build and boot a guest. |
| 170 | 172 | ||
| @@ -205,3 +207,36 @@ Name ID Mem VCPUs State Time(s) | |||
| 205 | Domain-0 0 192 4 r----- 696.2 | 207 | Domain-0 0 192 4 r----- 696.2 |
| 206 | xen-guest-image-minimal 1 512 1 -b---- 153.0 | 208 | xen-guest-image-minimal 1 512 1 -b---- 153.0 |
| 207 | root@qemuarm64:~# xl destroy xen-guest-image-minimal | 209 | root@qemuarm64:~# xl destroy xen-guest-image-minimal |
| 210 | |||
| 211 | ### qemux86-64 | ||
| 212 | |||
| 213 | The xen-image-minimal recipe includes x86-64 specific configuration: | ||
| 214 | |||
| 215 | - QB_CPU_KVM uses -cpu host to avoid AVX stripping by Xen's CPUID | ||
| 216 | filtering (required for x86-64-v3 tune) | ||
| 217 | - QB_MEM_VALUE = "1024" for 1GB Dom0 memory | ||
| 218 | - dom0_mem=512M reserves memory for DomU guests | ||
| 219 | |||
| 220 | % MACHINE=qemux86-64 bitbake xen-guest-image-minimal | ||
| 221 | % MACHINE=qemux86-64 bitbake xen-image-minimal | ||
| 222 | |||
| 223 | % runqemu qemux86-64 nographic slirp kvm | ||
| 224 | |||
| 225 | qemux86-64 login: root | ||
| 226 | |||
| 227 | root@qemux86-64:~# xl list | ||
| 228 | Name ID Mem VCPUs State Time(s) | ||
| 229 | Domain-0 0 512 4 r----- 12.3 | ||
| 230 | alpine 1 256 1 -b---- 0.8 | ||
| 231 | xen-guest-image-minimal 2 256 1 -b---- 3.1 | ||
| 232 | |||
| 233 | vxn standalone test: | ||
| 234 | |||
| 235 | root@qemux86-64:~# vxn run --rm alpine echo hello | ||
| 236 | hello | ||
| 237 | |||
| 238 | containerd test: | ||
| 239 | |||
| 240 | root@qemux86-64:~# ctr image pull docker.io/library/alpine:latest | ||
| 241 | root@qemux86-64:~# vctr run --rm docker.io/library/alpine:latest test1 echo hello | ||
| 242 | hello | ||
diff --git a/tests/test_xen_guest_bundle.py b/tests/test_xen_guest_bundle.py index 2035a5f9..dd56dcc9 100644 --- a/tests/test_xen_guest_bundle.py +++ b/tests/test_xen_guest_bundle.py | |||
| @@ -281,14 +281,32 @@ class TestAlpineRecipe: | |||
| 281 | assert "alpine-minirootfs" in alpine_recipe_content | 281 | assert "alpine-minirootfs" in alpine_recipe_content |
| 282 | assert "subdir=alpine-rootfs" in alpine_recipe_content | 282 | assert "subdir=alpine-rootfs" in alpine_recipe_content |
| 283 | 283 | ||
| 284 | def test_sha256sum(self, alpine_recipe_content): | 284 | def test_per_arch_checksums(self, alpine_recipe_content): |
| 285 | """Test sha256sum is set (not placeholder).""" | 285 | """Test per-architecture sha256sums are set.""" |
| 286 | match = re.search(r'SRC_URI\[sha256sum\]\s*=\s*"([^"]*)"', | 286 | aarch64_match = re.search( |
| 287 | alpine_recipe_content) | 287 | r'SRC_URI\[aarch64\.sha256sum\]\s*=\s*"([^"]*)"', |
| 288 | assert match, "sha256sum not found" | 288 | alpine_recipe_content) |
| 289 | sha = match.group(1) | 289 | x86_64_match = re.search( |
| 290 | assert len(sha) == 64, f"sha256sum wrong length: {len(sha)}" | 290 | r'SRC_URI\[x86_64\.sha256sum\]\s*=\s*"([^"]*)"', |
| 291 | assert sha != "x" * 64, "sha256sum is still placeholder" | 291 | alpine_recipe_content) |
| 292 | assert aarch64_match, "aarch64 sha256sum not found" | ||
| 293 | assert x86_64_match, "x86_64 sha256sum not found" | ||
| 294 | for name, match in [("aarch64", aarch64_match), ("x86_64", x86_64_match)]: | ||
| 295 | sha = match.group(1) | ||
| 296 | assert len(sha) == 64, f"{name} sha256sum wrong length: {len(sha)}" | ||
| 297 | assert sha != "x" * 64, f"{name} sha256sum is still placeholder" | ||
| 298 | # Checksums must differ (different arch tarballs) | ||
| 299 | assert aarch64_match.group(1) != x86_64_match.group(1), \ | ||
| 300 | "aarch64 and x86_64 checksums should differ" | ||
| 301 | |||
| 302 | def test_src_uri_per_arch_name(self, alpine_recipe_content): | ||
| 303 | """Test SRC_URI uses name= for per-arch checksum matching.""" | ||
| 304 | assert "name=${ALPINE_ARCH}" in alpine_recipe_content, \ | ||
| 305 | "SRC_URI should use name=${ALPINE_ARCH} for per-arch checksums" | ||
| 306 | |||
| 307 | def test_s_variable(self, alpine_recipe_content): | ||
| 308 | """Test S variable is set to avoid UNPACKDIR warning.""" | ||
| 309 | assert 'S = "${UNPACKDIR}"' in alpine_recipe_content | ||
| 292 | 310 | ||
| 293 | def test_guest_bundles(self, alpine_recipe_content): | 311 | def test_guest_bundles(self, alpine_recipe_content): |
| 294 | """Test XEN_GUEST_BUNDLES is set.""" | 312 | """Test XEN_GUEST_BUNDLES is set.""" |
| @@ -359,3 +377,98 @@ class TestReadme: | |||
| 359 | """Test custom handler instructions.""" | 377 | """Test custom handler instructions.""" |
| 360 | assert "xen_guest_import_" in readme_content | 378 | assert "xen_guest_import_" in readme_content |
| 361 | assert "XEN_GUEST_IMPORT_DEPENDS_" in readme_content | 379 | assert "XEN_GUEST_IMPORT_DEPENDS_" in readme_content |
| 380 | |||
| 381 | |||
| 382 | # ============================================================================ | ||
| 383 | # x86-64 configuration tests | ||
| 384 | # ============================================================================ | ||
| 385 | |||
| 386 | class TestXenImageMinimalX86Config: | ||
| 387 | """Test xen-image-minimal.bb x86-64 configuration. | ||
| 388 | |||
| 389 | These verify the fixes needed for Xen on qemux86-64: | ||
| 390 | - CPU passthrough to avoid AVX stripping by Xen CPUID filtering | ||
| 391 | - Memory configuration using QB_MEM_VALUE (not QB_MEM) | ||
| 392 | - dom0_mem in QB_XEN_CMDLINE_EXTRA for runqemu | ||
| 393 | - dom0_mem in static WKS syslinux config for guest autostart | ||
| 394 | - vgabios using reliable download mirror | ||
| 395 | """ | ||
| 396 | |||
| 397 | @pytest.fixture(scope="class") | ||
| 398 | def image_recipe_content(self, meta_virt_dir): | ||
| 399 | path = meta_virt_dir / "recipes-extended" / "images" / "xen-image-minimal.bb" | ||
| 400 | if not path.exists(): | ||
| 401 | pytest.skip("xen-image-minimal.bb not found") | ||
| 402 | return path.read_text() | ||
| 403 | |||
| 404 | @pytest.fixture(scope="class") | ||
| 405 | def wks_cfg_content(self, meta_virt_dir): | ||
| 406 | path = meta_virt_dir / "wic" / "qemuboot-xen-x86-64.cfg" | ||
| 407 | if not path.exists(): | ||
| 408 | pytest.skip("qemuboot-xen-x86-64.cfg not found") | ||
| 409 | return path.read_text() | ||
| 410 | |||
| 411 | @pytest.fixture(scope="class") | ||
| 412 | def vgabios_recipe_content(self, meta_virt_dir): | ||
| 413 | recipes = list((meta_virt_dir / "recipes-extended" / "vgabios").glob( | ||
| 414 | "vgabios_*.bb")) | ||
| 415 | if not recipes: | ||
| 416 | pytest.skip("vgabios recipe not found") | ||
| 417 | return recipes[0].read_text() | ||
| 418 | |||
| 419 | def test_cpu_kvm_host_passthrough(self, image_recipe_content): | ||
| 420 | """Test QB_CPU_KVM uses -cpu host for qemux86-64. | ||
| 421 | |||
| 422 | Xen's CPUID filtering strips AVX/AVX2 when using fixed CPU models | ||
| 423 | (e.g. Skylake-Client), causing illegal instruction crashes with | ||
| 424 | x86-64-v3 tune. -cpu host passes real features through KVM. | ||
| 425 | """ | ||
| 426 | assert 'QB_CPU_KVM:qemux86-64 = "-cpu host' in image_recipe_content | ||
| 427 | |||
| 428 | def test_qb_mem_value_not_qb_mem(self, image_recipe_content): | ||
| 429 | """Test memory uses QB_MEM_VALUE, not QB_MEM. | ||
| 430 | |||
| 431 | qemuboot-xen-defaults.bbclass uses a hard assign: | ||
| 432 | QB_MEM = "-m ${QB_MEM_VALUE}" | ||
| 433 | A recipe-level QB_MEM ?= cannot override it. QB_MEM_VALUE ??= | ||
| 434 | in the class is the intended override point. | ||
| 435 | """ | ||
| 436 | assert 'QB_MEM_VALUE' in image_recipe_content | ||
| 437 | # Should NOT have QB_MEM ?= (common mistake) | ||
| 438 | assert 'QB_MEM ?=' not in image_recipe_content | ||
| 439 | |||
| 440 | def test_dom0_mem_in_xen_cmdline(self, image_recipe_content): | ||
| 441 | """Test dom0_mem is in QB_XEN_CMDLINE_EXTRA for runqemu.""" | ||
| 442 | match = re.search( | ||
| 443 | r'QB_XEN_CMDLINE_EXTRA\s*=\s*"([^"]*)"', image_recipe_content) | ||
| 444 | assert match, "QB_XEN_CMDLINE_EXTRA not found" | ||
| 445 | assert "dom0_mem=" in match.group(1), \ | ||
| 446 | "dom0_mem= must be in QB_XEN_CMDLINE_EXTRA" | ||
| 447 | |||
| 448 | def test_dom0_mem_in_wks_syslinux(self, wks_cfg_content): | ||
| 449 | """Test dom0_mem is in static WKS syslinux config. | ||
| 450 | |||
| 451 | Without dom0_mem in the syslinux config, Xen gives ALL memory | ||
| 452 | to Dom0 and guest autostart fails with 'failed to free memory'. | ||
| 453 | QB_XEN_CMDLINE_EXTRA only affects runqemu's dynamic command line. | ||
| 454 | """ | ||
| 455 | assert "dom0_mem=" in wks_cfg_content, \ | ||
| 456 | "dom0_mem= must be in qemuboot-xen-x86-64.cfg for guest autostart" | ||
| 457 | |||
| 458 | def test_wks_xen_kernel_cmdline(self, wks_cfg_content): | ||
| 459 | """Test WKS config has correct Xen and kernel boot params.""" | ||
| 460 | assert "mboot.c32" in wks_cfg_content | ||
| 461 | assert "/xen.gz" in wks_cfg_content | ||
| 462 | assert "console=hvc0" in wks_cfg_content | ||
| 463 | |||
| 464 | def test_vgabios_savannah_mirror(self, vgabios_recipe_content): | ||
| 465 | """Test vgabios uses ${SAVANNAH_GNU_MIRROR} for reliable downloads. | ||
| 466 | |||
| 467 | The old http://savannah.gnu.org/download/ URL can redirect to | ||
| 468 | broken mirrors that return HTML instead of the tarball. | ||
| 469 | """ | ||
| 470 | assert "${SAVANNAH_GNU_MIRROR}" in vgabios_recipe_content, \ | ||
| 471 | "vgabios should use ${SAVANNAH_GNU_MIRROR} variable" | ||
| 472 | # Should NOT use raw savannah.gnu.org URL | ||
| 473 | assert "http://savannah.gnu.org/download" not in vgabios_recipe_content, \ | ||
| 474 | "Should not use raw savannah.gnu.org URL (broken redirects)" | ||
