#!/usr/bin/env bash

# Copyright (C) 2021 Red Hat, Inc. All rights reserved.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

test_description='udev rule and systemd unit run vgchange'

SKIP_WITH_LVMPOLLD=1
SKIP_WITH_LVMLOCKD=1

. lib/inittest

# FIXME: currently test relies on several system properties to be
# explcitely configure and directly modifies their state

#
# $ cat /tmp/devs
# /dev/sdb
# /dev/sdc
# /dev/sdd
#
# Specify this file as LVM_TEST_DEVICE_LIST=/tmp/devs
# when running the test.
#
# This test will wipe these devices.
#

if [ -z ${LVM_TEST_DEVICE_LIST+x} ]; then
	skip "LVM_TEST_DEVICE_LIST is unset"
else
	echo "LVM_TEST_DEVICE_LIST is set to '$LVM_TEST_DEVICE_LIST'"
fi

test -e "$LVM_TEST_DEVICE_LIST" || skip

num_devs=$(wc -l < "$LVM_TEST_DEVICE_LIST")

RUNDIR="/run"
test -d "$RUNDIR" || RUNDIR="/var/run"
PVS_ONLINE_DIR="$RUNDIR/lvm/pvs_online"
VGS_ONLINE_DIR="$RUNDIR/lvm/vgs_online"
PVS_LOOKUP_DIR="$RUNDIR/lvm/pvs_lookup"

_clear_online_files() {
	# wait till udev is finished
	aux udev_wait
	rm -f "$PVS_ONLINE_DIR"/*
	rm -f "$VGS_ONLINE_DIR"/*
	rm -f "$PVS_LOOKUP_DIR"/*
}

test -d "$PVS_ONLINE_DIR" || mkdir -p "$PVS_ONLINE_DIR"
test -d "$VGS_ONLINE_DIR" || mkdir -p "$VGS_ONLINE_DIR"
test -d "$PVS_LOOKUP_DIR" || mkdir -p "$PVS_LOOKUP_DIR"
_clear_online_files

aux prepare_real_devs

aux lvmconf 'devices/dir = "/dev"'
aux lvmconf 'devices/use_devicesfile = 1'
DFDIR="$LVM_SYSTEM_DIR/devices"
DF="$DFDIR/system.devices"
mkdir "$DFDIR" || true
not ls "$DF"

get_real_devs

wipe_all() {
	for dev in "${REAL_DEVICES[@]}"; do
		wipefs -a "$dev"
	done
}

wait_lvm_activate() {
	local vgw=$1
	local wait=0
	rm status || true

	# time for service to be started
	sleep 1

 	while systemctl status lvm-activate-$vgw |tee status && test "$wait" -le 30; do
 		sleep .2
 		wait=$(( wait + 1 ))
	done
	cat status || true
}

# Test requires 3 devs
test "$num_devs" -gt 2 || skip
BDEV1=$(basename "$dev1")
BDEV2=$(basename "$dev2")
BDEV3=$(basename "$dev3")

wipe_all
touch "$DF"
for dev in "${REAL_DEVICES[@]}"; do
	pvcreate $dev
done

# 1 dev, 1 vg, 1 lv

vgcreate $vg1 "$dev1"
lvcreate -l1 -an -n $lv1 $vg1 "$dev1"

PVID1=$(pvs "$dev1" --noheading -o uuid | tr -d - | awk '{print $1}')

_clear_online_files
udevadm trigger --settle -c add "/sys/block/$BDEV1"

wait_lvm_activate $vg1

ls "$RUNDIR/lvm/pvs_online/$PVID1" || true
ls "$RUNDIR/lvm/vgs_online/$vg1" || true
journalctl -u lvm-activate-$vg1 | tee out || true
grep "now active" out
check lv_field $vg1/$lv1 lv_active "active"

vgchange -an $vg1
vgremove -y $vg1


# 2 devs, 1 vg, 2 lvs

vgcreate $vg2 "$dev1" "$dev2"
lvcreate -l1 -an -n $lv1 $vg2 "$dev1"
lvcreate -l1 -an -n $lv2 $vg2 "$dev2"

PVID1=$(pvs "$dev1" --noheading -o uuid | tr -d - | awk '{print $1}')
PVID2=$(pvs "$dev2" --noheading -o uuid | tr -d - | awk '{print $1}')

_clear_online_files

udevadm trigger --settle -c add "/sys/block/$BDEV1"
ls "$RUNDIR/lvm/pvs_online/$PVID1"
not ls "$RUNDIR/lvm/vgs_online/$vg2"
journalctl -u lvm-activate-$vg2 | tee out || true
not grep "now active" out
check lv_field $vg2/$lv1 lv_active ""
check lv_field $vg2/$lv2 lv_active ""

udevadm trigger --settle -c add "/sys/block/$BDEV2"
ls "$RUNDIR/lvm/pvs_online/$PVID2"
ls "$RUNDIR/lvm/vgs_online/$vg2"

wait_lvm_activate $vg2

journalctl -u lvm-activate-$vg2 | tee out || true
grep "now active" out
check lv_field $vg2/$lv1 lv_active "active"
check lv_field $vg2/$lv2 lv_active "active"

vgchange -an $vg2
vgremove -y $vg2


# 3 devs, 1 vg, 4 lvs, concurrent pvscans
# (attempting to have the pvscans run concurrently and race
# to activate the VG)

vgcreate $vg3 "$dev1" "$dev2" "$dev3"
lvcreate -l1 -an -n $lv1 $vg3 "$dev1"
lvcreate -l1 -an -n $lv2 $vg3 "$dev2"
lvcreate -l1 -an -n $lv3 $vg3 "$dev3"
lvcreate -l8 -an -n $lv4 -i 2 $vg3 "$dev1" "$dev2"

PVID1=$(pvs "$dev1" --noheading -o uuid | tr -d - | awk '{print $1}')
PVID2=$(pvs "$dev2" --noheading -o uuid | tr -d - | awk '{print $1}')
PVID3=$(pvs "$dev3" --noheading -o uuid | tr -d - | awk '{print $1}')

_clear_online_files

udevadm trigger -c add "/sys/block/$BDEV1" &
udevadm trigger -c add "/sys/block/$BDEV2" &
udevadm trigger -c add "/sys/block/$BDEV3"

aux udev_wait
wait_lvm_activate $vg3

find "$RUNDIR/lvm"
ls "$RUNDIR/lvm/pvs_online/$PVID1"
ls "$RUNDIR/lvm/pvs_online/$PVID2"
ls "$RUNDIR/lvm/pvs_online/$PVID3"
ls "$RUNDIR/lvm/vgs_online/$vg3"

journalctl -u lvm-activate-$vg3 | tee out || true
grep "now active" out
check lv_field $vg3/$lv1 lv_active "active"
check lv_field $vg3/$lv2 lv_active "active"
check lv_field $vg3/$lv3 lv_active "active"
check lv_field $vg3/$lv4 lv_active "active"

vgchange -an $vg3
vgremove -y $vg3


# 3 devs, 1 vg, 4 lvs, concurrent pvscans, metadata on only 1 PV

wipe_all
rm $DF
touch $DF
pvcreate --metadatacopies 0 "$dev1"
pvcreate --metadatacopies 0 "$dev2"
pvcreate "$dev3"

vgcreate $vg4 "$dev1" "$dev2" "$dev3"
lvcreate -l1 -an -n $lv1 $vg4 "$dev1"
lvcreate -l1 -an -n $lv2 $vg4 "$dev2"
lvcreate -l1 -an -n $lv3 $vg4 "$dev3"
lvcreate -l8 -an -n $lv4 -i 2 $vg4 "$dev1" "$dev2"

PVID1=$(pvs "$dev1" --noheading -o uuid | tr -d - | awk '{print $1}')
PVID2=$(pvs "$dev2" --noheading -o uuid | tr -d - | awk '{print $1}')
PVID3=$(pvs "$dev3" --noheading -o uuid | tr -d - | awk '{print $1}')

_clear_online_files

udevadm trigger -c add "/sys/block/$BDEV1" &
udevadm trigger -c add "/sys/block/$BDEV2" &
udevadm trigger -c add "/sys/block/$BDEV3"

aux udev_wait
wait_lvm_activate $vg4

ls "$RUNDIR/lvm/pvs_lookup/"
cat "$RUNDIR/lvm/pvs_lookup/$vg4" || true
ls "$RUNDIR/lvm/pvs_online/$PVID1"
ls "$RUNDIR/lvm/pvs_online/$PVID2"
ls "$RUNDIR/lvm/pvs_online/$PVID3"
ls "$RUNDIR/lvm/vgs_online/$vg4"
journalctl -u lvm-activate-$vg4 | tee out || true
grep "now active" out
check lv_field $vg4/$lv1 lv_active "active"
check lv_field $vg4/$lv2 lv_active "active"
check lv_field $vg4/$lv3 lv_active "active"
check lv_field $vg4/$lv4 lv_active "active"

vgchange -an $vg4
vgremove -y $vg4


# 3 devs, 3 vgs, 2 lvs in each vg, concurrent pvscans

wipe_all
rm "$DF"
touch "$DF"

vgcreate $vg5 "$dev1"
vgcreate $vg6 "$dev2"
vgcreate $vg7 "$dev3"
lvcreate -l1 -an -n $lv1 $vg5
lvcreate -l1 -an -n $lv2 $vg5
lvcreate -l1 -an -n $lv1 $vg6
lvcreate -l1 -an -n $lv2 $vg6
lvcreate -l1 -an -n $lv1 $vg7
lvcreate -l1 -an -n $lv2 $vg7

_clear_online_files

udevadm trigger -c add "/sys/block/$BDEV1" &
udevadm trigger -c add "/sys/block/$BDEV2" &
udevadm trigger -c add "/sys/block/$BDEV3"

aux udev_wait
wait_lvm_activate $vg5
wait_lvm_activate $vg6
wait_lvm_activate $vg7

ls "$RUNDIR/lvm/vgs_online/$vg5"
ls "$RUNDIR/lvm/vgs_online/$vg6"
ls "$RUNDIR/lvm/vgs_online/$vg7"
journalctl -u lvm-activate-$vg5 | tee out || true
grep "now active" out
journalctl -u lvm-activate-$vg6 | tee out || true
grep "now active" out
journalctl -u lvm-activate-$vg7 | tee out || true
grep "now active" out
check lv_field $vg5/$lv1 lv_active "active"
check lv_field $vg5/$lv2 lv_active "active"
check lv_field $vg6/$lv1 lv_active "active"
check lv_field $vg6/$lv2 lv_active "active"
check lv_field $vg7/$lv1 lv_active "active"
check lv_field $vg7/$lv2 lv_active "active"

vgchange -an $vg5
vgremove -y $vg5
vgchange -an $vg6
vgremove -y $vg6
vgchange -an $vg7
vgremove -y $vg7

# 3 devs, 1 vg, 1000 LVs

wipe_all
rm "$DF"
touch "$DF"
pvcreate --metadatacopies 0 "$dev1"
pvcreate "$dev2"
pvcreate "$dev3"
vgcreate -s 128K $vg8 "$dev1" "$dev2" "$dev3"

# Number of LVs to create
TEST_DEVS=1000
# On low-memory boxes let's not stress too much
test "$(aux total_mem)" -gt 524288 || TEST_DEVS=256

vgcfgbackup -f data $vg8

# Generate a lot of devices (size of 1 extent)
awk -v TEST_DEVS=$TEST_DEVS '/^\t\}/ {
    printf("\t}\n\tlogical_volumes {\n");
    cnt=0;
    for (i = 0; i < TEST_DEVS; i++) {
        printf("\t\tlvol%06d  {\n", i);
        printf("\t\t\tid = \"%06d-1111-2222-3333-2222-1111-%06d\"\n", i, i);
        print "\t\t\tstatus = [\"READ\", \"WRITE\", \"VISIBLE\"]";
        print "\t\t\tsegment_count = 1";
        print "\t\t\tsegment1 {";
        print "\t\t\t\tstart_extent = 0";
        print "\t\t\t\textent_count = 1";
        print "\t\t\t\ttype = \"striped\"";
        print "\t\t\t\tstripe_count = 1";
        print "\t\t\t\tstripes = [";
        print "\t\t\t\t\t\"pv0\", " cnt++;
        printf("\t\t\t\t]\n\t\t\t}\n\t\t}\n");
      }
  }
  {print}
' data >data_new

vgcfgrestore -f data_new $vg8

_clear_online_files

udevadm trigger -c add "/sys/block/$BDEV1" &
udevadm trigger -c add "/sys/block/$BDEV2" &
udevadm trigger -c add "/sys/block/$BDEV3"

aux udev_wait
wait_lvm_activate $vg8

ls "$RUNDIR/lvm/vgs_online/$vg8"
journalctl -u lvm-activate-$vg8 | tee out || true
grep "now active" out

num_active=$(lvs $vg8 --noheading -o active | grep -c active)

test "$num_active" -eq "$TEST_DEVS"

vgchange -an $vg8
vgremove -y $vg8

# 1 pv on an md dev, 1 vg

wait_md_create() {
	local md=$1

	while :; do
		if ! grep "$(basename $md)" /proc/mdstat; then
			echo "$md not ready"
			cat /proc/mdstat
			sleep 2
		else
			break
		fi
	done
	echo "$md" > WAIT_MD_DEV
}

test -f /proc/mdstat && grep -q raid1 /proc/mdstat || \
       modprobe raid1 || skip

wipe_all
rm "$DF"
touch "$DF"

aux mdadm_create --metadata=1.0 --level 1 --chunk=64 --raid-devices=2 "$dev1" "$dev2"
mddev=$(< MD_DEV)

wait_md_create "$mddev"
vgcreate $vg9 "$mddev"
lvmdevices --adddev "$mddev" || true

PVIDMD=$(pvs "$mddev" --noheading -o uuid | tr -d - | awk '{print $1}')
BDEVMD=$(basename "$mddev")

lvcreate -l1 -an -n $lv1 $vg9
lvcreate -l1 -an -n $lv2 $vg9

mdadm --stop "$mddev"
_clear_online_files
aux mdadm_assemble "$mddev" "$dev1" "$dev2"

# this trigger might be redundant because the mdadm --assemble
# probably triggers an add uevent
udevadm trigger --settle -c add /sys/block/$BDEVMD

wait_lvm_activate $vg9

ls "$RUNDIR/lvm/vgs_online/$vg9"
journalctl -u lvm-activate-$vg9 | tee out || true
grep "now active" out
check lv_field $vg9/$lv1 lv_active "active"
check lv_field $vg9/$lv2 lv_active "active"

vgchange -an $vg9
vgremove -y $vg9

mdadm --stop "$mddev"
aux udev_wait
wipe_all

# no devices file, filter with symlink of PV
# the pvscan needs to look at all dev names to
# match the symlink in the filter with the
# dev name (or major minor) passed to pvscan.
# This test doesn't really belong in this file
# because it's not testing lvm-activate.

aux lvmconf 'devices/use_devicesfile = 0'
_clear_online_files
rm "$DF"
vgcreate $vg10 "$dev1"
lvcreate -l1 -an -n $lv1 $vg10 "$dev1"

PVID1=$(pvs "$dev1" --noheading -o uuid | tr -d - | awk '{print $1}')
# PVID with dashes
OPVID1=$(pvs "$dev1" --noheading -o uuid | awk '{print $1}')

udevadm trigger --settle -c add "/sys/block/$BDEV1"

# uevent from the trigger should create this symlink
ls "/dev/disk/by-id/lvm-pv-uuid-$OPVID1"

vgchange -an $vg10
_clear_online_files

aux lvmconf "devices/filter = [ \"a|/dev/disk/by-id/lvm-pv-uuid-$OPVID1|\", \"r|.*|\" ]"
aux lvmconf 'devices/global_filter = [ "a|.*|" ]'

pvscan --cache -aay "$dev1"

check lv_field $vg10/$lv1 lv_active "active"

vgchange -an $vg10
_clear_online_files

aux lvmconf 'devices/filter = [ "a|lvm-pv-uuid|", "r|.*|" ]'
aux lvmconf 'devices/global_filter = [ "a|.*|" ]'

pvscan --cache -aay "$dev1"

check lv_field $vg10/$lv1 lv_active "active"

vgchange -an $vg10
vgremove -y $vg10
wipe_all

aux lvmconf 'devices/filter = [ "a|.*|" ]'
aux lvmconf 'devices/global_filter = [ "a|.*|" ]'

#
# system.devices contains different product_uuid and incorrect device IDs
#

SYS_DIR="$PWD/test/sys"

aux lvmconf "devices/use_devicesfile = 1" \
	"devices/device_id_sysfs_dir = \"$SYS_DIR/\""

WWID1="naa.111"
WWID2="naa.222"
PRODUCT_UUID1="11111111-2222-3333-4444-555555555555"
PRODUCT_UUID2="11111111-2222-3333-4444-666666666666"

vgcreate $vg11 "$dev1"
lvcreate -l1 -an -n $lv1 $vg11 "$dev1"

eval "$(pvs --noheading --nameprefixes -o major,minor,uuid "$dev1")"
MAJOR1=$LVM2_PV_MAJOR
MINOR1=$LVM2_PV_MINOR
OPVID1=$LVM2_PV_UUID
PVID1=${OPVID1//-/}

mkdir -p "$SYS_DIR/dev/block/$MAJOR1:$MINOR1/device"
echo "$WWID1" > "$SYS_DIR/dev/block/$MAJOR1:$MINOR1/device/wwid"
mkdir -p "$SYS_DIR/devices/virtual/dmi/id/"
echo "$PRODUCT_UUID1" > "$SYS_DIR/devices/virtual/dmi/id/product_uuid"

vgimportdevices $vg11

grep $PRODUCT_UUID1 "$DF"
grep $PVID1 "$DF"
grep $WWID1 "$DF"
grep "$dev1" "$DF"

# change wwid for dev1 and product_uuid for host

echo "$WWID2" > "$SYS_DIR/dev/block/$MAJOR1:$MINOR1/device/wwid"
echo "$PRODUCT_UUID2" > "$SYS_DIR/devices/virtual/dmi/id/product_uuid"

_clear_online_files

udevadm trigger --settle -c add "/sys/block/$BDEV1"

wait_lvm_activate $vg11

ls "$RUNDIR/lvm/vgs_online/$vg11"
journalctl -u lvm-activate-$vg11 | tee out || true
grep "now active" out

# Run ordinary command that will refresh device ID in system.devices
pvs -o+uuid | tee out
grep "$dev1" out
grep "$OPVID1" out

# check new wwid for dev1 and new product_uuid for host
cat "$DF"
grep $PRODUCT_UUID2 "$DF"
not grep $PRODUCT_UUID1 "$DF"
grep $PVID1 "$DF"
grep $WWID2 "$DF"
not grep $WWID1 "$DF"
grep "$dev1" "$DF"

check lv_field $vg11/$lv1 lv_active "active"

vgchange -an $vg11
vgremove -y $vg11

rm -rf "$SYS_DIR"

wipe_all