| #!/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")" |
| echo "$link -> $target" |
| # 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) |
| )} |
| |
| # Wait for a specific device link to appear |
| # Arguments: |
| # $1 - device path |
| # $2 - number of retries (default: 10) |
| helper_wait_for_dev() { |
| local dev="${1:?}" |
| local ntries="${2:-10}" |
| local i |
| |
| for ((i = 0; i < ntries; i++)); do |
| test ! -e "$dev" || return 0 |
| sleep .2 |
| done |
| |
| return 1 |
| } |
| |
| # Wrapper around `helper_wait_for_lvm_activate()` and `helper_wait_for_pvscan()` |
| # functions to cover differences between pre and post lvm 2.03.14, which introduced |
| # a new way of vgroup autoactivation |
| # See: https://sourceware.org/git/?p=lvm2.git;a=commit;h=67722b312390cdab29c076c912e14bd739c5c0f6 |
| # Arguments: |
| # $1 - device path (for helper_wait_for_pvscan()) |
| # $2 - volume group name (for helper_wait_for_lvm_activate()) |
| # $3 - number of retries (default: 10) |
| helper_wait_for_vgroup() { |
| local dev="${1:?}" |
| local vgroup="${2:?}" |
| local ntries="${3:-10}" |
| |
| if ! systemctl -q list-unit-files lvm2-pvscan@.service >/dev/null; then |
| helper_wait_for_lvm_activate "$vgroup" "$ntries" |
| else |
| helper_wait_for_pvscan "$dev" "$ntries" |
| fi |
| } |
| |
| # Wait for the lvm-activate-$vgroup.service of a specific $vgroup to finish |
| # Arguments: |
| # $1 - volume group name |
| # $2 - number of retries (default: 10) |
| helper_wait_for_lvm_activate() { |
| local vgroup="${1:?}" |
| local ntries="${2:-10}" |
| local i lvm_activate_svc |
| |
| lvm_activate_svc="lvm-activate-$vgroup.service" |
| for ((i = 0; i < ntries; i++)); do |
| if systemctl -q is-active "$lvm_activate_svc"; then |
| # Since the service is started via `systemd-run --no-block`, we need |
| # to wait until it finishes, otherwise we might continue while |
| # `vgchange` is still running |
| if [[ "$(systemctl show -P SubState "$lvm_activate_svc")" == exited ]]; then |
| return 0 |
| fi |
| else |
| # Since lvm 2.03.15 the lvm-activate transient unit no longer remains |
| # after finishing, so we have to treat non-existent units as a success |
| # as well |
| # See: https://sourceware.org/git/?p=lvm2.git;a=commit;h=fbd8b0cf43dc67f51f86f060dce748f446985855 |
| if [[ "$(systemctl show -P LoadState "$lvm_activate_svc")" == not-found ]]; then |
| return 0 |
| fi |
| fi |
| |
| sleep .5 |
| done |
| |
| return 1 |
| } |
| |
| # Wait for the lvm2-pvscan@.service of a specific device to finish |
| # Arguments: |
| # $1 - device path |
| # $2 - number of retries (default: 10) |
| helper_wait_for_pvscan() { |
| local dev="${1:?}" |
| local ntries="${2:-10}" |
| local MAJOR MINOR i pvscan_svc real_dev |
| |
| # Sanity check we got a valid block device (or a symlink to it) |
| real_dev="$(readlink -f "$dev")" |
| if [[ ! -b "$real_dev" ]]; then |
| echo >&2 "ERROR: '$dev ($real_dev) is not a valid block device'" |
| return 1 |
| fi |
| |
| # Get major and minor numbers from the udev database |
| # (udevadm returns MAJOR= and MINOR= expressions, so let's pull them into |
| # the current environment via `source` for easier parsing) |
| # |
| # shellcheck source=/dev/null |
| source <(udevadm info -q property "$real_dev" | grep -E "(MAJOR|MINOR)=") |
| # Sanity check if we got correct major and minor numbers |
| test -e "/sys/dev/block/$MAJOR:$MINOR/" |
| |
| # Wait n_tries*0.5 seconds until the respective lvm2-pvscan service becomes |
| # active (i.e. it got executed and finished) |
| pvscan_svc="lvm2-pvscan@$MAJOR:$MINOR.service" |
| for ((i = 0; i < ntries; i++)); do |
| ! systemctl -q is-active "$pvscan_svc" || return 0 |
| sleep .5 |
| done |
| |
| return 1 |
| } |
| |
| # Generate an `flock` command line for a device list |
| # |
| # This is useful mainly for mkfs.btrfs, which doesn't hold the lock on each |
| # device for the entire duration of mkfs.btrfs, causing weird races between udev |
| # and mkfs.btrfs. This function creates an array of chained flock calls to take |
| # the lock of all involved devices, which can be then used in combination with |
| # mkfs.btrfs to mitigate the issue. |
| # |
| # For example, calling: |
| # helper_generate_flock_cmdline my_array /dev/loop1 /dev/loop2 /dev/loop3 |
| # |
| # will result in "${my_array[@]}" containing: |
| # flock -x /dev/loop1 flock -x /dev/loop2 flock -x /dev/loop3 |
| # |
| # Note: the array will be CLEARED before the first assignment |
| # |
| # Arguments: |
| # $1 - NAME of an array in which the commands/argument will be stored |
| # $2-$n - path to devices |
| helper_generate_flock_cmdline() { |
| # Create a name reference to the array passed as the first argument |
| # (requires bash 4.3+) |
| local -n cmd_array="${1:?}" |
| shift |
| |
| if [[ $# -eq 0 ]]; then |
| echo >&2 "Missing argument(s): device path(s)" |
| return 1 |
| fi |
| |
| cmd_array=() |
| for dev in "$@"; do |
| cmd_array+=("flock" "-x" "$dev") |
| done |
| } |
| |
| testcase_megasas2_basic() { |
| lsblk -S |
| [[ "$(lsblk --scsi --noheadings | wc -l)" -ge 128 ]] |
| } |
| |
| testcase_nvme_basic() { |
| lsblk --noheadings | grep "^nvme" |
| [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 28 ]] |
| } |
| |
| testcase_virtio_scsi_identically_named_partitions() { |
| lsblk --noheadings -a -o NAME,PARTLABEL |
| [[ "$(lsblk --noheadings -a -o NAME,PARTLABEL | grep -c "Hello world")" -eq $((16 * 8)) ]] |
| } |
| |
| 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 |
| 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..63}; 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 |
| 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" |
| ) |
| for link in "${part_links[@]}"; do |
| test -e "$link" |
| done |
| |
| # 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 |
| for link in "${part_links[@]}"; do |
| test -e "$link" |
| done |
| 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() { |
| local blockdev part partscript |
| |
| blockdev="$(readlink -f /dev/disk/by-id/scsi-*_deadbeeftest)" |
| partscript="$(mktemp)" |
| |
| if [[ ! -b "$blockdev" ]]; then |
| echo "ERROR: failed to find the test SCSI block device" |
| return 1 |
| fi |
| |
| cat >"$partscript" <<EOF |
| $(printf 'name="test%d", size=2M\n' {1..50}) |
| EOF |
| |
| # Initial partition table |
| sfdisk -q -X gpt "$blockdev" <"$partscript" |
| |
| # 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 in {1..100}; do |
| sfdisk -q --delete "$blockdev" |
| sfdisk -q -X gpt "$blockdev" <"$partscript" |
| |
| if ((i % 10 == 0)); then |
| udevadm settle |
| helper_check_device_symlinks |
| fi |
| done |
| |
| rm -f "$partscript" |
| } |
| |
| testcase_lvm_basic() { |
| local i part |
| local vgroup="MyTestGroup$RANDOM" |
| local devices=( |
| /dev/disk/by-id/ata-foobar_deadbeeflvm{0..3} |
| ) |
| |
| # 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 8M "$vgroup" -n mypart2 |
| lvm lvs |
| udevadm settle |
| test -e "/dev/$vgroup/mypart1" |
| test -e "/dev/$vgroup/mypart2" |
| mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1" |
| udevadm settle |
| test -e "/dev/disk/by-label/mylvpart1" |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| |
| # Disable the VG and check symlinks... |
| lvm vgchange -an "$vgroup" |
| udevadm settle |
| test ! -e "/dev/$vgroup" |
| test ! -e "/dev/disk/by-label/mylvpart1" |
| helper_check_device_symlinks "/dev/disk" |
| |
| # reenable the VG and check the symlinks again if all LVs are properly activated |
| lvm vgchange -ay "$vgroup" |
| udevadm settle |
| test -e "/dev/$vgroup/mypart1" |
| test -e "/dev/$vgroup/mypart2" |
| test -e "/dev/disk/by-label/mylvpart1" |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| |
| # Same as above, but now with more "stress" |
| for i in {1..50}; do |
| lvm vgchange -an "$vgroup" |
| lvm vgchange -ay "$vgroup" |
| |
| if ((i % 5 == 0)); then |
| udevadm settle |
| test -e "/dev/$vgroup/mypart1" |
| test -e "/dev/$vgroup/mypart2" |
| test -e "/dev/disk/by-label/mylvpart1" |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| fi |
| done |
| |
| # Remove the first LV |
| lvm lvremove -y "$vgroup/mypart1" |
| udevadm settle |
| test ! -e "/dev/$vgroup/mypart1" |
| test -e "/dev/$vgroup/mypart2" |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| |
| # Create & remove LVs in a loop, i.e. with more "stress" |
| for i in {1..16}; do |
| # 1) Create 16 logical volumes |
| for part in {0..15}; do |
| lvm lvcreate -y -L 4M "$vgroup" -n "looppart$part" |
| done |
| |
| # 2) Immediately remove them |
| lvm lvremove -y "$vgroup"/looppart{0..15} |
| |
| # 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 |
| udevadm settle |
| for part in {0..15}; do |
| test ! -e "/dev/$vgroup/looppart$part" |
| done |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| fi |
| done |
| } |
| |
| testcase_btrfs_basic() { |
| local dev_stub i label mpoint uuid |
| local flock_cmd=() |
| local devices=( |
| /dev/disk/by-id/ata-foobar_deadbeefbtrfs{0..3} |
| ) |
| |
| ls -l "${devices[@]}" |
| |
| echo "Single device: default settings" |
| uuid="deadbeef-dead-dead-beef-000000000000" |
| label="btrfs_root" |
| helper_generate_flock_cmdline flock_cmd "${devices[0]}" |
| "${flock_cmd[@]}" mkfs.btrfs -L "$label" -U "$uuid" "${devices[0]}" |
| udevadm settle |
| btrfs filesystem show |
| test -e "/dev/disk/by-uuid/$uuid" |
| test -e "/dev/disk/by-label/$label" |
| helper_check_device_symlinks |
| |
| echo "Multiple devices: using partitions, data: single, metadata: raid1" |
| uuid="deadbeef-dead-dead-beef-000000000001" |
| label="btrfs_mpart" |
| 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 settle |
| # We need to flock only the device itself, not its partitions |
| helper_generate_flock_cmdline flock_cmd "${devices[0]}" |
| "${flock_cmd[@]}" mkfs.btrfs -d single -m raid1 -L "$label" -U "$uuid" /dev/disk/by-partlabel/diskpart{1..4} |
| udevadm settle |
| btrfs filesystem show |
| test -e "/dev/disk/by-uuid/$uuid" |
| test -e "/dev/disk/by-label/$label" |
| helper_check_device_symlinks |
| wipefs -a -f "${devices[0]}" |
| |
| echo "Multiple devices: using disks, data: raid10, metadata: raid10, mixed mode" |
| uuid="deadbeef-dead-dead-beef-000000000002" |
| label="btrfs_mdisk" |
| helper_generate_flock_cmdline flock_cmd "${devices[@]}" |
| "${flock_cmd[@]}" mkfs.btrfs -M -d raid10 -m raid10 -L "$label" -U "$uuid" "${devices[@]}" |
| udevadm settle |
| btrfs filesystem show |
| test -e "/dev/disk/by-uuid/$uuid" |
| test -e "/dev/disk/by-label/$label" |
| helper_check_device_symlinks |
| |
| 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 |
| 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 settle |
| test -e "/dev/disk/by-uuid/deadbeef-dead-dead-beef-11111111111$i" |
| test -e "/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,noearly" >>/etc/crypttab |
| systemctl daemon-reload |
| systemctl start "systemd-cryptsetup@encbtrfs$i" |
| done |
| helper_check_device_symlinks |
| # 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 |
| helper_generate_flock_cmdline flock_cmd /dev/mapper/encbtrfs{0..3} |
| "${flock_cmd[@]}" mkfs.btrfs -M -d raid1 -m raid1 -L "$label" -U "$uuid" /dev/mapper/encbtrfs{0..3} |
| udevadm settle |
| btrfs filesystem show |
| test -e "/dev/disk/by-uuid/$uuid" |
| test -e "/dev/disk/by-label/$label" |
| helper_check_device_symlinks |
| # 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} |
| test ! -e "/dev/disk/by-uuid/$uuid" |
| helper_check_device_symlinks |
| # 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" |
| btrfs filesystem show |
| test -e "/dev/disk/by-uuid/$uuid" |
| test -e "/dev/disk/by-label/$label" |
| helper_check_device_symlinks |
| 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/ata-foobar_deadbeefiscsi{0..3} |
| ) |
| |
| ls -l "${devices[@]}" |
| |
| # Start the target daemon |
| systemctl start tgtd |
| systemctl status tgtd |
| |
| 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 settle |
| # Check if all device symlinks are valid and if all expected device symlinks exist |
| for link in "${expected_symlinks[@]}"; do |
| # We need to do some active waiting anyway, as it may take kernel a bit |
| # to attach the newly connected SCSI devices |
| helper_wait_for_dev "$link" |
| test -e "$link" |
| done |
| udevadm settle |
| helper_check_device_symlinks |
| # 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 |
| mkfs.ext4 -L iscsi_store "${devices[0]}" |
| udevadm settle |
| 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 settle |
| # Check if all device symlinks are valid and if all expected device symlinks exist |
| for link in "${expected_symlinks[@]}"; do |
| # We need to do some active waiting anyway, as it may take kernel a bit |
| # to attach the newly connected SCSI devices |
| helper_wait_for_dev "$link" |
| test -e "$link" |
| done |
| udevadm settle |
| helper_check_device_symlinks |
| # 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 settle |
| test -e "/dev/$vgroup/mypart1" |
| test -e "/dev/$vgroup/mypart2" |
| mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1" |
| udevadm settle |
| test -e "/dev/disk/by-label/mylvpart1" |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| # 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 |
| udevadm settle |
| # The LVM and iSCSI related symlinks should be gone |
| test ! -e "/dev/$vgroup" |
| test ! -e "/dev/disk/by-label/mylvpart1" |
| for link in "${expected_symlinks[@]}"; do |
| test ! -e "$link" |
| done |
| helper_check_device_symlinks "/dev/disk" |
| # 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 settle |
| for link in "${expected_symlinks[@]}"; do |
| helper_wait_for_dev "$link" |
| helper_wait_for_vgroup "$link" "$vgroup" |
| test -e "$link" |
| done |
| udevadm settle |
| test -e "/dev/$vgroup/mypart1" |
| test -e "/dev/$vgroup/mypart2" |
| test -e "/dev/disk/by-label/mylvpart1" |
| helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" |
| # 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 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" |
| ) |
| |
| # Make sure the test device is connected and show its "wonderful" path |
| stat /sys/block/vda |
| readlink -f /sys/block/vda/dev |
| |
| for link in "${expected_symlinks[@]}"; do |
| test -e "$link" |
| done |
| |
| # 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" |
| 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)" |
| journalctl -b -q --no-pager -o short-monotonic -p info --grep "Device path.*vda.?' too long to fit into unit name" |
| # 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 "${logfile:?}" "${mpoint:?}" |
| } |
| |
| : >/failed |
| |
| 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 |
| rm /failed |