From c8ea8e3568cee5b38a0597564dedc89857826805 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 21 Nov 2024 12:59:47 +0100 Subject: [PATCH] test: add (slightly messy) disk-customization test --- test/test_build.py | 65 ++++++++++++++++++++++++++++++++++------------ test/testcases.py | 8 +++--- test/testutil.py | 46 +++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/test/test_build.py b/test/test_build.py index 7c94616d..cf6657c4 100644 --- a/test/test_build.py +++ b/test/test_build.py @@ -38,6 +38,7 @@ class ImageBuildResult(NamedTuple): img_arch: str container_ref: str rootfs: str + disk_config: str username: str password: str ssh_keyfile_private_path: str @@ -327,7 +328,8 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload, gpg_ bib_output = bib_output_path.read_text(encoding="utf8") results.append(ImageBuildResult( image_type, generated_img, tc.target_arch, - container_ref, tc.rootfs, username, password, + container_ref, tc.rootfs, tc.disk_config, + username, password, ssh_keyfile_private_path, kargs, bib_output, journal_output)) # generate new keyfile @@ -368,12 +370,16 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload, gpg_ "groups": ["wheel"], }, ], - "filesystem": testutil.create_filesystem_customizations(tc.rootfs), "kernel": { "append": kargs, }, }, } + if tc.disk_config: + cfg["customizations"]["disk"] = testutil.create_disk_customizations(tc.disk_config) + # XXX: disk_config/fs_config cannot be combined, for our tests disk wins + elif tc.rootfs: + cfg["customizations"]["filesystem"] = testutil.create_filesystem_customizations(tc.disk_config, tc.rootfs) config_json_path = output_path / "config.json" config_json_path.write_text(json.dumps(cfg), encoding="utf-8") @@ -473,7 +479,8 @@ def del_ami(): for image_type in image_types: results.append(ImageBuildResult( image_type, artifact[image_type], tc.target_arch, - container_ref, tc.rootfs, username, password, + container_ref, tc.rootfs, tc.disk_config, + username, password, ssh_keyfile_private_path, kargs, bib_output, journal_output, metadata)) yield results @@ -532,19 +539,11 @@ def test_image_boots(image_type): # XXX: read the fully yaml instead? assert f"image: {image_type.container_ref}" in output - # check the minsize specified in the build configuration for each mountpoint against the sizes in the image - # TODO: replace 'df' call with 'parted --json' and find the partition size for each mountpoint - exit_status, output = test_vm.run("df --output=target,size", user="root", - keyfile=image_type.ssh_keyfile_private_path) - assert exit_status == 0 - # parse the output of 'df' to a mountpoint -> size dict for convenience - mountpoint_sizes = {} - for line in output.splitlines()[1:]: - fields = line.split() - # Note that df output is in 1k blocks, not bytes - mountpoint_sizes[fields[0]] = int(fields[1]) * 2 ** 10 # in bytes + if image_type.disk_config: + assert_disk_customizations(image_type, test_vm) + else: + assert_fs_customizations(image_type, test_vm) - assert_fs_customizations(image_type, mountpoint_sizes) @pytest.mark.parametrize("image_type", gen_testcases("ami-boot"), indirect=["image_type"]) @@ -663,17 +662,49 @@ def test_multi_build_request(images): assert artifacts == expected -def assert_fs_customizations(image_type, mountpoint_sizes): +def assert_fs_customizations(image_type, test_vm): """ Asserts that each mountpoint that appears in the build configuration also appears in mountpoint_sizes. TODO: assert that the size of each filesystem (or partition) also matches the expected size based on the customization. """ - fs_customizations = testutil.create_filesystem_customizations(image_type.rootfs) + # check the minsize specified in the build configuration for each mountpoint against the sizes in the image + # TODO: replace 'df' call with 'parted --json' and find the partition size for each mountpoint + exit_status, output = test_vm.run("df --output=target,size", user="root", + keyfile=image_type.ssh_keyfile_private_path) + assert exit_status == 0 + # parse the output of 'df' to a mountpoint -> size dict for convenience + mountpoint_sizes = {} + for line in output.splitlines()[1:]: + fields = line.split() + # Note that df output is in 1k blocks, not bytes + mountpoint_sizes[fields[0]] = int(fields[1]) * 2 ** 10 # in bytes + + fs_customizations = testutil.create_filesystem_customizations(image_type.disk_config, image_type.rootfs) for fs in fs_customizations: mountpoint = fs["mountpoint"] if mountpoint == "/": # / is actually /sysroot mountpoint = "/sysroot" assert mountpoint in mountpoint_sizes + + +def assert_disk_customizations(image_type, test_vm): + exit_status, output = test_vm.run("findmnt --json", user="root", + keyfile=image_type.ssh_keyfile_private_path) + assert exit_status == 0 + findmnt = json.loads(output) + if dc := image_type.disk_config: + if dc == "lvm": + mnts = [mnt for mnt in findmnt["filesystems"][0]["children"] + if mnt["target"] == "/sysroot"] + assert len(mnts) == 1 + assert "/dev/mapper/vg00-rootlv" == mnts[0]["source"] + elif dc == "btrfs": + mnts = [mnt for mnt in findmnt["filesystems"][0]["children"] + if mnt["target"] == "/sysroot"] + assert len(mnts) == 1 + assert "btrfs" == mnts[0]["fstype"] + # ensure sysroot comes from the "root" subvolume + assert mnts[0]["source"].endswith("[/root]") diff --git a/test/testcases.py b/test/testcases.py index 0cf51e1d..ed0ee45e 100644 --- a/test/testcases.py +++ b/test/testcases.py @@ -25,8 +25,8 @@ class TestCase: rootfs: str = "" # Sign the container_ref and use the new signed image instead of the original one sign: bool = False - # use special partition_mode like "lvm" - partition_mode: str = "" + # use special disk_config like "lvm" + disk_config: str = "" def bib_rootfs_args(self): if self.rootfs: @@ -92,9 +92,9 @@ def gen_testcases(what): # pylint: disable=too-many-return-statements # and custom with raw (this is arbitrary, we could do it the # other way around too test_cases.append( - TestCaseCentos(image="raw", partition_mode="lvm")) + TestCaseCentos(image="raw", disk_config="lvm")) test_cases.append( - TestCaseFedora(image="raw", partition_mode="btrfs")) + TestCaseFedora(image="raw", disk_config="btrfs")) # do a cross arch test too if platform.machine() == "x86_64": # TODO: re-enable once diff --git a/test/testutil.py b/test/testutil.py index b853c613..cbecaf7a 100644 --- a/test/testutil.py +++ b/test/testutil.py @@ -109,7 +109,11 @@ def deregister_ami(ami_id): print(f"Error {err_code}: {err_msg}") -def create_filesystem_customizations(rootfs: str): +def create_filesystem_customizations(disk_config: str, rootfs: str): + if disk_config: + # XXX: make this nicer, disk-customizations and fs-customizations + # cannot be combined right now + return [] if rootfs == "btrfs": # only minimal customizations are supported for btrfs currently return [ @@ -140,6 +144,46 @@ def create_filesystem_customizations(rootfs: str): ] +def create_disk_customizations(disk_config: str): + if disk_config == "lvm": + return { + # XXX: needed because we have no defaults in images currently + "minsize": "10 GiB", + "partitions": [ + { + "type": "lvm", + "logical_volumes": [ + { + # XXX: without this we get + # .pipelines[1].stages[2].options.volumes[0].size: + # '0B' does not match '[1-9][0-9]*[bBsSkKmMgGtTpPeE]?' + "minsize": 10000000000, + "fs_type": "xfs", + "mountpoint": "/", + } + ] + } + ] + } + if disk_config == "btrfs": + return { + # XXX: needed because we have no defaults in images currently + "minsize": "10 GiB", + "partitions": [ + { + "type": "btrfs", + "subvolumes": [ + { + "name": "varlog", + "mountpoint": "/var/log", + } + ] + } + ] + } + raise ValueError(f"unsupported disk_config {disk_config}") + + # podman_run_common has the common prefix for the podman run invocations podman_run_common = [ "podman", "run", "--rm",