diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
commit | fc53809803cd2bc2434e312b19a18fa36776da12 (patch) | |
tree | b4b43bd6538f51965ce32856e9c053d0f90919c8 /test/units/TEST-64-UDEV-STORAGE.sh | |
parent | Adding upstream version 255.5. (diff) | |
download | systemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip |
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/units/TEST-64-UDEV-STORAGE.sh')
-rwxr-xr-x | test/units/TEST-64-UDEV-STORAGE.sh | 1262 |
1 files changed, 1262 insertions, 0 deletions
diff --git a/test/units/TEST-64-UDEV-STORAGE.sh b/test/units/TEST-64-UDEV-STORAGE.sh new file mode 100755 index 0000000..5ddddf5 --- /dev/null +++ b/test/units/TEST-64-UDEV-STORAGE.sh @@ -0,0 +1,1262 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# vi: ts=4 sw=4 tw=0 et: + +set -eux +set -o pipefail + +# Check if all symlinks under /dev/disk/ are valid +# shellcheck disable=SC2120 +helper_check_device_symlinks() {( + set +x + + local dev link path paths target + + [[ $# -gt 0 ]] && paths=("$@") || paths=("/dev/disk" "/dev/mapper") + + # Check if all given paths are valid + for path in "${paths[@]}"; do + if ! test -e "$path"; then + echo >&2 "Path '$path' doesn't exist" + return 1 + fi + done + + while read -r link; do + target="$(readlink -f "$link")" + # Both checks should do virtually the same thing, but check both to be + # on the safe side + if [[ ! -e "$link" || ! -e "$target" ]]; then + echo >&2 "ERROR: symlink '$link' points to '$target' which doesn't exist" + return 1 + fi + + # Check if the symlink points to the correct device in /dev + dev="/dev/$(udevadm info -q name "$link")" + if [[ "$target" != "$dev" ]]; then + echo >&2 "ERROR: symlink '$link' points to '$target' but '$dev' was expected" + return 1 + fi + done < <(find "${paths[@]}" -type l) +)} + +helper_check_udev_watch() {( + set +x + + local link target id dev + + while read -r link; do + target="$(readlink "$link")" + if [[ ! -L "/run/udev/watch/$target" ]]; then + echo >&2 "ERROR: symlink /run/udev/watch/$target does not exist" + return 1 + fi + if [[ "$(readlink "/run/udev/watch/$target")" != "$(basename "$link")" ]]; then + echo >&2 "ERROR: symlink target of /run/udev/watch/$target is inconsistent with $link" + return 1 + fi + + if [[ "$target" =~ ^[0-9]+$ ]]; then + # $link is ID -> wd + id="$(basename "$link")" + else + # $link is wd -> ID + id="$target" + fi + + if [[ "${id:0:1}" == "b" ]]; then + dev="/dev/block/${id:1}" + elif [[ "${id:0:1}" == "c" ]]; then + dev="/dev/char/${id:1}" + else + echo >&2 "ERROR: unexpected device ID '$id'" + return 1 + fi + + if [[ ! -e "$dev" ]]; then + echo >&2 "ERROR: device '$dev' corresponding to symlink '$link' does not exist" + return 1 + fi + done < <(find /run/udev/watch -type l) +)} + +check_device_unit() {( + set +x + + local log_level link links path syspath unit + + log_level="${1?}" + path="${2?}" + unit=$(systemd-escape --path --suffix=device "$path") + + [[ "$log_level" == 1 ]] && echo "INFO: check_device_unit($unit)" + + syspath=$(systemctl show --value --property SysFSPath "$unit" 2>/dev/null) + if [[ -z "$syspath" ]]; then + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit not found." + return 1 + fi + + if [[ ! -L "$path" ]]; then + if [[ ! -d "$syspath" ]]; then + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit exists for $syspath but it does not exist." + return 1 + fi + return 0 + fi + + if [[ ! -b "$path" && ! -c "$path" ]]; then + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: invalid file type $path" + return 1 + fi + + read -r -a links < <(udevadm info -q symlink "$syspath" 2>/dev/null) + for link in "${links[@]}"; do + if [[ "/dev/$link" == "$path" ]]; then # DEVLINKS= given by -q symlink are relative to /dev + return 0 + fi + done + + read -r -a links < <(udevadm info "$syspath" | sed -ne '/SYSTEMD_ALIAS=/ { s/^E: SYSTEMD_ALIAS=//; p }' 2>/dev/null) + for link in "${links[@]}"; do + if [[ "$link" == "$path" ]]; then # SYSTEMD_ALIAS= are absolute + return 0 + fi + done + + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit exists for $syspath but it does not have the corresponding DEVLINKS or SYSTEMD_ALIAS." + return 1 +)} + +check_device_units() {( + set +x + + local log_level path paths + + log_level="${1?}" + shift + paths=("$@") + + for path in "${paths[@]}"; do + if ! check_device_unit "$log_level" "$path"; then + return 1 + fi + done + + while read -r unit _; do + path=$(systemd-escape --path --unescape "$unit") + if ! check_device_unit "$log_level" "$path"; then + return 1 + fi + done < <(systemctl list-units --all --type=device --no-legend dev-* | awk '$1 !~ /dev-tty.+/ && $4 == "plugged" { print $1 }' | sed -e 's/\.device$//') + + return 0 +)} + +helper_check_device_units() {( + set +x + + local i + + for i in {1..20}; do + (( i > 1 )) && sleep 0.5 + if check_device_units 0 "$@"; then + return 0 + fi + done + + check_device_units 1 "$@" +)} + +testcase_virtio_scsi_basic() { + lsblk -S + [[ "$(lsblk --scsi --noheadings | wc -l)" -ge 128 ]] +} + +testcase_nvme_basic() { + local expected_symlinks=() + local i + + for (( i = 0; i < 5; i++ )); do + expected_symlinks+=( + # both replace mode provides the same devlink + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1 + ) + done + for (( i = 5; i < 10; i++ )); do + expected_symlinks+=( + # old replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i" + # newer replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1 + ) + done + for (( i = 10; i < 15; i++ )); do + expected_symlinks+=( + # old replace mode does not provide devlink, as serial contains "/" + # newer replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1 + ) + done + for (( i = 15; i < 20; i++ )); do + expected_symlinks+=( + # old replace mode does not provide devlink, as serial contains "/" + # newer replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"_1 + ) + done + + udevadm settle + ls /dev/disk/by-id + for i in "${expected_symlinks[@]}"; do + udevadm wait --settle --timeout=30 "$i" + done + + lsblk --noheadings | grep "^nvme" + [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]] +} + +testcase_nvme_subsystem() { + local expected_symlinks=( + # Controller(s) + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_16 + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_17 + # Shared namespaces + /dev/disk/by-path/pci-*-nvme-16 + /dev/disk/by-path/pci-*-nvme-17 + ) + + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" +} + +testcase_virtio_scsi_identically_named_partitions() { + local num_part num_disk i j + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + num_part=4 + num_disk=4 + else + num_part=8 + num_disk=16 + fi + + for ((i = 0; i < num_disk; i++)); do + udevadm lock --device "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive$i" \ + sfdisk "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive$i" <<EOF +label: gpt + +$(for ((j = 1; j <= num_part; j++)); do echo 'name="Hello world", size=2M'; done) +EOF + done + + udevadm settle + lsblk --noheadings -a -o NAME,PARTLABEL + [[ "$(lsblk --noheadings -a -o NAME,PARTLABEL | grep -c "Hello world")" -eq "$((num_part * num_disk))" ]] +} + +testcase_multipath_basic_failover() { + local dmpath i path wwid + + # Configure multipath + cat >/etc/multipath.conf <<\EOF +defaults { + # Use /dev/mapper/$WWN paths instead of /dev/mapper/mpathX + user_friendly_names no + find_multipaths yes + enable_foreign "^$" +} + +blacklist_exceptions { + property "(SCSI_IDENT_|ID_WWN)" +} + +blacklist { +} +EOF + + udevadm lock --device /dev/disk/by-id/wwn-0xdeaddeadbeef0000 \ + sfdisk /dev/disk/by-id/wwn-0xdeaddeadbeef0000 <<EOF +label: gpt + +name="first_partition", size=5M +uuid="deadbeef-dead-dead-beef-000000000000", name="failover_part", size=5M +EOF + udevadm settle + udevadm lock --device /dev/disk/by-id/wwn-0xdeaddeadbeef0000-part2 \ + mkfs.ext4 -U "deadbeef-dead-dead-beef-111111111111" -L "failover_vol" /dev/disk/by-id/wwn-0xdeaddeadbeef0000-part2 + + modprobe -v dm_multipath + systemctl start multipathd.service + systemctl status multipathd.service + multipath -ll + udevadm settle + ls -l /dev/disk/by-id/ + + for i in {0..15}; do + wwid="deaddeadbeef$(printf "%.4d" "$i")" + path="/dev/disk/by-id/wwn-0x$wwid" + dmpath="$(readlink -f "$path")" + + lsblk "$path" + multipath -C "$dmpath" + # We should have 4 active paths for each multipath device + [[ "$(multipath -l "$path" | grep -c running)" -eq 4 ]] + done + + # Test failover (with the first multipath device that has a partitioned disk) + echo "${FUNCNAME[0]}: test failover" + local device expected link mpoint part + local -a devices + mkdir -p /mnt + mpoint="$(mktemp -d /mnt/mpathXXX)" + wwid="deaddeadbeef0000" + path="/dev/disk/by-id/wwn-0x$wwid" + + # All following symlinks should exists and should be valid + local -a part_links=( + "/dev/disk/by-id/wwn-0x$wwid-part2" + "/dev/disk/by-partlabel/failover_part" + "/dev/disk/by-partuuid/deadbeef-dead-dead-beef-000000000000" + "/dev/disk/by-label/failover_vol" + "/dev/disk/by-uuid/deadbeef-dead-dead-beef-111111111111" + ) + udevadm wait --settle --timeout=30 "${part_links[@]}" + helper_check_device_units "${part_links[@]}" + + # Choose a random symlink to the failover data partition each time, for + # a better coverage + part="${part_links[$RANDOM % ${#part_links[@]}]}" + + # Get all devices attached to a specific multipath device (in H:C:T:L format) + # and sort them in a random order, so we cut off different paths each time + mapfile -t devices < <(multipath -l "$path" | grep -Eo '[0-9]+:[0-9]+:[0-9]+:[0-9]+' | sort -R) + if [[ "${#devices[@]}" -ne 4 ]]; then + echo "Expected 4 devices attached to WWID=$wwid, got ${#devices[@]} instead" + return 1 + fi + # Drop the last path from the array, since we want to leave at least one path active + unset "devices[3]" + # Mount the first multipath partition, write some data we can check later, + # and then disconnect the remaining paths one by one while checking if we + # can still read/write from the mount + mount -t ext4 "$part" "$mpoint" + expected=0 + echo -n "$expected" >"$mpoint/test" + # Sanity check we actually wrote what we wanted + [[ "$(<"$mpoint/test")" == "$expected" ]] + + for device in "${devices[@]}"; do + echo offline >"/sys/class/scsi_device/$device/device/state" + [[ "$(<"$mpoint/test")" == "$expected" ]] + expected="$((expected + 1))" + echo -n "$expected" >"$mpoint/test" + + # Make sure all symlinks are still valid + udevadm wait --settle --timeout=30 "${part_links[@]}" + helper_check_device_units "${part_links[@]}" + done + + multipath -l "$path" + # Three paths should be now marked as 'offline' and one as 'running' + [[ "$(multipath -l "$path" | grep -c offline)" -eq 3 ]] + [[ "$(multipath -l "$path" | grep -c running)" -eq 1 ]] + + umount "$mpoint" + rm -fr "$mpoint" +} + +testcase_simultaneous_events_1() { + local disk expected i iterations key link num_part part partscript rule target timeout + local -a devices symlinks + local -A running + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + num_part=2 + iterations=10 + timeout=240 + else + num_part=10 + iterations=100 + timeout=60 + fi + + for disk in {0..9}; do + link="/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_deadbeeftest${disk}" + target="$(readlink -f "$link")" + if [[ ! -b "$target" ]]; then + echo "ERROR: failed to find the test SCSI block device $link" + return 1 + fi + + devices+=("$target") + done + + for ((part = 1; part <= num_part; part++)); do + symlinks+=( + "/dev/disk/by-partlabel/test${part}" + ) + done + + partscript="$(mktemp)" + + cat >"$partscript" <<EOF +$(for ((part = 1; part <= num_part; part++)); do printf 'name="test%d", size=2M\n' "$part"; done) +EOF + + rule=/run/udev/rules.d/50-test.rules + mkdir -p "${rule%/*}" + cat >"$rule" <<EOF +SUBSYSTEM=="block", KERNEL=="${devices[4]##*/}*|${devices[5]##*/}*", OPTIONS="link_priority=10" +EOF + + udevadm control --reload + + # initialize partition table + for disk in {0..9}; do + echo 'label: gpt' | udevadm lock --device="${devices[$disk]}" sfdisk -q "${devices[$disk]}" + done + + # Delete the partitions, immediately recreate them, wait for udev to settle + # down, and then check if we have any dangling symlinks in /dev/disk/. Rinse + # and repeat. + # + # On unpatched udev versions the delete-recreate cycle may trigger a race + # leading to dead symlinks in /dev/disk/ + for ((i = 1; i <= iterations; i++)); do + for disk in {0..9}; do + if ((disk % 2 == i % 2)); then + udevadm lock --device="${devices[$disk]}" sfdisk -q --delete "${devices[$disk]}" & + else + udevadm lock --device="${devices[$disk]}" sfdisk -q -X gpt "${devices[$disk]}" <"$partscript" & + fi + running[$disk]=$! + done + + for key in "${!running[@]}"; do + wait "${running[$key]}" + unset "running[$key]" + done + + if ((i % 10 <= 1)); then + udevadm wait --settle --timeout="$timeout" "${devices[@]}" "${symlinks[@]}" + helper_check_device_symlinks + helper_check_udev_watch + for ((part = 1; part <= num_part; part++)); do + link="/dev/disk/by-partlabel/test${part}" + target="$(readlink -f "$link")" + if ((i % 2 == 0)); then + expected="${devices[5]}$part" + else + expected="${devices[4]}$part" + fi + if [[ "$target" != "$expected" ]]; then + echo >&2 "ERROR: symlink '/dev/disk/by-partlabel/test${part}' points to '$target' but '$expected' was expected" + return 1 + fi + done + fi + done + + helper_check_device_units + rm -f "$rule" "$partscript" + + udevadm control --reload +} + +testcase_simultaneous_events_2() { + local disk expected i iterations key link num_part part script_dir target timeout + local -a devices symlinks + local -A running + + script_dir="$(mktemp --directory "/tmp/test-udev-storage.script.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$script_dir'" RETURN + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + num_part=20 + iterations=1 + timeout=2400 + else + num_part=100 + iterations=3 + timeout=300 + fi + + for disk in {0..9}; do + link="/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_deadbeeftest${disk}" + target="$(readlink -f "$link")" + if [[ ! -b "$target" ]]; then + echo "ERROR: failed to find the test SCSI block device $link" + return 1 + fi + + devices+=("$target") + done + + for ((i = 1; i <= iterations; i++)); do + cat >"$script_dir/partscript-$i" <<EOF +$(for ((part = 1; part <= num_part; part++)); do printf 'name="testlabel-%d", size=1M\n' "$i"; done) +EOF + done + + echo "## $iterations iterations start: $(date '+%H:%M:%S.%N')" + for ((i = 1; i <= iterations; i++)); do + + for disk in {0..9}; do + udevadm lock --device="${devices[$disk]}" sfdisk -q --delete "${devices[$disk]}" & + running[$disk]=$! + done + + for key in "${!running[@]}"; do + wait "${running[$key]}" + unset "running[$key]" + done + + for disk in {0..9}; do + udevadm lock --device="${devices[$disk]}" sfdisk -q -X gpt "${devices[$disk]}" <"$script_dir/partscript-$i" & + running[$disk]=$! + done + + for key in "${!running[@]}"; do + wait "${running[$key]}" + unset "running[$key]" + done + + udevadm wait --settle --timeout="$timeout" "${devices[@]}" "/dev/disk/by-partlabel/testlabel-$i" + done + echo "## $iterations iterations end: $(date '+%H:%M:%S.%N')" +} + +testcase_simultaneous_events() { + testcase_simultaneous_events_1 + testcase_simultaneous_events_2 +} + +testcase_lvm_basic() { + local i iterations partitions part timeout + local vgroup="MyTestGroup$RANDOM" + local devices=( + /dev/disk/by-id/scsi-0systemd_foobar_deadbeeflvm{0..3} + ) + + . /etc/os-release + if [[ "$ID" == "ubuntu" ]]; then + echo "LVM on Ubuntu is broken, skipping the test" | tee --append /skipped + exit 77 + fi + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + timeout=180 + else + timeout=30 + fi + # Make sure all the necessary soon-to-be-LVM devices exist + ls -l "${devices[@]}" + + # Add all test devices into a volume group, create two logical volumes, + # and check if necessary symlinks exist (and are valid) + lvm pvcreate -y "${devices[@]}" + lvm pvs + lvm vgcreate "$vgroup" -y "${devices[@]}" + lvm vgs + lvm vgchange -ay "$vgroup" + lvm lvcreate -y -L 4M "$vgroup" -n mypart1 + lvm lvcreate -y -L 32M "$vgroup" -n mypart2 + lvm lvs + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" + mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1" + udevadm trigger --settle "/dev/$vgroup/mypart1" + udevadm wait --settle --timeout="$timeout" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Mount mypart1 through by-label devlink + mkdir -p /tmp/mypart1-mount-point + mount /dev/disk/by-label/mylvpart1 /tmp/mypart1-mount-point + timeout 30 bash -c "until systemctl -q is-active /tmp/mypart1-mount-point; do sleep .2; done" + # Extend the partition and check if the device and mount units are still active. + # See https://bugzilla.redhat.com/show_bug.cgi?id=2158628 + # Note, the test below may be unstable with LVM2 without the following patch: + # https://github.com/lvmteam/lvm2/pull/105 + # But, to reproduce the issue, udevd must start to process the first 'change' uevent + # earlier than extending the volume has been finished, and in most case, the extension + # is hopefully fast. + lvm lvextend -y --size 8M "/dev/$vgroup/mypart1" + udevadm wait --settle --timeout="$timeout" "/dev/disk/by-label/mylvpart1" + timeout 30 bash -c "until systemctl -q is-active '/dev/$vgroup/mypart1'; do sleep .2; done" + timeout 30 bash -c "until systemctl -q is-active /tmp/mypart1-mount-point; do sleep .2; done" + # Umount the partition, otherwise the underlying device unit will stay in + # the inactive state and not be collected, and helper_check_device_units() will fail. + systemctl show /tmp/mypart1-mount-point + umount /tmp/mypart1-mount-point + + # Rename partitions (see issue #24518) + lvm lvrename "/dev/$vgroup/mypart1" renamed1 + lvm lvrename "/dev/$vgroup/mypart2" renamed2 + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/renamed1" "/dev/$vgroup/renamed2" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Rename them back + lvm lvrename "/dev/$vgroup/renamed1" mypart1 + lvm lvrename "/dev/$vgroup/renamed2" mypart2 + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/renamed1" "/dev/$vgroup/renamed2" + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Do not "unready" suspended encrypted devices w/o superblock info + # See: + # - https://github.com/systemd/systemd/pull/24177 + # - https://bugzilla.redhat.com/show_bug.cgi?id=1985288 + dd if=/dev/urandom of=/etc/lvm_keyfile bs=64 count=1 iflag=fullblock + chmod 0600 /etc/lvm_keyfile + # Intentionally use weaker cipher-related settings, since we don't care + # about security here as it's a throwaway LUKS partition + cryptsetup luksFormat -q --use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \ + "/dev/$vgroup/mypart2" /etc/lvm_keyfile + # Mount the LUKS partition & create a filesystem on it + mkdir -p /tmp/lvmluksmnt + cryptsetup open --key-file=/etc/lvm_keyfile "/dev/$vgroup/mypart2" "lvmluksmap" + udevadm wait --settle --timeout="$timeout" "/dev/mapper/lvmluksmap" + mkfs.ext4 -L lvmluksfs "/dev/mapper/lvmluksmap" + udevadm trigger --settle "/dev/mapper/lvmluksmap" + udevadm wait --settle --timeout="$timeout" "/dev/disk/by-label/lvmluksfs" + # Make systemd "interested" in the mount by adding it to /etc/fstab + echo "/dev/disk/by-label/lvmluksfs /tmp/lvmluksmnt ext4 defaults 0 2" >>/etc/fstab + systemctl daemon-reload + mount "/tmp/lvmluksmnt" + mountpoint "/tmp/lvmluksmnt" + # Temporarily suspend the LUKS device and trigger udev - basically what `cryptsetup resize` + # does but in a more deterministic way suitable for a test/reproducer + for _ in {0..5}; do + dmsetup suspend "/dev/mapper/lvmluksmap" + udevadm trigger -v --settle "/dev/mapper/lvmluksmap" + dmsetup resume "/dev/mapper/lvmluksmap" + # The mount should survive this sequence of events + mountpoint "/tmp/lvmluksmnt" + done + # Cleanup + umount "/tmp/lvmluksmnt" + cryptsetup close "/dev/mapper/lvmluksmap" + sed -i "/lvmluksfs/d" "/etc/fstab" + systemctl daemon-reload + + # Disable the VG and check symlinks... + lvm vgchange -an "$vgroup" + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" + helper_check_device_units + + # reenable the VG and check the symlinks again if all LVs are properly activated + lvm vgchange -ay "$vgroup" + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Same as above, but now with more "stress" + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + iterations=10 + else + iterations=50 + fi + + for ((i = 1; i <= iterations; i++)); do + lvm vgchange -an "$vgroup" + lvm vgchange -ay "$vgroup" + + if ((i % 5 == 0)); then + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + fi + done + + # Remove the first LV + lvm lvremove -y "$vgroup/mypart1" + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/mypart1" + udevadm wait --timeout=0 "/dev/$vgroup/mypart2" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Create & remove LVs in a loop, i.e. with more "stress" + if [[ -v ASAN_OPTIONS ]]; then + iterations=8 + partitions=16 + elif [[ "$(systemd-detect-virt -v)" == "qemu" ]]; then + iterations=8 + partitions=8 + else + iterations=16 + partitions=16 + fi + + for ((i = 1; i <= iterations; i++)); do + # 1) Create some logical volumes + for ((part = 0; part < partitions; part++)); do + lvm lvcreate -y -L 4M "$vgroup" -n "looppart$part" + done + + # 2) Immediately remove them + lvm lvremove -y $(seq -f "$vgroup/looppart%g" 0 "$((partitions - 1))") + + # 3) On every 4th iteration settle udev and check if all partitions are + # indeed gone, and if all symlinks are still valid + if ((i % 4 == 0)); then + for ((part = 0; part < partitions; part++)); do + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/looppart$part" + done + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + fi + done +} + +testcase_btrfs_basic() { + local dev_stub i label mpoint uuid + local devices=( + /dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs{0..3} + ) + + if ! modinfo btrfs; then + echo "This test requires the btrfs kernel module but it is not installed, skipping the test" | tee --append /skipped + exit 77 + fi + + ls -l "${devices[@]}" + + echo "Single device: default settings" + uuid="deadbeef-dead-dead-beef-000000000000" + label="btrfs_root" + udevadm lock --device="${devices[0]}" mkfs.btrfs -f -L "$label" -U "$uuid" "${devices[0]}" + udevadm wait --settle --timeout=30 "${devices[0]}" "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + + echo "Multiple devices: using partitions, data: single, metadata: raid1" + uuid="deadbeef-dead-dead-beef-000000000001" + label="btrfs_mpart" + udevadm lock --device="${devices[0]}" sfdisk --wipe=always "${devices[0]}" <<EOF +label: gpt + +name="diskpart1", size=85M +name="diskpart2", size=85M +name="diskpart3", size=85M +name="diskpart4", size=85M +EOF + udevadm wait --settle --timeout=30 /dev/disk/by-partlabel/diskpart{1..4} + udevadm lock --device="${devices[0]}" mkfs.btrfs -f -d single -m raid1 -L "$label" -U "$uuid" /dev/disk/by-partlabel/diskpart{1..4} + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + wipefs -a -f "${devices[0]}" + udevadm wait --settle --timeout=30 --removed /dev/disk/by-partlabel/diskpart{1..4} + + echo "Multiple devices: using disks, data: raid10, metadata: raid10, mixed mode" + uuid="deadbeef-dead-dead-beef-000000000002" + label="btrfs_mdisk" + udevadm lock \ + --device=/dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs0 \ + --device=/dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs1 \ + --device=/dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs2 \ + --device=/dev/disk/by-id/scsi-0systemd_foobar_deadbeefbtrfs3 \ + mkfs.btrfs -f -M -d raid10 -m raid10 -L "$label" -U "$uuid" "${devices[@]}" + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + + echo "Multiple devices: using LUKS encrypted disks, data: raid1, metadata: raid1, mixed mode" + uuid="deadbeef-dead-dead-beef-000000000003" + label="btrfs_mencdisk" + mpoint="/btrfs_enc$RANDOM" + mkdir "$mpoint" + # Create a key-file + dd if=/dev/urandom of=/etc/btrfs_keyfile bs=64 count=1 iflag=fullblock + chmod 0600 /etc/btrfs_keyfile + # Encrypt each device and add it to /etc/crypttab, so it can be mounted + # automagically later + : >/etc/crypttab + for ((i = 0; i < ${#devices[@]}; i++)); do + # Intentionally use weaker cipher-related settings, since we don't care + # about security here as it's a throwaway LUKS partition + udevadm lock --device="${devices[$i]}" \ + cryptsetup luksFormat -q \ + --use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \ + --uuid "deadbeef-dead-dead-beef-11111111111$i" --label "encdisk$i" "${devices[$i]}" /etc/btrfs_keyfile + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/deadbeef-dead-dead-beef-11111111111$i" "/dev/disk/by-label/encdisk$i" + # Add the device into /etc/crypttab, reload systemd, and then activate + # the device so we can create a filesystem on it later + echo "encbtrfs$i UUID=deadbeef-dead-dead-beef-11111111111$i /etc/btrfs_keyfile luks" >>/etc/crypttab + systemctl daemon-reload + systemctl start "systemd-cryptsetup@encbtrfs$i" + done + helper_check_device_symlinks + helper_check_device_units + # Check if we have all necessary DM devices + ls -l /dev/mapper/encbtrfs{0..3} + # Create a multi-device btrfs filesystem on the LUKS devices + udevadm lock \ + --device=/dev/mapper/encbtrfs0 \ + --device=/dev/mapper/encbtrfs1 \ + --device=/dev/mapper/encbtrfs2 \ + --device=/dev/mapper/encbtrfs3 \ + mkfs.btrfs -f -M -d raid1 -m raid1 -L "$label" -U "$uuid" /dev/mapper/encbtrfs{0..3} + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + # Mount it and write some data to it we can compare later + mount -t btrfs /dev/mapper/encbtrfs0 "$mpoint" + echo "hello there" >"$mpoint/test" + # "Deconstruct" the btrfs device and check if we're in a sane state (symlink-wise) + umount "$mpoint" + systemctl stop systemd-cryptsetup@encbtrfs{0..3} + udevadm wait --settle --timeout=30 --removed "/dev/disk/by-uuid/$uuid" + helper_check_device_symlinks + helper_check_device_units + # Add the mount point to /etc/fstab and check if the device can be put together + # automagically. The source device is the DM name of the first LUKS device + # (from /etc/crypttab). We have to specify all LUKS devices manually, as + # registering the necessary devices is usually initrd's job (via btrfs device scan) + dev_stub="/dev/mapper/encbtrfs" + echo "/dev/mapper/encbtrfs0 $mpoint btrfs device=${dev_stub}0,device=${dev_stub}1,device=${dev_stub}2,device=${dev_stub}3 0 2" >>/etc/fstab + # Tell systemd about the new mount + systemctl daemon-reload + # Restart cryptsetup.target to trigger autounlock of partitions in /etc/crypttab + systemctl restart cryptsetup.target + # Start the corresponding mount unit and check if the btrfs device was reconstructed + # correctly + systemctl start "${mpoint##*/}.mount" + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + grep "hello there" "$mpoint/test" + # Cleanup + systemctl stop "${mpoint##*/}.mount" + systemctl stop systemd-cryptsetup@encbtrfs{0..3} + sed -i "/${mpoint##*/}/d" /etc/fstab + : >/etc/crypttab + rm -fr "$mpoint" + systemctl daemon-reload + udevadm settle +} + +testcase_iscsi_lvm() { + local dev i label link lun_id mpoint target_name uuid + local target_ip="127.0.0.1" + local target_port="3260" + local vgroup="iscsi_lvm$RANDOM" + local expected_symlinks=() + local devices=( + /dev/disk/by-id/scsi-0systemd_foobar_deadbeefiscsi{0..3} + ) + + . /etc/os-release + if [[ "$ID" == "ubuntu" ]]; then + echo "LVM on Ubuntu is broken, skipping the test" | tee --append /skipped + exit 77 + fi + + ls -l "${devices[@]}" + + # Start the target daemon (debian names it tgt.service so make sure we handle that) + if systemctl list-unit-files tgt.service; then + systemctl start tgt + systemctl status tgt + elif systemctl list-unit-files tgtd.service; then + systemctl start tgtd + systemctl status tgtd + else + echo "This test requires tgtd but it is not installed, skipping ..." | tee --append /skipped + exit 77 + fi + + echo "iSCSI LUNs backed by devices" + # See RFC3721 and RFC7143 + target_name="iqn.2021-09.com.example:iscsi.test" + # Initialize a new iSCSI target <$target_name> consisting of 4 LUNs, each + # backed by a device + tgtadm --lld iscsi --op new --mode target --tid=1 --targetname "$target_name" + for ((i = 0; i < ${#devices[@]}; i++)); do + # lun-0 is reserved by iSCSI + lun_id="$((i + 1))" + tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun "$lun_id" -b "${devices[$i]}" + tgtadm --lld iscsi --op update --mode logicalunit --tid 1 --lun "$lun_id" + expected_symlinks+=( + "/dev/disk/by-path/ip-$target_ip:$target_port-iscsi-$target_name-lun-$lun_id" + ) + done + tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL + # Configure the iSCSI initiator + iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + helper_check_device_symlinks + helper_check_device_units + # Cleanup + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout + tgtadm --lld iscsi --op delete --mode target --tid=1 + + echo "iSCSI LUNs backed by files + LVM" + # Note: we use files here to "trick" LVM the disks are indeed on a different + # host, so it doesn't automagically detect another path to the backing + # device once we disconnect the iSCSI devices + target_name="iqn.2021-09.com.example:iscsi.lvm.test" + mpoint="$(mktemp -d /iscsi_storeXXX)" + expected_symlinks=() + # Use the first device as it's configured with larger capacity + udevadm lock --device "${devices[0]}" mkfs.ext4 -L iscsi_store "${devices[0]}" + udevadm wait --settle --timeout=30 "${devices[0]}" + mount "${devices[0]}" "$mpoint" + for i in {1..4}; do + dd if=/dev/zero of="$mpoint/lun$i.img" bs=1M count=32 + done + # Initialize a new iSCSI target <$target_name> consisting of 4 LUNs, each + # backed by a file + tgtadm --lld iscsi --op new --mode target --tid=2 --targetname "$target_name" + # lun-0 is reserved by iSCSI + for i in {1..4}; do + tgtadm --lld iscsi --op new --mode logicalunit --tid 2 --lun "$i" -b "$mpoint/lun$i.img" + tgtadm --lld iscsi --op update --mode logicalunit --tid 2 --lun "$i" + expected_symlinks+=( + "/dev/disk/by-path/ip-$target_ip:$target_port-iscsi-$target_name-lun-$i" + ) + done + tgtadm --lld iscsi --op bind --mode target --tid 2 -I ALL + # Configure the iSCSI initiator + iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + helper_check_device_symlinks + helper_check_device_units + # Add all iSCSI devices into a LVM volume group, create two logical volumes, + # and check if necessary symlinks exist (and are valid) + lvm pvcreate -y "${expected_symlinks[@]}" + lvm pvs + lvm vgcreate "$vgroup" -y "${expected_symlinks[@]}" + lvm vgs + lvm vgchange -ay "$vgroup" + lvm lvcreate -y -L 4M "$vgroup" -n mypart1 + lvm lvcreate -y -L 8M "$vgroup" -n mypart2 + lvm lvs + udevadm wait --settle --timeout=30 "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" + mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1" + udevadm trigger --settle "/dev/$vgroup/mypart1" + udevadm wait --settle --timeout=30 "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + # Disconnect the iSCSI devices and check all the symlinks + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout + # "Reset" the DM state, since we yanked the backing storage from under the LVM, + # so the currently active VGs/LVs are invalid + dmsetup remove_all --deferred + # The LVM and iSCSI related symlinks should be gone + udevadm wait --settle --timeout=30 --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1" "${expected_symlinks[@]}" + helper_check_device_symlinks "/dev/disk" + helper_check_device_units + # Reconnect the iSCSI devices and check if everything get detected correctly + iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + # Cleanup + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout + tgtadm --lld iscsi --op delete --mode target --tid=2 + umount "$mpoint" + rm -rf "$mpoint" +} + +testcase_long_sysfs_path() { + local cursor link logfile mpoint + local expected_symlinks=( + "/dev/disk/by-label/data_vol" + "/dev/disk/by-label/swap_vol" + "/dev/disk/by-partlabel/test_swap" + "/dev/disk/by-partlabel/test_part" + "/dev/disk/by-partuuid/deadbeef-dead-dead-beef-000000000000" + "/dev/disk/by-uuid/deadbeef-dead-dead-beef-111111111111" + "/dev/disk/by-uuid/deadbeef-dead-dead-beef-222222222222" + ) + + # Create a cursor file to skip messages generated by udevd in initrd, as it + # might not be the same up-to-date version as we currently run (hence generating + # messages we check for later and making the test fail) + cursor="$(mktemp)" + journalctl --cursor-file="${cursor:?}" -n0 -q + + # Make sure the test device is connected and show its "wonderful" path + stat /sys/block/vda + readlink -f /sys/block/vda/dev + + dev="/dev/vda" + udevadm lock --device "$dev" sfdisk "$dev" <<EOF +label: gpt + +name="test_swap", size=32M +uuid="deadbeef-dead-dead-beef-000000000000", name="test_part", size=5M +EOF + udevadm settle + udevadm lock --device "${dev}1" mkswap -U "deadbeef-dead-dead-beef-111111111111" -L "swap_vol" "${dev}1" + udevadm lock --device "${dev}2" mkfs.ext4 -U "deadbeef-dead-dead-beef-222222222222" -L "data_vol" "${dev}2" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + + # Try to mount the data partition manually (using its label) + mpoint="$(mktemp -d /logsysfsXXX)" + mount LABEL=data_vol "$mpoint" + touch "$mpoint/test" + umount "$mpoint" + # Do the same, but with UUID and using fstab + echo "UUID=deadbeef-dead-dead-beef-222222222222 $mpoint ext4 defaults 0 0" >>/etc/fstab + systemctl daemon-reload + mount "$mpoint" + timeout 30 bash -c "until systemctl -q is-active '$mpoint'; do sleep .2; done" + test -e "$mpoint/test" + umount "$mpoint" + + # Test out the swap partition + swapon -v -L swap_vol + swapoff -v -L swap_vol + + udevadm settle + + logfile="$(mktemp)" + # Check state of affairs after https://github.com/systemd/systemd/pull/22759 + # Note: can't use `--cursor-file` here, since we don't want to update the cursor + # after using it + [[ "$(journalctl --after-cursor="$(<"$cursor")" -q --no-pager -o short-monotonic -p info --grep "Device path.*vda.?' too long to fit into unit name" | wc -l)" -eq 0 ]] + [[ "$(journalctl --after-cursor="$(<"$cursor")" -q --no-pager -o short-monotonic --grep "Unit name .*vda.?\.device\" too long, falling back to hashed unit name" | wc -l)" -gt 0 ]] + # Check if the respective "hashed" units exist and are active (plugged) + systemctl status --no-pager "$(readlink -f /sys/block/vda/vda1)" + systemctl status --no-pager "$(readlink -f /sys/block/vda/vda2)" + # Make sure we don't unnecessarily spam the log + { journalctl -b -q --no-pager -o short-monotonic -p info --grep "/sys/devices/.+/vda[0-9]?" _PID=1 + UNIT=systemd-udevd.service || :;} | tee "$logfile" + [[ "$(wc -l <"$logfile")" -lt 10 ]] + + : >/etc/fstab + rm -fr "${cursor:?}" "${logfile:?}" "${mpoint:?}" +} + +testcase_mdadm_basic() { + local i part_name raid_name raid_dev uuid + local expected_symlinks=() + local devices=( + /dev/disk/by-id/scsi-0systemd_foobar_deadbeefmdadm{0..4} + ) + + ls -l "${devices[@]}" + + echo "Mirror raid (RAID 1)" + raid_name="mdmirror" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + uuid="aaaaaaaa:bbbbbbbb:cccccccc:00000001" + expected_symlinks=( + "$raid_dev" + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + ) + # Create a simple RAID 1 with an ext4 filesystem + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/scsi-0systemd_foobar_deadbeefmdadm{0..1} -v -f --level=1 --raid-devices=2 + udevadm wait --settle --timeout=30 "$raid_dev" + # udevd does not lock md devices, hence we need to trigger uevent after creating filesystem. + mkfs.ext4 -L "$part_name" "$raid_dev" + udevadm trigger --settle "$raid_dev" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + for i in {0..9}; do + echo "Disassemble - reassemble loop, iteration #$i" + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + mdadm --assemble "$raid_dev" --name "$raid_name" -v + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + done + helper_check_device_symlinks + helper_check_device_units + # Cleanup + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + + echo "Parity raid (RAID 5)" + raid_name="mdparity" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + uuid="aaaaaaaa:bbbbbbbb:cccccccc:00000101" + expected_symlinks=( + "$raid_dev" + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + ) + # Create a simple RAID 5 with an ext4 filesystem + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/scsi-0systemd_foobar_deadbeefmdadm{0..2} -v -f --level=5 --raid-devices=3 + udevadm wait --settle --timeout=30 "$raid_dev" + mkfs.ext4 -L "$part_name" "$raid_dev" + udevadm trigger --settle "$raid_dev" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + for i in {0..9}; do + echo "Disassemble - reassemble loop, iteration #$i" + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + mdadm --assemble "$raid_dev" --name "$raid_name" -v + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + done + helper_check_device_symlinks + helper_check_device_units + # Cleanup + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + helper_check_device_units + + echo "Mirror + parity raid (RAID 10) + multiple partitions" + raid_name="mdmirpar" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + uuid="aaaaaaaa:bbbbbbbb:cccccccc:00001010" + expected_symlinks=( + "$raid_dev" + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + # Partitions + "${raid_dev}1" + "${raid_dev}2" + "${raid_dev}3" + "/dev/disk/by-id/md-name-H:$raid_name-part1" + "/dev/disk/by-id/md-name-H:$raid_name-part2" + "/dev/disk/by-id/md-name-H:$raid_name-part3" + "/dev/disk/by-id/md-uuid-$uuid-part1" + "/dev/disk/by-id/md-uuid-$uuid-part2" + "/dev/disk/by-id/md-uuid-$uuid-part3" + ) + # Create a simple RAID 10 with an ext4 filesystem + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/scsi-0systemd_foobar_deadbeefmdadm{0..3} -v -f --level=10 --raid-devices=4 + udevadm wait --settle --timeout=30 "$raid_dev" + # Partition the raid device + # Here, 'udevadm lock' is meaningless, as udevd does not lock MD devices. + # We need to trigger uevents after sfdisk and mkfs. + sfdisk --wipe=always "$raid_dev" <<EOF +label: gpt + +uuid="deadbeef-dead-dead-beef-111111111111", name="mdpart1", size=8M +uuid="deadbeef-dead-dead-beef-222222222222", name="mdpart2", size=32M +uuid="deadbeef-dead-dead-beef-333333333333", name="mdpart3", size=16M +EOF + udevadm trigger --settle --parent-match "$raid_dev" + udevadm wait --settle --timeout=30 "/dev/disk/by-id/md-uuid-$uuid-part2" + mkfs.ext4 -L "$part_name" "/dev/disk/by-id/md-uuid-$uuid-part2" + udevadm trigger --settle "/dev/disk/by-id/md-uuid-$uuid-part2" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + for i in {0..9}; do + echo "Disassemble - reassemble loop, iteration #$i" + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + mdadm --assemble "$raid_dev" --name "$raid_name" -v + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + done + helper_check_device_symlinks + helper_check_device_units + # Cleanup + mdadm -v --stop "$raid_dev" + # Check if all expected symlinks were removed after the cleanup + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + helper_check_device_units +} + +testcase_mdadm_lvm() { + local part_name raid_name raid_dev uuid vgroup + local expected_symlinks=() + local devices=( + /dev/disk/by-id/scsi-0systemd_foobar_deadbeefmdadmlvm{0..4} + ) + + ls -l "${devices[@]}" + + raid_name="mdlvm" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + vgroup="${raid_name}_vg" + uuid="aaaaaaaa:bbbbbbbb:ffffffff:00001010" + expected_symlinks=( + "$raid_dev" + "/dev/$vgroup/mypart1" # LVM partition + "/dev/$vgroup/mypart2" # LVM partition + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + ) + # Create a RAID 10 with LVM + ext4 + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/scsi-0systemd_foobar_deadbeefmdadmlvm{0..3} -v -f --level=10 --raid-devices=4 + udevadm wait --settle --timeout=30 "$raid_dev" + # Create an LVM on the MD + lvm pvcreate -y "$raid_dev" + lvm pvs + lvm vgcreate "$vgroup" -y "$raid_dev" + lvm vgs + lvm vgchange -ay "$vgroup" + lvm lvcreate -y -L 4M "$vgroup" -n mypart1 + lvm lvcreate -y -L 8M "$vgroup" -n mypart2 + lvm lvs + udevadm wait --settle --timeout=30 "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" + mkfs.ext4 -L "$part_name" "/dev/$vgroup/mypart2" + udevadm trigger --settle "/dev/$vgroup/mypart2" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + # Disassemble the array + lvm vgchange -an "$vgroup" + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + helper_check_device_symlinks + helper_check_device_units + # Reassemble it and check if all required symlinks exist + mdadm --assemble "$raid_dev" --name "$raid_name" -v + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + helper_check_device_symlinks + helper_check_device_units + # Cleanup + lvm vgchange -an "$vgroup" + mdadm -v --stop "$raid_dev" + # Check if all expected symlinks were removed after the cleanup + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + helper_check_device_units +} + +udevadm settle +udevadm control --log-level debug +lsblk -a + +echo "Check if all symlinks under /dev/disk/ are valid (pre-test)" +helper_check_device_symlinks + +# TEST_FUNCTION_NAME is passed on the kernel command line via systemd.setenv= +# in the respective test.sh file +if ! command -v "${TEST_FUNCTION_NAME:?}"; then + echo >&2 "Missing verification handler for test case '$TEST_FUNCTION_NAME'" + exit 1 +fi + +echo "TEST_FUNCTION_NAME=$TEST_FUNCTION_NAME" +"$TEST_FUNCTION_NAME" +udevadm settle + +echo "Check if all symlinks under /dev/disk/ are valid (post-test)" +helper_check_device_symlinks + +udevadm control --log-level info + +systemctl status systemd-udevd + +touch /testok |