test: make boot mode work in unprivileged containers, take TEST_MKOSI_ARGS (#43531)

The final enablement pieces needed for coco tests in systemd CI. Runners
will be incus containers, these two commits enable running coco tests
with mkosi boot (where image is a directory), then start a confidential
guest with vmspawn as part of the test.
This commit is contained in:
Luca Boccassi
2026-08-27 13:44:47 +01:00
committed by GitHub
2 changed files with 45 additions and 1 deletions
+9
View File
@@ -120,6 +120,15 @@ rerun the test.
`TEST_NO_KVM=1`: Disable qemu KVM auto-detection (may be necessary when you're
trying to run the *vanilla* qemu and have both qemu and qemu-kvm installed)
`TEST_MKOSI_ARGS=options`: Extra options (split like a shell command line)
passed to the `mkosi` invocation that launches the test image, without
affecting the image build. Only the `--option=value` syntax is accepted. For example,
`TEST_MKOSI_ARGS="--format=directory --output-dir=build/mkosi.output.dir"`
boots the test container from a directory format output (which has to be built
beforehand, into a separate output directory), allowing boot mode tests to run
in environments that can't dissect disk images, such as unprivileged
containers, where loop devices and udev are not available.
`TEST_MATCH_SUBTEST=subtest`: If the test makes use of `run_subtests` use this
variable to provide a POSIX extended regex to run only subtests matching the
expression.
@@ -365,6 +365,15 @@ def process_coverage(args: argparse.Namespace, summary: Summary, name: str, jour
print(f'Wrote coverage report for {name} to {output}', file=sys.stderr)
def in_userns() -> bool:
# The initial user namespace has a well-known inode number (USER_NS_INIT_INO, linux/nsfs.h).
# Without kernel support for user namespaces, ours can only be the initial one.
try:
return os.stat('/proc/self/ns/user').st_ino != 0xEFFFFFFD
except OSError:
return False
def coco_host_type() -> Optional[str]:
try:
if Path('/sys/module/kvm_intel/parameters/tdx').read_text().strip() == 'Y':
@@ -661,6 +670,12 @@ def main() -> None:
'''
)
# Extra mkosi options for this invocation only. Each has to use the '--option=value' syntax.
test_mkosi_args = shlex.split(os.getenv('TEST_MKOSI_ARGS', ''))
for arg in test_mkosi_args:
if not re.fullmatch('--[^=]+=.*', arg):
sys.exit(f"TEST_MKOSI_ARGS must contain only '--option=value' mkosi options, got: {arg}")
cmd = [
args.mkosi,
'--directory', os.fspath(args.mkosi_dir),
@@ -704,9 +719,29 @@ def main() -> None:
),
'--credential', f"journal.storage={'persistent' if sys.stdin.isatty() else args.storage}",
*(['--runtime-build-sources=no', '--register=no'] if not sys.stdin.isatty() else []),
*test_mkosi_args,
'vm' if vm else 'boot',
*(
['--', '--capability=CAP_BPF', f'--suppress-sync={"yes" if args.suppress_sync else "no"}', *vm_images_args, *coco_boot_args]
[
'--',
'--capability=CAP_BPF',
f'--suppress-sync={"yes" if args.suppress_sync else "no"}',
# In a non-initial user namespace the kernel only allows mounting a fresh proc or
# sysfs if the mount namespace already contains a fully visible mount of the same
# filesystem type (mount_too_revealing()), which nspawn's pivoted root doesn't.
# Satisfy the check with read-only binds, but only when actually confined, as
# they expose the outer proc and sysfs to the container. Bind non-recursively:
# carried-along submounts (e.g. lxcfs overmounts of container runtimes) would
# spoil full visibility wherever they end up locked, and --bind-ro wouldn't make
# them read-only either.
*(
['--bind-ro=/proc:/work/proc:norbind', '--bind-ro=/sys:/work/sys:norbind']
if in_userns()
else []
),
*vm_images_args,
*coco_boot_args,
]
if not vm
else []
),