| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 4 | # |
| 5 | # x86_64 NVMe migration test |
| 6 | |
| 7 | from migration import MigrationTest |
| 8 | from qemu_test import QemuSystemTest, Asset |
| 9 | from qemu_test import wait_for_console_pattern |
| 10 | from qemu_test import exec_command, exec_command_and_wait_for_pattern |
| 11 | |
| 12 | |
| 13 | class X8664NVMeMigrationTest(MigrationTest): |
| 14 | ASSET_KERNEL = Asset( |
| 15 | ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' |
| 16 | '/31/Server/x86_64/os/images/pxeboot/vmlinuz'), |
| 17 | 'd4738d03dbbe083ca610d0821d0a8f1488bebbdccef54ce33e3adb35fda00129') |
| 18 | |
| 19 | ASSET_INITRD = Asset( |
| 20 | ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' |
| 21 | '/31/Server/x86_64/os/images/pxeboot/initrd.img'), |
| 22 | '277cd6c7adf77c7e63d73bbb2cded8ef9e2d3a2f100000e92ff1f8396513cd8b') |
| 23 | |
| 24 | ASSET_DISKIMAGE = Asset( |
| 25 | ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' |
| 26 | '/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2'), |
| 27 | 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0') |
| 28 | |
| 29 | DEFAULT_KERNEL_PARAMS = ('root=/dev/nvme0n1p1 console=ttyS0 net.ifnames=0 ' |
| 30 | 'rd.rescue quiet') |
| 31 | |
| 32 | def wait_for_console_pattern(self, success_message, vm): |
| 33 | wait_for_console_pattern( |
| 34 | self, |
| 35 | success_message, |
| 36 | failure_message="Kernel panic - not syncing", |
| 37 | vm=vm, |
| 38 | ) |
| 39 | |
| 40 | def exec_command_and_check(self, command, vm): |
| 41 | prompt = '# ' |
| 42 | exec_command_and_wait_for_pattern(self, |
| 43 | f"{command} && echo OK || echo FAIL", |
| 44 | 'FAIL', vm=vm) |
| 45 | # Note, that commands we send to the console are echo-ed back, |
| 46 | # so if we have a word "FAIL" in the command itself, we should |
| 47 | # expect to see it once. |
| 48 | wait_for_console_pattern(self, 'OK', failure_message="FAIL", vm=vm) |
| 49 | self.wait_for_console_pattern(prompt, vm) |
| 50 | |
| 51 | def configure_machine(self, vm): |
| 52 | kernel_path = self.ASSET_KERNEL.fetch() |
| 53 | initrd_path = self.ASSET_INITRD.fetch() |
| 54 | diskimage_path = self.ASSET_DISKIMAGE.fetch() |
| 55 | |
| 56 | vm.set_console() |
| 57 | vm.add_args("-cpu", "max") |
| 58 | vm.add_args("-m", "2G") |
| 59 | vm.add_args("-accel", "kvm") |
| 60 | |
| 61 | vm.add_args('-drive', |
| 62 | f'file={diskimage_path},if=none,id=drv0,snapshot=on') |
| 63 | vm.add_args('-device', 'nvme,bus=pcie.0,' + |
| 64 | 'drive=drv0,id=nvme-disk0,serial=nvmemigtest,bootindex=1') |
| 65 | |
| 66 | vm.add_args( |
| 67 | "-kernel", |
| 68 | kernel_path, |
| 69 | "-initrd", |
| 70 | initrd_path, |
| 71 | "-append", |
| 72 | self.DEFAULT_KERNEL_PARAMS |
| 73 | ) |
| 74 | |
| 75 | def launch_source_vm(self, vm): |
| 76 | vm.launch() |
| 77 | |
| 78 | self.wait_for_console_pattern('Entering emergency mode.', vm) |
| 79 | prompt = '# ' |
| 80 | self.wait_for_console_pattern(prompt, vm) |
| 81 | |
| 82 | # Synchronize on NVMe driver creating the root device |
| 83 | exec_command_and_wait_for_pattern(self, |
| 84 | "while ! (dmesg -c | grep nvme0n1:) ; do sleep 1 ; done", |
| 85 | "nvme0n1", vm=vm) |
| 86 | self.wait_for_console_pattern(prompt, vm) |
| 87 | |
| 88 | # prepare system |
| 89 | exec_command_and_wait_for_pattern(self, 'mount /dev/nvme0n1p1 /sysroot', |
| 90 | prompt, vm=vm) |
| 91 | exec_command_and_wait_for_pattern(self, 'chroot /sysroot', |
| 92 | prompt, vm=vm) |
| 93 | exec_command_and_wait_for_pattern(self, 'mount -t proc proc /proc', |
| 94 | prompt, vm=vm) |
| 95 | exec_command_and_wait_for_pattern(self, 'mount -t sysfs sysfs /sys', |
| 96 | prompt, vm=vm) |
| 97 | |
| 98 | # Run workload before migration to check if it continues |
| 99 | # to run properly after migration. |
| 100 | # |
| 101 | # Workload is simple: it continuously calculates checksums of |
| 102 | # all files in /usr/bin to generate some I/O load on |
| 103 | # the NVMe disk and at the same time it drops caches to |
| 104 | # make sure that we have some read I/O on the disk as well. |
| 105 | # If there are any issues with the migration of the NVMe device, |
| 106 | # we should see errors in dmesg and consequently in the workload log. |
| 107 | exec_command_and_wait_for_pattern(self, |
| 108 | "(while [ ! -f /tmp/test_nvme_mig_workload.stop ]; do \ |
| 109 | rm -f /tmp/test_nvme_mig_workload.iter_finished; \ |
| 110 | echo 3 > /proc/sys/vm/drop_caches; \ |
| 111 | find /usr/bin -type f -exec cksum {} \\;; \ |
| 112 | touch /tmp/test_nvme_mig_workload.iter_finished; \ |
| 113 | done) > /dev/null 2> /tmp/test_nvme_mig_workload.errors &", |
| 114 | prompt, vm=vm) |
| 115 | exec_command_and_wait_for_pattern(self, |
| 116 | 'echo $! > /tmp/test_nvme_mig_workload.pid', |
| 117 | prompt, vm=vm) |
| 118 | |
| 119 | # check if process is alive and running |
| 120 | self.exec_command_and_check( |
| 121 | "kill -0 $(cat /tmp/test_nvme_mig_workload.pid)", vm) |
| 122 | |
| 123 | def assert_dest_vm(self, vm): |
| 124 | prompt = '# ' |
| 125 | |
| 126 | # check if process is alive and running after migration, |
| 127 | # if not - fail the test |
| 128 | self.exec_command_and_check( |
| 129 | "kill -0 $(cat /tmp/test_nvme_mig_workload.pid)", vm) |
| 130 | |
| 131 | # signal workload to stop |
| 132 | exec_command_and_wait_for_pattern(self, |
| 133 | 'touch /tmp/test_nvme_mig_workload.stop', |
| 134 | prompt, vm=vm) |
| 135 | |
| 136 | # wait workload to finish, because we want to examine log |
| 137 | # to see if there are any errors |
| 138 | exec_command_and_wait_for_pattern(self, |
| 139 | "while [ ! -f /tmp/test_nvme_mig_workload.iter_finished ]; do \ |
| 140 | sleep 1; \ |
| 141 | done;", |
| 142 | prompt, vm=vm) |
| 143 | |
| 144 | exec_command_and_wait_for_pattern(self, |
| 145 | 'cat /tmp/test_nvme_mig_workload.errors', |
| 146 | prompt, vm=vm) |
| 147 | |
| 148 | # fail the test if non-empty |
| 149 | self.exec_command_and_check( |
| 150 | "[ ! -s /tmp/test_nvme_mig_workload.errors ]", vm) |
| 151 | |
| 152 | def test_migration_with_tcp_localhost(self): |
| 153 | self.set_machine('q35') |
| 154 | self.require_accelerator("kvm") |
| 155 | |
| 156 | self.migration_with_tcp_localhost() |
| 157 | |
| 158 | def test_migration_with_unix(self): |
| 159 | self.set_machine('q35') |
| 160 | self.require_accelerator("kvm") |
| 161 | |
| 162 | self.migration_with_unix() |
| 163 | |
| 164 | def test_migration_with_exec(self): |
| 165 | self.set_machine('q35') |
| 166 | self.require_accelerator("kvm") |
| 167 | |
| 168 | self.migration_with_exec() |
| 169 | |
| 170 | |
| 171 | if __name__ == '__main__': |
| 172 | MigrationTest.main() |