]> git.ipfire.org Git - thirdparty/systemd.git/blob - test/TEST-64-UDEV-STORAGE/test.sh
bd3f8250a3fa598b7f0bbe710d9d1e4ef01f71e0
[thirdparty/systemd.git] / test / TEST-64-UDEV-STORAGE / test.sh
1 #!/usr/bin/env bash
2 # vi: ts=4 sw=4 tw=0 et:
3 #
4 # TODO:
5 # * SW raid (mdadm)
6 # * MD (mdadm) -> dm-crypt -> LVM
7 # * iSCSI -> dm-crypt -> LVM
8 set -e
9
10 TEST_DESCRIPTION="systemd-udev storage tests"
11 IMAGE_NAME="default"
12 TEST_NO_NSPAWN=1
13 # Save only journals of failing test cases by default (to conserve space)
14 TEST_SAVE_JOURNAL="${TEST_SAVE_JOURNAL:-fail}"
15 QEMU_TIMEOUT="${QEMU_TIMEOUT:-600}"
16
17 # shellcheck source=test/test-functions
18 . "${TEST_BASE_DIR:?}/test-functions"
19
20 USER_QEMU_OPTIONS="${QEMU_OPTIONS:-}"
21 USER_KERNEL_APPEND="${KERNEL_APPEND:-}"
22
23 if ! get_bool "$QEMU_KVM"; then
24 echo "This test requires KVM, skipping..."
25 exit 0
26 fi
27
28 _host_has_feature() {(
29 set -e
30
31 case "${1:?}" in
32 btrfs)
33 modprobe -nv btrfs && command -v mkfs.btrfs && command -v btrfs || return $?
34 ;;
35 iscsi)
36 # Client/initiator (Open-iSCSI)
37 command -v iscsiadm && command -v iscsid || return $?
38 # Server/target (TGT)
39 command -v tgtadm && command -v tgtd || return $?
40 ;;
41 lvm)
42 command -v lvm || return $?
43 ;;
44 multipath)
45 command -v multipath && command -v multipathd || return $?
46 ;;
47 *)
48 echo >&2 "ERROR: Unknown feature '$1'"
49 # Make this a hard error to distinguish an invalid feature from
50 # a missing feature
51 exit 1
52 esac
53 )}
54
55 test_append_files() {(
56 local feature
57 # An associative array of requested (but optional) features and their
58 # respective "handlers" from test/test-functions
59 #
60 # Note: we install cryptsetup unconditionally, hence it's not explicitly
61 # checked for here
62 local -A features=(
63 [btrfs]=install_btrfs
64 [iscsi]=install_iscsi
65 [lvm]=install_lvm
66 [multipath]=install_multipath
67 )
68
69 instmods "=block" "=md" "=nvme" "=scsi"
70 install_dmevent
71 image_install lsblk swapoff swapon wc wipefs
72
73 # Install the optional features if the host has the respective tooling
74 for feature in "${!features[@]}"; do
75 if _host_has_feature "$feature"; then
76 "${features[$feature]}"
77 fi
78 done
79
80 generate_module_dependencies
81
82 for i in {0..127}; do
83 dd if=/dev/zero of="${TESTDIR:?}/disk$i.img" bs=1M count=1
84 echo "device$i" >"${TESTDIR:?}/disk$i.img"
85 done
86 )}
87
88 _image_cleanup() {
89 mount_initdir
90 # Clean up certain "problematic" files which may be left over by failing tests
91 : >"${initdir:?}/etc/fstab"
92 : >"${initdir:?}/etc/crypttab"
93 }
94
95 test_run_one() {
96 local test_id="${1:?}"
97
98 if run_qemu "$test_id"; then
99 check_result_qemu || { echo "QEMU test failed"; return 1; }
100 fi
101
102 return 0
103 }
104
105 test_run() {
106 local test_id="${1:?}"
107 local passed=()
108 local failed=()
109 local skipped=()
110 local ec state
111
112 mount_initdir
113
114 if get_bool "${TEST_NO_QEMU:=}" || ! find_qemu_bin; then
115 dwarn "can't run QEMU, skipping"
116 return 0
117 fi
118
119 # Execute each currently defined function starting with "testcase_"
120 for testcase in "${TESTCASES[@]}"; do
121 _image_cleanup
122 echo "------ $testcase: BEGIN ------"
123 # Note for my future frustrated self: `fun && xxx` (as wel as ||, if, while,
124 # until, etc.) _DISABLES_ the `set -e` behavior in _ALL_ nested function
125 # calls made from `fun()`, i.e. the function _CONTINUES_ even when a called
126 # command returned non-zero EC. That may unexpectedly hide failing commands
127 # if not handled properly. See: bash(1) man page, `set -e` section.
128 #
129 # So, be careful when adding clean up snippets in the testcase_*() functions -
130 # if the `test_run_one()` function isn't the last command, you have propagate
131 # the exit code correctly (e.g. `test_run_one() || return $?`, see below).
132 ec=0
133 "$testcase" "$test_id" || ec=$?
134 case $ec in
135 0)
136 passed+=("$testcase")
137 state="PASS"
138 ;;
139 77)
140 skipped+=("$testcase")
141 state="SKIP"
142 ;;
143 *)
144 failed+=("$testcase")
145 state="FAIL"
146 esac
147 echo "------ $testcase: END ($state) ------"
148 done
149
150 echo "Passed tests: ${#passed[@]}"
151 printf " * %s\n" "${passed[@]}"
152 echo "Skipped tests: ${#skipped[@]}"
153 printf " * %s\n" "${skipped[@]}"
154 echo "Failed tests: ${#failed[@]}"
155 printf " * %s\n" "${failed[@]}"
156
157 [[ ${#failed[@]} -eq 0 ]] || return 1
158
159 return 0
160 }
161
162 testcase_megasas2_basic() {
163 if ! "${QEMU_BIN:?}" -device help | grep 'name "megasas-gen2"'; then
164 echo "megasas-gen2 device driver is not available, skipping test..."
165 return 77
166 fi
167
168 local qemu_opts=(
169 "-device megasas-gen2,id=scsi0"
170 "-device megasas-gen2,id=scsi1"
171 "-device megasas-gen2,id=scsi2"
172 "-device megasas-gen2,id=scsi3"
173 )
174
175 for i in {0..127}; do
176 # Add 128 drives, 32 per bus
177 qemu_opts+=(
178 "-device scsi-hd,drive=drive$i,bus=scsi$((i / 32)).0,channel=0,scsi-id=$((i % 32)),lun=0"
179 "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=drive$i"
180 )
181 done
182
183 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
184 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
185 test_run_one "${1:?}"
186 }
187
188 testcase_nvme_basic() {
189 if ! "${QEMU_BIN:?}" -device help | grep 'name "nvme"'; then
190 echo "nvme device driver is not available, skipping test..."
191 return 77
192 fi
193
194 for i in {0..27}; do
195 qemu_opts+=(
196 "-device nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
197 "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
198 )
199 done
200
201 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
202 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
203 test_run_one "${1:?}"
204 }
205
206 # Test for issue https://github.com/systemd/systemd/issues/20212
207 testcase_virtio_scsi_identically_named_partitions() {
208 if ! "${QEMU_BIN:?}" -device help | grep 'name "virtio-scsi-pci"'; then
209 echo "virtio-scsi-pci device driver is not available, skipping test..."
210 return 77
211 fi
212
213 # Create 16 disks, with 8 partitions per disk (all identically named)
214 # and attach them to a virtio-scsi controller
215 local qemu_opts=("-device virtio-scsi-pci,id=scsi0,num_queues=4")
216 local diskpath="${TESTDIR:?}/namedpart0.img"
217 local lodev
218
219 dd if=/dev/zero of="$diskpath" bs=1M count=18
220 lodev="$(losetup --show -f -P "$diskpath")"
221 sfdisk "${lodev:?}" <<EOF
222 label: gpt
223
224 name="Hello world", size=2M
225 name="Hello world", size=2M
226 name="Hello world", size=2M
227 name="Hello world", size=2M
228 name="Hello world", size=2M
229 name="Hello world", size=2M
230 name="Hello world", size=2M
231 name="Hello world", size=2M
232 EOF
233 losetup -d "$lodev"
234
235 for i in {0..15}; do
236 diskpath="${TESTDIR:?}/namedpart$i.img"
237 if [[ $i -gt 0 ]]; then
238 cp -uv "${TESTDIR:?}/namedpart0.img" "$diskpath"
239 fi
240
241 qemu_opts+=(
242 "-device scsi-hd,drive=drive$i,bus=scsi0.0,channel=0,scsi-id=0,lun=$i"
243 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
244 )
245 done
246
247 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
248 # Limit the number of VCPUs and set a timeout to make sure we trigger the issue
249 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
250 QEMU_SMP=1 QEMU_TIMEOUT=60 test_run_one "${1:?}" || return $?
251
252 rm -f "${TESTDIR:?}"/namedpart*.img
253 }
254
255 testcase_multipath_basic_failover() {
256 if ! _host_has_feature "multipath"; then
257 echo "Missing multipath tools, skipping the test..."
258 return 77
259 fi
260
261 local qemu_opts=("-device virtio-scsi-pci,id=scsi")
262 local partdisk="${TESTDIR:?}/multipathpartitioned.img"
263 local image lodev nback ndisk wwn
264
265 dd if=/dev/zero of="$partdisk" bs=1M count=16
266 lodev="$(losetup --show -f -P "$partdisk")"
267 sfdisk "${lodev:?}" <<EOF
268 label: gpt
269
270 name="first_partition", size=5M
271 uuid="deadbeef-dead-dead-beef-000000000000", name="failover_part", size=5M
272 EOF
273 udevadm settle
274 mkfs.ext4 -U "deadbeef-dead-dead-beef-111111111111" -L "failover_vol" "${lodev}p2"
275 losetup -d "$lodev"
276
277 # Add 64 multipath devices, each backed by 4 paths
278 for ndisk in {0..63}; do
279 wwn="0xDEADDEADBEEF$(printf "%.4d" "$ndisk")"
280 # Use a partitioned disk for the first device to test failover
281 [[ $ndisk -eq 0 ]] && image="$partdisk" || image="${TESTDIR:?}/disk$ndisk.img"
282
283 for nback in {0..3}; do
284 qemu_opts+=(
285 "-device scsi-hd,drive=drive${ndisk}x${nback},serial=MPIO$ndisk,wwn=$wwn"
286 "-drive format=raw,cache=unsafe,file=$image,file.locking=off,if=none,id=drive${ndisk}x${nback}"
287 )
288 done
289 done
290
291 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
292 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
293 test_run_one "${1:?}" || return $?
294
295 rm -f "$partdisk"
296 }
297
298 # Test case for issue https://github.com/systemd/systemd/issues/19946
299 testcase_simultaneous_events() {
300 local qemu_opts=("-device virtio-scsi-pci,id=scsi")
301 local partdisk="${TESTDIR:?}/simultaneousevents.img"
302
303 dd if=/dev/zero of="$partdisk" bs=1M count=110
304 qemu_opts+=(
305 "-device scsi-hd,drive=drive1,serial=deadbeeftest"
306 "-drive format=raw,cache=unsafe,file=$partdisk,if=none,id=drive1"
307 )
308
309 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
310 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
311 test_run_one "${1:?}" || return $?
312
313 rm -f "$partdisk"
314 }
315
316 testcase_lvm_basic() {
317 if ! _host_has_feature "lvm"; then
318 echo "Missing lvm tools, skipping the test..."
319 return 77
320 fi
321
322 local qemu_opts=("-device ahci,id=ahci0")
323 local diskpath
324
325 # Attach 4 SATA disks to the VM (and set their model and serial fields
326 # to something predictable, so we can refer to them later)
327 for i in {0..3}; do
328 diskpath="${TESTDIR:?}/lvmbasic${i}.img"
329 dd if=/dev/zero of="$diskpath" bs=1M count=32
330 qemu_opts+=(
331 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeeflvm$i"
332 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
333 )
334 done
335
336 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
337 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
338 test_run_one "${1:?}" || return $?
339
340 rm -f "${TESTDIR:?}"/lvmbasic*.img
341 }
342
343 testcase_btrfs_basic() {
344 if ! _host_has_feature "btrfs"; then
345 echo "Missing btrfs tools/modules, skipping the test..."
346 return 77
347 fi
348
349 local qemu_opts=("-device ahci,id=ahci0")
350 local diskpath i size
351
352 for i in {0..3}; do
353 diskpath="${TESTDIR:?}/btrfsbasic${i}.img"
354 # Make the first disk larger for multi-partition tests
355 [[ $i -eq 0 ]] && size=350 || size=128
356
357 dd if=/dev/zero of="$diskpath" bs=1M count="$size"
358 qemu_opts+=(
359 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeefbtrfs$i"
360 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
361 )
362 done
363
364 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
365 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
366 test_run_one "${1:?}" || return $?
367
368 rm -f "${TESTDIR:?}"/btrfsbasic*.img
369 }
370
371 testcase_iscsi_lvm() {
372 if ! _host_has_feature "iscsi" || ! _host_has_feature "lvm"; then
373 echo "Missing iSCSI client/server tools (Open-iSCSI/TGT) or LVM utilities, skipping the test..."
374 return 77
375 fi
376
377 local qemu_opts=("-device ahci,id=ahci0")
378 local diskpath i size
379
380 for i in {0..3}; do
381 diskpath="${TESTDIR:?}/iscsibasic${i}.img"
382 # Make the first disk larger for multi-partition tests
383 [[ $i -eq 0 ]] && size=150 || size=64
384 # Make the first disk larger for multi-partition tests
385
386 dd if=/dev/zero of="$diskpath" bs=1M count="$size"
387 qemu_opts+=(
388 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeefiscsi$i"
389 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
390 )
391 done
392
393 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
394 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
395 test_run_one "${1:?}" || return $?
396
397 rm -f "${TESTDIR:?}"/iscsibasic*.img
398 }
399
400 testcase_long_sysfs_path() {
401 local brid
402 local testdisk="${TESTDIR:?}/longsysfspath.img"
403 local qemu_opts=(
404 "-drive if=none,id=drive0,format=raw,cache=unsafe,file=$testdisk"
405 "-device pci-bridge,id=pci_bridge0,bus=pci.0,chassis_nr=64"
406 )
407
408 dd if=/dev/zero of="$testdisk" bs=1M count=64
409 lodev="$(losetup --show -f -P "$testdisk")"
410 sfdisk "${lodev:?}" <<EOF
411 label: gpt
412
413 name="test_swap", size=32M
414 uuid="deadbeef-dead-dead-beef-000000000000", name="test_part", size=5M
415 EOF
416 udevadm settle
417 mkswap -U "deadbeef-dead-dead-beef-111111111111" -L "swap_vol" "${lodev}p1"
418 mkfs.ext4 -U "deadbeef-dead-dead-beef-222222222222" -L "data_vol" "${lodev}p2"
419 losetup -d "$lodev"
420
421 # Create 25 additional PCI bridges, each one connected to the previous one
422 # (basically a really long extension cable), and attach a virtio drive to
423 # the last one. This should force udev into attempting to create a device
424 # unit with a _really_ long name.
425 for brid in {1..25}; do
426 qemu_opts+=("-device pci-bridge,id=pci_bridge$brid,bus=pci_bridge$((brid-1)),chassis_nr=$((64+brid))")
427 done
428
429 qemu_opts+=("-device virtio-blk-pci,drive=drive0,scsi=off,bus=pci_bridge$brid")
430
431 KERNEL_APPEND="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
432 QEMU_OPTIONS="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
433 test_run_one "${1:?}" || return $?
434
435 rm -f "${testdisk:?}"
436 }
437
438 # Allow overriding which tests should be run from the "outside", useful for manual
439 # testing (make -C test/... TESTCASES="testcase1 testcase2")
440 if [[ -v "TESTCASES" && -n "$TESTCASES" ]]; then
441 read -ra TESTCASES <<< "$TESTCASES"
442 else
443 # This must run after all functions were defined, otherwise `declare -F` won't
444 # see them
445 mapfile -t TESTCASES < <(declare -F | awk '$3 ~ /^testcase_/ {print $3;}')
446 fi
447
448 do_test "$@"