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()