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