Skip to content

Commit 3563055

Browse files
committed
tests: add test for pivot_root in initramfs support
The only way to run in the proper initramfs is to start a VM using a custom initrd that runs runc. This should be a fairly reasonable smoke-test that matches what minikube and kata do. Unfortunately, running the right qemu for the native architecture on various distros is a little different, so we need a helper function to get it to work on both Debian and AlmaLinux. Signed-off-by: Aleksa Sarai <[email protected]>
1 parent f730bd4 commit 3563055

File tree

4 files changed

+170
-3
lines changed

4 files changed

+170
-3
lines changed

Diff for: .cirrus.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ task:
7979
CIRRUS_WORKING_DIR: /home/runc
8080
GO_VERSION: "1.23"
8181
BATS_VERSION: "v1.11.0"
82-
RPMS: gcc git iptables jq glibc-static libseccomp-devel make criu fuse-sshfs container-selinux
82+
RPMS: cpio gcc git iptables jq qemu-kvm glibc-static libseccomp-devel make criu fuse-sshfs container-selinux
8383
# yamllint disable rule:key-duplicates
8484
matrix:
8585
DISTRO: almalinux-8

Diff for: .github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ jobs:
120120
- name: install deps
121121
run: |
122122
sudo apt update
123-
sudo apt -y install libseccomp-dev sshfs uidmap
123+
sudo apt -y install cpio libseccomp-dev qemu-kvm sshfs uidmap
124124
125125
- name: install CRIU
126126
if: ${{ matrix.criu == '' }}

Diff for: Vagrantfile.fedora

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Vagrant.configure("2") do |config|
2424
cat << EOF | dnf -y --exclude=kernel,kernel-core shell && break
2525
config install_weak_deps false
2626
update
27-
install iptables gcc golang-go make glibc-static libseccomp-devel bats jq git-core criu fuse-sshfs container-selinux
27+
install cpio iptables gcc golang-go make qemu-kvm glibc-static libseccomp-devel bats jq git-core criu fuse-sshfs container-selinux
2828
ts run
2929
EOF
3030
done

Diff for: tests/integration/initramfs.bats

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env bats
2+
3+
load helpers
4+
5+
# Rather than building our own kernel for use with qemu, just reuse the host's
6+
# kernel since we just need some kernel that supports containers that we can
7+
# use to run our custom initramfs.
8+
function find_vmlinuz() {
9+
shopt -s nullglob
10+
local candidate candidates=(
11+
/boot/vmlinuz
12+
/boot/vmlinuz-"$(uname -r)"*
13+
/usr/lib*/modules/"$(uname -r)"/vmlinuz*
14+
)
15+
shopt -u nullglob
16+
17+
for candidate in "${candidates[@]}"; do
18+
[ -e "$candidate" ] || continue
19+
export HOST_KERNEL="$candidate"
20+
return 0
21+
done
22+
23+
# Actuated doesn't provide a copy of the boot kernel, so we have to skip
24+
# the test in that case. It also seems they don't allow aarch64 guests
25+
# either (see <https://docs.actuated.com/examples/kvm-guest/>).
26+
skip "could not find host vmlinuz kernel"
27+
}
28+
29+
function setup() {
30+
INITRAMFS_ROOT="$(mktemp -d "$BATS_RUN_TMPDIR/runc-initramfs.XXXXXX")"
31+
find_vmlinuz
32+
}
33+
34+
function teardown() {
35+
[ -v INITRAMFS_ROOT ] && rm -rf "$INITRAMFS_ROOT"
36+
}
37+
38+
function qemu_native() {
39+
# Different distributions put qemu-kvm in different locations and with
40+
# different names. Debian and Ubuntu have a "kvm" binary, while AlmaLinux
41+
# has /usr/libexec/qemu-kvm.
42+
local qemu_binary="" qemu_candidates=("kvm" "qemu-kvm" "/usr/libexec/qemu-kvm")
43+
local candidate
44+
for candidate in "${qemu_candidates[@]}"; do
45+
"$candidate" -help &>/dev/null || continue
46+
qemu_binary="$candidate"
47+
break
48+
done
49+
# TODO: Maybe we should also try to call qemu-system-FOO for the current
50+
# architecture if qemu-kvm is missing?
51+
[ -n "$qemu_binary" ] || skip "could not find qemu-kvm binary"
52+
53+
local machine=
54+
case "$(go env GOARCH)" in
55+
386 | amd64)
56+
# Try to use a slightly newer PC CPU.
57+
machine="pc"
58+
;;
59+
arm | arm64)
60+
# ARM doesn't provide a "default" machine value (because its use is so
61+
# varied) so we have to specify the machine manually.
62+
machine="virt"
63+
;;
64+
*)
65+
echo "could not figure out -machine argument for qemu -- using default" >&2
66+
;;
67+
esac
68+
# We use -cpu max to ensure that the glibc we built runc with doesn't rely
69+
# on CPU features that the default QEMU CPU doesn't support (such as on
70+
# AlmaLinux 9).
71+
local machine_args=("-cpu" "max")
72+
[ -n "$machine" ] && machine_args+=("-machine" "$machine")
73+
74+
sane_run --timeout=3m \
75+
"$qemu_binary" "${machine_args[@]}" "$@"
76+
if [ "$status" -ne 0 ]; then
77+
# To help with debugging, output the set of valid machine values.
78+
"$qemu_binary" -machine help >&2
79+
fi
80+
}
81+
82+
@test "runc run [initramfs + pivot_root]" {
83+
requires root
84+
85+
# Configure our minimal initrd.
86+
mkdir -p "$INITRAMFS_ROOT/initrd"
87+
pushd "$INITRAMFS_ROOT/initrd"
88+
89+
# Use busybox as a base for our initrd.
90+
tar --exclude './dev/*' -xf "$BUSYBOX_IMAGE"
91+
# Make sure that "sh" and "poweroff" are installed, otherwise qemu will
92+
# boot loop when init stops.
93+
[ -x ./bin/sh ] || skip "busybox image is missing /bin/sh"
94+
[ -x ./bin/poweroff ] || skip "busybox image is missing /bin/poweroff"
95+
96+
# Copy the runc binary into the container. In theory we would prefer to
97+
# copy a static binary, but some distros (like openSUSE) don't ship
98+
# libseccomp-static so requiring a static build for any integration test
99+
# run wouldn't work. Instead, we copy all of the library dependencies into
100+
# the rootfs (note that we also have to copy ld-linux-*.so because runc was
101+
# probably built with a newer glibc than the one in our busybox image.
102+
cp "$RUNC" ./bin/runc
103+
readarray -t runclibs \
104+
<<<"$(ldd "$RUNC" | grep -Eo '/[^ ]*lib[^ ]*.so.[^ ]*')"
105+
cp -vt ./lib64/ "${runclibs[@]}"
106+
# busybox has /lib64 -> /lib so we can just fill in one path.
107+
108+
# Create a container bundle using the same busybox image.
109+
mkdir -p ./run/bundle
110+
pushd ./run/bundle
111+
mkdir -p rootfs
112+
tar --exclude './dev/*' -C rootfs -xf "$BUSYBOX_IMAGE"
113+
runc spec
114+
update_config '.process.args = ["/bin/echo", "hello from inside the container"]'
115+
popd
116+
117+
# Build a custom /init script.
118+
cat >./init <<-EOF
119+
#!/bin/sh
120+
121+
set -x
122+
echo "==START INIT SCRIPT=="
123+
124+
mkdir -p /proc /sys
125+
mount -t proc proc /proc
126+
mkdir -p /sys
127+
mount -t sysfs sysfs /sys
128+
129+
mkdir -p /sys/fs/cgroup
130+
mount -t cgroup2 cgroup2 /sys/fs/cgroup
131+
132+
mkdir -p /tmp
133+
mount -t tmpfs tmpfs /tmp
134+
135+
mkdir -p /dev
136+
mount -t devtmpfs devtmpfs /dev
137+
mkdir -p /dev/pts
138+
mount -t devpts -o newinstance devpts /dev/pts
139+
mkdir -p /dev/shm
140+
mount --bind /tmp /dev/shm
141+
142+
# Wait for as little as possible if we panic so we can output the error
143+
# log as part of the test failure before the test times out.
144+
echo 1 >/proc/sys/kernel/panic
145+
146+
runc run -b /run/bundle ctr
147+
148+
echo "==END INIT SCRIPT=="
149+
poweroff -f
150+
EOF
151+
chmod +x ./init
152+
153+
find . | cpio -o -H newc >"$INITRAMFS_ROOT/initrd.cpio"
154+
popd
155+
156+
# Now we can just run the image (use qemu-kvm so that we run on the same
157+
# architecture as the host system). We can just reuse the host kernel.
158+
qemu_native \
159+
-initrd "$INITRAMFS_ROOT/initrd.cpio" \
160+
-kernel "$HOST_KERNEL" \
161+
-m 512M \
162+
-nographic -append console=ttyS0 -no-reboot
163+
[ "$status" -eq 0 ]
164+
[[ "$output" = *"==START INIT SCRIPT=="* ]]
165+
[[ "$output" = *"hello from inside the container"* ]]
166+
[[ "$output" = *"==END INIT SCRIPT=="* ]]
167+
}

0 commit comments

Comments
 (0)