1import os 2import subprocess 3import time 4 5import infra.basetest 6 7 8class TestMdadm(infra.basetest.BRTest): 9 # This test creates a dm-raid volume with mdadm. A specific Kernel 10 # need to be built with a config fragment enabling this support. 11 kernel_fragment = \ 12 infra.filepath("tests/package/test_mdadm/linux-mdadm.fragment") 13 config = \ 14 f""" 15 BR2_aarch64=y 16 BR2_TOOLCHAIN_EXTERNAL=y 17 BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0" 18 BR2_LINUX_KERNEL=y 19 BR2_LINUX_KERNEL_CUSTOM_VERSION=y 20 BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.1.75" 21 BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y 22 BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config" 23 BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="{kernel_fragment}" 24 BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y 25 BR2_PACKAGE_E2FSPROGS=y 26 BR2_PACKAGE_MDADM=y 27 BR2_TARGET_ROOTFS_CPIO=y 28 BR2_TARGET_ROOTFS_CPIO_GZIP=y 29 # BR2_TARGET_ROOTFS_TAR is not set 30 """ 31 32 def test_run(self): 33 # Test configuration: 34 md_dev = "/dev/md0" 35 storage_devs = ["/dev/vda", "/dev/vdb", "/dev/vdc"] 36 storage_size = 16 # Mega Bytes 37 failing_dev = storage_devs[-1] 38 mnt_pt = "/mnt/raid-storage" 39 data_file = f"{mnt_pt}/data.bin" 40 41 qemu_storage_opts = [] 42 for i in range(len(storage_devs)): 43 disk_file = os.path.join(self.builddir, "images", f"disk{i}.img") 44 self.emulator.logfile.write(f"Creating disk image: {disk_file}\n") 45 self.emulator.logfile.flush() 46 subprocess.check_call( 47 ["dd", "if=/dev/zero", f"of={disk_file}", 48 "bs=1M", f"count={storage_size}"], 49 stdout=self.emulator.logfile, 50 stderr=self.emulator.logfile) 51 opts = ["-drive", f"file={disk_file},if=virtio,format=raw"] 52 qemu_storage_opts += opts 53 54 img = os.path.join(self.builddir, "images", "rootfs.cpio.gz") 55 kern = os.path.join(self.builddir, "images", "Image") 56 self.emulator.boot(arch="aarch64", 57 kernel=kern, 58 kernel_cmdline=["console=ttyAMA0"], 59 options=["-M", "virt", "-cpu", "cortex-a57", "-m", "256M", 60 "-initrd", img] + qemu_storage_opts) 61 self.emulator.login() 62 63 # Test the program can execute. 64 self.assertRunOk("mdadm --version") 65 66 # Show the mdstat, to confirm the Kernel has support and the 67 # configuration is empty. 68 cat_mdstat_cmd = "cat /proc/mdstat" 69 self.assertRunOk(cat_mdstat_cmd) 70 71 # We create a raid5 array with the drives. 72 cmd = f"mdadm --create --verbose {md_dev} --level=5 " 73 cmd += f"--raid-devices={len(storage_devs)} " 74 cmd += " ".join(storage_devs) 75 self.assertRunOk(cmd) 76 77 # We show again mdstat, to confirm the array creation. This is 78 # also for debugging. 79 self.assertRunOk(cat_mdstat_cmd) 80 81 # We format the device as ext4 and mount it. 82 self.assertRunOk(f"mkfs.ext4 {md_dev}") 83 self.assertRunOk(f"mkdir -p {mnt_pt}") 84 self.assertRunOk(f"mount {md_dev} {mnt_pt}") 85 86 # We store some random data on this new filesystem. Note: this 87 # file is slightly larger than a single storage drive. This 88 # data file should span over two drives and use the raid5. 89 file_size = storage_size + 4 90 cmd = f"dd if=/dev/urandom of={data_file} bs=1M count={file_size}" 91 self.assertRunOk(cmd) 92 93 # We compute the hash of our data, and save it for later. 94 hash_cmd = f"sha256sum {data_file}" 95 out, ret = self.emulator.run(hash_cmd) 96 self.assertEqual(ret, 0) 97 data_sha256 = out[0] 98 99 # We run few common mdadm commands. 100 self.assertRunOk("mdadm --detail --scan") 101 self.assertRunOk(f"mdadm --query {md_dev}") 102 self.assertRunOk(f"mdadm --detail --test {md_dev}") 103 self.assertRunOk(f"mdadm --action=check {md_dev}") 104 self.assertRunOk(f"mdadm --monitor --oneshot {md_dev}") 105 106 # We mark a device as "failed". 107 self.assertRunOk(f"mdadm {md_dev} --fail {failing_dev}") 108 109 # The monitoring should now report the array as degraded. 110 monitor_cmd = f"mdadm --monitor --oneshot {md_dev}" 111 out, ret = self.emulator.run(monitor_cmd) 112 self.assertEqual(ret, 0) 113 self.assertIn("DegradedArray", "\n".join(out)) 114 115 # We remove the failing drive from the array. 116 self.assertRunOk(f"mdadm {md_dev} --remove {failing_dev}") 117 118 # We wipe the failing drive by writing zeroes. 119 cmd = f"dd if=/dev/zero of={failing_dev} bs=1M count={storage_size}" 120 self.assertRunOk(cmd) 121 122 # We add back this blank drive to the array. 123 self.assertRunOk(f"mdadm {md_dev} --add {failing_dev}") 124 125 # Device rebuild can take a variable amount of time, depending 126 # on the load of the test controller host. So we will allow 127 # several attempts, before failing. 128 for attempt in range(10): 129 # We wait few seconds to let the device rebuild. 130 time.sleep(3 * self.timeout_multiplier) 131 132 # Once rebuilt, the array should no longer be marked as 133 # degraded. 134 out, ret = self.emulator.run(monitor_cmd) 135 self.assertEqual(ret, 0) 136 if "DegradedArray" not in "\n".join(out): 137 break 138 else: 139 self.fail("Timeout while waiting for the array to rebuild.") 140 141 # With all those array manipulations, the data file should not 142 # be corrupted. We should be able to recompute the same hash 143 # as before. 144 out, ret = self.emulator.run(hash_cmd) 145 self.assertEqual(ret, 0) 146 self.assertEqual(out[0], data_sha256) 147