1import os
2import subprocess
3
4import infra.basetest
5
6
7class TestLvm2(infra.basetest.BRTest):
8    # The lvm2 package has _LINUX_CONFIG_FIXUPS, so we cannot use
9    # the runtime test pre-built Kernel. We need to compile a Kernel
10    # to make sure it will include the required configuration.
11    config = \
12        """
13        BR2_aarch64=y
14        BR2_TOOLCHAIN_EXTERNAL=y
15        BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0"
16        BR2_LINUX_KERNEL=y
17        BR2_LINUX_KERNEL_CUSTOM_VERSION=y
18        BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.1.77"
19        BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
20        BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config"
21        BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
22        BR2_PACKAGE_E2FSPROGS=y
23        BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
24        BR2_PACKAGE_LVM2=y
25        BR2_TARGET_ROOTFS_CPIO=y
26        BR2_TARGET_ROOTFS_CPIO_GZIP=y
27        # BR2_TARGET_ROOTFS_TAR is not set
28        """
29
30    def get_free_disk_space(self, path):
31        out, ret = self.emulator.run(f"df -k {path}")
32        self.assertEqual(ret, 0)
33        return int(out[1].split()[3])
34
35    def test_run(self):
36        # Test configuration:
37        storage_devs = ["/dev/vda", "/dev/vdb", "/dev/vdc"]
38        storage_size = 16                       # Mega Bytes
39        lvm_vg = "br_vg"                        # Volume Group name
40        lvm_lv = "br_lv"                        # Logical Volume name
41        lv_dev = f"/dev/{lvm_vg}/{lvm_lv}"      # Logical Volume dev name
42        mnt_pt = "/mnt/lvm2-storage"
43        data_file = f"{mnt_pt}/data.bin"
44
45        qemu_storage_opts = []
46        for i in range(len(storage_devs)):
47            disk_file = os.path.join(self.builddir, "images", f"disk{i}.img")
48            self.emulator.logfile.write(f"Creating disk image: {disk_file}\n")
49            self.emulator.logfile.flush()
50            subprocess.check_call(
51                ["dd", "if=/dev/zero", f"of={disk_file}",
52                 "bs=1M", f"count={storage_size}"],
53                stdout=self.emulator.logfile,
54                stderr=self.emulator.logfile)
55            opts = ["-drive", f"file={disk_file},if=virtio,format=raw"]
56            qemu_storage_opts += opts
57
58        img = os.path.join(self.builddir, "images", "rootfs.cpio.gz")
59        kern = os.path.join(self.builddir, "images", "Image")
60        self.emulator.boot(arch="aarch64",
61                           kernel=kern,
62                           kernel_cmdline=["console=ttyAMA0"],
63                           options=["-M", "virt", "-cpu", "cortex-a57", "-m", "256M",
64                                    "-initrd", img] + qemu_storage_opts)
65        self.emulator.login()
66
67        # Test the program can execute.
68        self.assertRunOk("lvm version")
69
70        # We did not created any Physical Volume yet. We should NOT
71        # see any of our storage devices in a pvscan.
72        out, ret = self.emulator.run("pvscan")
73        self.assertEqual(ret, 0)
74        for dev in storage_devs:
75            self.assertNotIn(dev, "\n".join(out))
76
77        # We initialize our Physical Volumes (PVs).
78        pv_devs = " ".join(storage_devs)
79        self.assertRunOk(f"pvcreate {pv_devs}")
80
81        # We run few diagnostic commands related to PVs.
82        self.assertRunOk(f"pvck {pv_devs}")
83        self.assertRunOk(f"pvdisplay {pv_devs}")
84        self.assertRunOk("pvs")
85
86        # Now we initialized the PVs, we should see them in a pvscan.
87        out, ret = self.emulator.run("pvscan")
88        self.assertEqual(ret, 0)
89        for dev in storage_devs:
90            self.assertIn(dev, "\n".join(out))
91
92        # We create a Volume Group (VG) including two of our three
93        # PVs.
94        cmd = f"vgcreate {lvm_vg} {storage_devs[0]} {storage_devs[1]}"
95        self.assertRunOk(cmd)
96
97        # We run few diagnostic commands related to VGs.
98        self.assertRunOk(f"vgck {lvm_vg}")
99        self.assertRunOk(f"vgdisplay {lvm_vg}")
100        self.assertRunOk("vgscan")
101        self.assertRunOk("vgs")
102
103        # We create a Logical Volume (LV) in our VG.
104        self.assertRunOk(f"lvcreate -l 100%FREE -n {lvm_lv} {lvm_vg}")
105
106        # We check LVM created the LV device.
107        self.assertRunOk(f"ls -al {lv_dev}")
108
109        # We run few diagnostic commands related to LVs.
110        self.assertRunOk("lvscan")
111        self.assertRunOk("lvs")
112
113        # We create a ext4 filesystem on our LV.
114        self.assertRunOk(f"mkfs.ext4 {lv_dev}")
115
116        # We create a mount point directory and mount the device.
117        self.assertRunOk(f"mkdir -p {mnt_pt}")
118        self.assertRunOk(f"mount {lv_dev} {mnt_pt}")
119
120        # We create a data file in our new filesystem. Note: this file
121        # is slightly larger than a single PV. This data file should
122        # span over the two PVs in the VG.
123        data_size = storage_size + 4
124        cmd = f"dd if=/dev/urandom of={data_file} bs=1M count={data_size}"
125        self.assertRunOk(cmd)
126
127        # We compute the hash of our data, and save it for later.
128        hash_cmd = f"sha256sum {data_file}"
129        out, ret = self.emulator.run(hash_cmd)
130        self.assertEqual(ret, 0)
131        data_sha256 = out[0]
132
133        # We compute the free space of the mount point.
134        fs_free_space = self.get_free_disk_space(mnt_pt)
135
136        # We extend of VG with our third PV.
137        self.assertRunOk(f"vgextend {lvm_vg} {storage_devs[2]}")
138
139        # We grow the LV to use all the space of the VG.
140        self.assertRunOk(f"lvresize -l +100%FREE {lvm_vg}/{lvm_lv}")
141
142        # We resize the filesystem to use all the LV space.
143        self.assertRunOk(f"resize2fs {lv_dev}")
144
145        # Now we grew the LV and resized the filesystem, we recompute
146        # the free space and check we have more.
147        fs2_free_space = self.get_free_disk_space(mnt_pt)
148        self.assertGreater(fs2_free_space, fs_free_space)
149
150        # With all those on-the-fly operations on the mounted
151        # filesystem, the data file should be intact. We should
152        # recompute the same data checksum as before.
153        out, ret = self.emulator.run(hash_cmd)
154        self.assertEqual(ret, 0)
155        self.assertEqual(out[0], data_sha256)
156
157        # Finally, we unmount the filesystem. It should not contain
158        # any error.
159        self.assertRunOk(f"umount {mnt_pt}")
160        self.assertRunOk(f"e2fsck -f -n {lv_dev}")
161