import infra.basetest import json import os class PodmanBase(infra.basetest.BRTest): config = \ """ BR2_arm=y BR2_cortex_a9=y BR2_ARM_ENABLE_VFP=y BR2_TOOLCHAIN_EXTERNAL=y BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y BR2_PER_PACKAGE_DIRECTORIES=y BR2_SYSTEM_DHCP="eth0" BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_CUSTOM_VERSION=y BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="5.10.202" BR2_LINUX_KERNEL_DEFCONFIG="vexpress" BR2_LINUX_KERNEL_DTS_SUPPORT=y BR2_LINUX_KERNEL_INTREE_DTS_NAME="vexpress-v2p-ca9" BR2_PACKAGE_PODMAN=y BR2_PACKAGE_UTIL_LINUX=y BR2_PACKAGE_UTIL_LINUX_MOUNT=y BR2_PACKAGE_HOST_GO_BIN=y BR2_TARGET_ROOTFS_EXT2=y BR2_TARGET_ROOTFS_EXT2_SIZE="256M" # BR2_TARGET_ROOTFS_TAR is not set """ def do_test(self): class _Emul(): def __init__(self, orig_emulator): self.emulator = orig_emulator def run(self, cmd, timeout=-1): if timeout < 0: timeout = 60 return self.emulator.run(cmd, timeout) def stop(self): self.emulator.stop() kernel_file = os.path.join(self.builddir, 'images', 'zImage') dtb_file = os.path.join(self.builddir, 'images', 'vexpress-v2p-ca9.dtb') ext2_file = os.path.join(self.builddir, 'images', 'rootfs.ext2') self.emulator.boot( arch='armv5', kernel=kernel_file, kernel_cmdline=[ 'root=/dev/mmcblk0', 'rootwait', 'console=ttyAMA0', ], options=[ '-M', 'vexpress-a9', '-dtb', dtb_file, '-drive', f'file={ext2_file},if=sd,format=raw', ] ) self.emulator.login() # Trick: replace the original emulator with one that always # adds a timeout self.emulator = _Emul(self.emulator) # Do some preparation for rootless use self.assertRunOk("mount --make-shared /") self.assertRunOk("chmod 666 /dev/net/tun") self.assertRunOk("useradd -d /home/foo -m -s /bin/sh -u 1000 foo") self.assertRunOk("touch /etc/subuid /etc/subgid") self.assertRunOk("usermod --add-subuids 10000-75535 foo") self.assertRunOk("usermod --add-subgids 10000-75535 foo") # First, test podman as root (the current user) self.do_podman() # Now, test podman as non-root. We need a bit of setup # We need to use the same prompts for the user as used for root, so that the # REPLWrapper still detects the prompts. This means it is going to be a bit # difficut to directly see that it was a user that executed a command. self.assertRunOk('su -s /usr/bin/env - foo PS1="${PS1}" PS2="${PS2}" /bin/sh') output, _ = self.emulator.run("id -u") self.assertEqual(output[0], "1000", "Could not switch to non-root") self.do_podman() def do_podman(self): # The podman binary is huge, so it takes time to load... # Next calls will be faster, though, as it is going to be cached. self.assertRunOk('podman --version') # Check for an empty image store output, exit_code = self.emulator.run("podman image ls --format '{{ json }}'") img_info = json.loads("".join(output)) self.assertEqual(len(img_info), 0, f"{len(img_info)} image(s) already present") # Pull an image; it can take time: network, hash checksums... self.assertRunOk('podman image pull busybox:1.37.0') output, exit_code = self.emulator.run("podman image ls --format '{{ json }}'") img_info = json.loads("".join(output)) self.assertEqual(len(img_info), 1, f"{len(img_info)} image(s), expecting 1") self.assertTrue("Id" in img_info[0], '"Id" not in img_info[0]') self.assertTrue("Digest" in img_info[0], '"Digest" not in img_info[0]') self.assertEqual(img_info[0]["Names"][0], "docker.io/library/busybox:1.37.0") output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "", "Already in a container") # Spawn the container; that can take a bit of time # Propagate the prompt so that the REPLWrapper detects it self.assertRunOk( "podman container run --rm -ti -e PS1 -e br_container=podman busybox:1.37.0", ) # Twist! The command above is still running, but the shell it # started exposes the same prompt we expect. This is all what we want. output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "podman", "Not in a podman container") # Check that pid1 is the shell output, _ = self.emulator.run('readlink /proc/1/exe') self.assertEqual(output[0], "/bin/sh", f"PID1 is {output[0]}, should be /bin/sh") # Try to get something off the network # Using http, not https, as busybox' wget does not do https # Using --spider to just check we can reach the remote. output, exit_code = self.emulator.run('wget --spider http://google.com/') self.assertEqual(exit_code, 0, "wget did not succeed to reach google.com") self.assertEqual(output[-1], "remote file exists", "wget did not succeed to reach google.com") # Exit the container self.assertRunOk("exit 0") # Twist, take two! We are now back to the shell in the VM. output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "", "Still in a container") # Spawn a container, round two, but with an injected init this time self.assertRunOk( "podman container run --rm -ti -e PS1 --init -e br_container=podman busybox:1.37.0", ) output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "podman", "Not in a podman container") # Check that pid1 is the init injected by podman output, _ = self.emulator.run('readlink /proc/1/exe') self.assertEqual(output[0], "/run/podman-init", f"PID1 is {output[0]}, should be /run/podman-init") # Exit the container self.assertRunOk("exit 0") output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "", "Still in a container") # Use an image from another registry, spawn without pulling first self.assertRunOk( "podman container run --rm -ti -e PS1 -e br_container=podman quay.io/prometheus/busybox:latest", ) output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "podman", "Not in a podman container") self.assertRunOk("exit 0") output, _ = self.emulator.run('echo ${br_container}') self.assertEqual(output[0], "", "Still in a container") # Test networking between two containers self.assertRunOk("podman network create buz") self.assertRunOk( "podman container run --rm -ti --name pod007 --network buz --detach busybox:1.37.0", ) self.assertRunOk( "podman container run --rm -ti --name pod006 --network buz --detach busybox:1.37.0", ) # Ensure each pod can resolv itself and the other # (not using itertools.matrix() just for those trivial combinations) for pod1, pod2 in [ ("pod006", "pod006"), ("pod006", "pod007"), ("pod007", "pod007"), ("pod007", "pod006"), ]: output, exit_code = self.emulator.run( f"podman container exec {pod1} nslookup {pod2}", ) self.assertEqual(exit_code, 0) self.assertTrue(output[0].startswith("Server:")) self.assertTrue(output[1].startswith("Address:")) # Busybox' nslookup emits one "Non-authoritative answer" per # supported address familly: IPv4 and IPv6. self.assertEqual( len([line for line in output[2:] if line == "Non-authoritative answer:"]), 2, ) # But only IPv4 is available on this network self.assertEqual( len([line for line in output[2:] if line.startswith("Address:")]), 1, ) self.assertRunOk("podman container kill --all") output, _ = self.emulator.run("podman container ls --format '{{ json }}'") pod_info = json.loads("".join(output)) self.assertEqual(len(pod_info), 0, f"{len(pod_info)} container(s) still present, expecting 0") # Remove the offical image self.assertRunOk('podman image rm busybox:1.37.0') output, _ = self.emulator.run("podman image ls --format '{{ json }}'") img_info = json.loads("".join(output)) # There is still one image(the unofficial one from quay.io) self.assertEqual(len(img_info), 1, f"{len(img_info)} image(s) still present, expecting 1") # Remove all remaining images self.assertRunOk('podman image prune -af') output, exit_code = self.emulator.run("podman image ls --format '{{ json }}'") img_info = json.loads("".join(output)) self.assertEqual(len(img_info), 0, f"{len(img_info)} image(s) still present, expecting 0") class TestPodmanIptables(PodmanBase): def test_run(self): self.do_test() class TestPodmanNftables(PodmanBase): config = PodmanBase.config + """ BR2_PACKAGE_NFTABLES=y """ def test_run(self): self.do_test() class TestPodmanTini(PodmanBase): config = PodmanBase.config + """ BR2_PACKAGE_PODMAN_INIT_TINI=y """ def test_run(self): self.do_test() class TestPodmanSlirpIptables(PodmanBase): config = PodmanBase.config + """ BR2_PACKAGE_PODMAN_NET_SLIRP4NETNS=y """ def test_run(self): self.do_test() class TestPodmanSlirpNftables(PodmanBase): config = PodmanBase.config + """ BR2_PACKAGE_NFTABLES=y BR2_PACKAGE_PODMAN_NET_SLIRP4NETNS=y """ def test_run(self): self.do_test()